re课程大纲
逆向工程基础(X86/64,ARM,AARCH64各种架构:汇编10分钟入门、calling convention) IDA Pro、HexRay、OllyDBG、GDB工具准备与快速入门 PE/ELF文件格式 Windows逆向:MFC分析、.Net逆向、X64逆向等 Linux逆向:LD_PRELOAD、混淆与反混淆、OLLVM 加壳与脱壳:壳简介、各类手工脱壳方法 反调试技术:去除花指令、反虚拟机、反调试各种trick 常见算法分析与识别 WinDBG和驱动调试:WinDBG、微软符号表、rootkit调试、加载驱动 其他技巧:符号执行、Pintools、恢复符号、识别虚表、编写IDA Processor
tool类型:
今天
disassembler 反汇编器
tracer 追踪函数调用
debugger 调试器
decompiler 反编译器
emulator 模拟器
symbolic execution符号执行
具体一些软件软件:
比较大型的有IDA,BINARYNINJA ,radare2
debugger: od 、windbg 、gdb
python 反汇编框架:
capstone:一个轻量级的多平台多架构支持的反汇编框架。支持包括ARM,ARM64,MIPS和x86/x64平台
keystone:一个轻量级的多平台多架构支持的汇编框架
CPU emulator 模拟器:unicorn
unicorn连接点击这里
intel process trace 用硬件来做trace
PIN 插桩
panda静态加动态分析, 在github上面的开源项目
要怎么逆向
-
先收集信息
利用string/file/binwalk/IDA etc等工具,收集信息,发现特征字符串,然后通过google或者github搜索,说不定就能找到源码 -
定位 关键代码:
- 跟控制流走
- 根据数据的交叉引用
- 根据代码的交叉引用
- 用 tracer追踪函数流
- 用 调试器动态调试,通过输入特征字符串,查看内存来定位关键代码
一些逆向的小技巧:
- regular pattern of code
- 要学会区分人写的代码跟编译器自己加上去的代码
- 学会区分库代码和程序代码
- 不需要仔细逆向code by compiler
- regular pattern of binary
- 熟悉常见的二进制文件布局
- 识别编译器优化的代码
- identify the open source code
- string xref 通过字符串的交叉引用来查询
- code style 通过代码的风格来猜测或者判断
- Dynamic Analysis
- 善用动态调试,找到关键代码或者验证自己的猜测是否正确
- debugging
- tracing 通过trace 函数执行流来分析
- symbolic execution 符号执行 --> angr库
- taint analysis 污点分析
- Reverse code block by block
- 要认识常见算法 各种加密算法 比如 Tea/XTea/XXTea/IDEA/RC4/RC5/RC6/AES/DES/IDEA/MD5/SHA1/SHA256 etc
- 熟悉数据结构 比如图、 树、哈希表等
- 共同设计的模式识别??? proxy stub
- 熟悉常见的框架
然后大佬还讲了一下代码混淆:
介绍了几种常见的代码混淆
- ollvm 解决方法
- control flow flatten
- movfuscator
- push rax,ret
- vm/self modify code
反混淆的方法: - 回复控制流程
- 模拟执行或者符号执行
还介绍了一些壳,这就不记录了
讲的大概就这么多
做的第一道逆向题 re0
先动态解密,因为程序一开始先进行了一个for循环,将judge地址附近的内容与'0xc'进行了异或,所以judge函数不能反编译
就会显示这个东东
image.png有两种方法解决:
一种是用ida-python 动态解密,将judge解密
另一种是 运行一下程序,然后把它dump下来
这里讲一下第一种:
ida-python 有内置的api函数,通过ida_bytes库的getbytes函数,获取judge地址的182个字节
buf = ida_bytes.get_bytes(0x600B00, 182)
然后与'0xc'异或后,将值填入相应的地址,这里用了patch_bytes函数
s = ""
for i in xrange(0, len(buf)):
s += chr(0xC ^ ord(buf[i]))
ida_bytes.patch_bytes(0x600B00, s)
这两个函数的定义:
def get_bytes(ea, size, use_dbg = False):
"""
Return the specified number of bytes of the program
@param ea: linear address
@param size: size of buffer in normal 8-bit bytes
@param use_dbg: if True, use debugger memory, otherwise just the database
@return: None on failure
otherwise a string containing the read bytes
"""
if use_dbg:
return ida_idd.dbg_read_memory(ea, size)
else:
return ida_bytes.get_bytes(ea, size)
def patch_byte(ea, value):
"""
Change value of a program byte
If debugger was active then the debugged process memory will be patched too
@param ea: linear address
@param value: new value of the byte
@return: 1 if the database has been modified,
0 if either the debugger is running and the process' memory
has value 'value' at address 'ea',
or the debugger is not running, and the IDB
has value 'value' at address 'ea already.
"""
return ida_bytes.patch_byte(ea, value)
所以写个script,然后用ida运行这个scritp就可以查看judge函数了
不过judge函数的尾部点问题,所以看汇编代码找到正确的尾部,然后重新设置一下
image.png再反编译一下,发现judge函数可以正确显示了,但是函数栈帧有点问题,它变量显示形式有点奇怪,这里先undefine 函数,用快捷键U,再将函数重新分析一遍 ,快捷键为p,就可以看到正常函数变量了
这里只是做了一个简单的异或,然后比较,写个脚本解密一下flag就出来了
flag_enc="fmcd\x7fk7d;V\x60;np"
flag=""
for i in range(len(flag_enc)):
c=flag_enc[i]
flag+=chr(ord(c)^i)
print flag
flag为:flag{n1c3_j0b}
第二道 re1
这是一道MFC??? 注册机的题
大佬讲了两种思路:
一种是将比较flag的汇编代码修改成mov,然后调试查看flag
另一种是慢慢动态调试,利用od,在比较flag的地方查看flag值
因为这是一道注册机的题,看着很容易想到MessageBoxA函数,所以通过MessageBoxA函数的调用,来追踪程序流,找到关键函数
因为这在追踪的时候会追踪到一段不能反编译的汇编代码,在这里要通过自己定义函数,找到正确的函数入口,在那个地址create一个function,就可以反编译了
image.png image.png同时通过观察它的汇编代码可以发现,在cmp命令中,它比较的注册码长度的为0x21
同时进行注册码检验的函数反编译代码如下:可以看出它是明文比较,所以可以通过动态调试,观察堆栈的值来获得正确的注册码
打开od调试一下,在0x4015e6处下个断点,按f9执行到断点处,输入33个1
然后就停在断点断点处了,然后往下拉下看到检验注册码的函数,在cmp那下个断点
在cmp那里下个断点,然后单步执行到那后,观察堆栈的情况
image.png可以看到此时堆栈有flag的第一个字母,通过连续执行这个循环,就可以将flag一位一位的得出来
flag:flag{The-Y3ll0w-turb4ns-Upri$ing}
evr:
这道题无法直接反编译main函数,它的sp指针有问题,可以用alt+k快捷键手动修复指针,错误修改完后,反编译进入main函数
程序要求先输入username,判断是否正确,然后再执行下面的代码,这里可以不逆它,它和flag没有多大的关系
看了下后面的函数,定位flag字符串密文存放的地址
将 flag_enc提取出来
用 ida-python 的get_bytes函数
buf = get_bytes(0x)
print map(ord,buf)
image.png
字符串一共经过四种加密
- 与0x76异或
- c=c ^ 0x76 ^ 0xad
c=((2*c)&0xff)&0xaa|(0xff&((c&0xaa)>>1)) - c=c ^ 0x76 ^ 0xbe
c=((4*c)&0xff)&0xcc|(0xff&((c&0xcc)>>2)) - c=c ^ 0x76 ^ 0xef
c=((16*c)&0xff)&0xf0|(0xff&((c&0xf0)>>4))
这里用爆破的方式来获取flag,爆破的话,从0x20开始爆破,因为爆破的是可见字符
flag_enc=[30, 21, 2, 16, 13, 72, 72, 111, 221, 221, 72, 100, 99, 215, 46, 44, 254, 106, 109, 42, 242, 111, 154, 77, 139, 75, 10, 138, 79, 69, 23, 70, 79, 20, 11];
flag=""
for i in range(7):
flag+=chr(flag_enc[i]^0x76)
for i in range(7):
for c in range(0x20,0x7f):
origc=c
c=c^0x76^0xad
c=((2*c)&0xff)&0xaa|(0xff&((c&0xaa)>>1))
if c==flag_enc[7+i]:
flag+=chr(origc)
break
for i in range(7):
for c in range(0x20,0x7f):
origc=c
c=c^0x76^0xbe
c=((4*c)&0xff)&0xcc|(0xff&((c&0xcc)>>2))
if c==flag_enc[14+i]:
flag+=chr(origc)
break
for i in range(7):
for c in range(0x20,0x7f):
origc=c
c=c^0x76^0xef
c=((16*c)&0xff)&0xf0|(0xff&((c&0xf0)>>4))
if c==flag_enc[21+i]:
flag+=chr(origc)
break
for i in range(7):
flag+=chr(flag_enc[i+28]^0x76)
print flag
flag为:hctf{>>D55_CH0CK3R_B0o0M!-9193a09b}
re2:
打开程序后它让你输入一个flag字符串,然后判断是否正确,然后打印对错,这里有个较坑的是它对flag加密的函数在main函数中没有,要通过调试找出来
如图:
加密后的密文是
['a', 'j', 'y', 'g', 'k', 'F', 'm', '.', '\x7f', '_', '~', '-', 'S', 'V', '{', '8', 'm', 'L', 'n']
写个解密脚本就出flag了
脚本
#!/usr/bin/env python
# -*- coding:UTF-8 -*-
flag_enc = "ajygkFm.\x7f_~-SV{8mLn"
flag_enc = list(flag_enc)
flag = flag_enc
flag[18] = chr(ord(flag_enc[18])^0x13)
for i in range(18):
k = 17 - i
flag[k] = chr(ord(flag_enc[k])^k)
if(k%2 ==1):
flag[k] = chr(ord(flag[k]) + k)
else:
flag[k+2] = flag[k]
flag[0] = 'f'
temp = ""
for i in range(len(flag)):
temp += flag[i]
print temp
flag:flag{Ho0k_w1th_Fun}
做题学习的姿势:
- IDA-python的使用:常用的api
get_bytes(address,count)从address处读取count个字节的内容
patch_bytes(address,buf),将adress地址处patch成buf的内容
一些IDA常用的快捷键:
- 跳转到特定地址 : G
- 查询交叉引用: X
- 查找字符串: alt + t
- 拍摄快照:ctrl+shift+w
- 重新定义变量数据类型 : y
- undefine一个 函数: u
- create 一个函数:p
ida反编译的一些技巧:
- 如果它函数反编译出来的变量很奇怪,可以将这个函数undefine掉然后再define,这样看的变量就会正常很多
- 如果反编译函数显示sp指针有问题,可以通过快捷键alt+k 在报错的地址修复sp指针
- 如果函数的结束地址正常,可以通过快捷键alt+p修改函数结束地址
- 可以变量转换数组,通过快捷键 y 将数据类型修改为 对应的类型的指针 例如 char *a1
- ida远程调试linux上的程序
先将ida文件夹下的linux_server64或者linux_server32拷到虚拟机去
然后给它执行权限,同时将要调试的文件和它放在同意文件夹,然后设置ida使用remote debugger 设置相关的路径,就可以开始调试了
od的常用命令:
F2:给程序下断点(ctrl+:重载exe程序)
F3:载入exe程序
F4:运行到鼠标指示位置
F7:单步进入到call中
F8:单步跳过call(ctrl+:自动逐行运行程序)
F9:运行exe程序(ctrl+:运行到ret处)(ALT+:快速返回程序领空)
Space:空格键,直接修改汇编代码
CTRL+F:命令模式
CTRL+G:跳转到某个地址
CTRL+B:查找二进制字符串
网友评论