Angr符号执行初步学习
angr符号执行框架,是我一直想要理解学习的,今天就来实际学习体验一下。
根据网上教的,应该是在GitHub - jakespringer/angr_ctf 网站上面下载对应的习题,编写对应的python进行练习。
前置要求:安装angr库,我已经满足,接下来就开始进行捣鼓。
angr的原理我也不太能解释清楚,所以直接开干吧。(默认python代码均引入angr库)
因此,使用 angr 一般分为如下步骤:
- 创建 Project,预设 state
- 创建位向量和符号变量,保存在内存/寄存器/文件或其他地方(简单的题目直接可以省略)
- 将 state 添加到 SM 中
- 运行,探索满足条件的路径
- 约束求解获取执行结果
1-angr-find
用这个作为入门吧。分析一下代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{int i; // [esp+1Ch] [ebp-1Ch]char s1[9]; // [esp+23h] [ebp-15h] BYREFunsigned int v6; // [esp+2Ch] [ebp-Ch]v6 = __readgsdword(0x14u);printf("Enter the password: ");__isoc99_scanf("%8s", s1);for ( i = 0; i <= 7; ++i )s1[i] = complex_function(s1[i], i);if ( !strcmp(s1, "JACEJGCS") )puts("Good Job.");elseputs("Try again.");return 0;
}
根据学习的知识,首先应该创建一个project。
p = angr.Project("./00",auto_load_libs='false')
为了提高效率,不自动加载依赖库。
然后应该创建一个state,就是作为一个镜像(Project
仅完成二进制文件加载,执行操作实际围绕 SimState
对象(程序状态)开展。 类似于做了一个镜像)
要创建状态,需借助 Project
对象的 factory
,它还可用于创建模拟管理器、基本块
state=p.factory.entry_state()
上述方式只是预设了程序开始分析时的状态,我们要分析程序就必须要让它到达下一个状态,这就需要
模拟管理器的帮助(简称 SM)
sm = p.factory.simgr(state)
可以通过 step() 方法来让处于 active 的 state 执行一个基本块,这种操作不会改变 state 本身(类比 docker)state只是一个镜像而已,进入SM才有执行的可能性:
可以使用 explorer 方法去执行某个状态,直到找到目标指令或者 active 中没有状态为止,它有如下参数:
- find:传入目标指令的地址或地址列表,或者一个用于判断的函数
- avoid:传入要避免的指令的地址或地址列表,或者一个用于判断的函数,用于减少路径
第一关还是比较简单的,下面就是脚本:
import angr
p = angr.Project("./00")
state=p.factory.entry_state()
sm = p.factory.simgr(state)
sm.explore(find=0x8048675)
if sm.found:print(sm.found[0].posix.dumps(0))
2-angr-avoid
这一关卡需要进行avoid。因为主函数太大了!为什么主函数大?一个函数“avoid me”在主函数被反复调用。只需要增加avoid参数即可。
import angr
p = angr.Project("./01")
state=p.factory.entry_state()
sm = p.factory.simgr(state)
sm.explore(find=0x80485B5,avoid=0x80485A8)
if sm.found:print(sm.found[0].posix.dumps(0))
3-angr-find-condition
这里引入新的概念:利用函数,传给find和avoid,什么意思呢?
函数的return表示“找到这个函数的条件”dump(1)表示标准输出。
这是一个具体实例:
def find(state):return b'Good Job.' in state.posix.dump(1)#标准输出
def avoid(state):return b'Try again.' in state.posix.dump(1)
程序在正确的时候会提示字符串,所以可以从这里入手。给explore传参只需要传递函数名即可
import angr
p = angr.Project("./02")
state=p.factory.entry_state()
sm = p.factory.simgr(state)
def find(state):return b'Good Job.' in state.posix.dumps(1)
def avoid(state):return b'Try again.' in state.posix.dumps(1)
sm.explore(find=find,avoid=avoid)
if sm.found:print(sm.found[0].posix.dumps(0))
4-angr_symbolic_registers
开始介绍怎么给寄存器传入对应的初始量了。这一题很有意思,输入做了拆分,通过ecx寄存器做中间人将值最终放在eax、ebx、edx寄存器。
所以我们需要求出三个未知量,自然需要做出调整,这里介绍angr里面的变量类型。我们想要从程序的某个地方开始执行,就需要用到blank_state(addr=0xxxx)。我们可以跳过拆分输入的环节,直接设置符号变量让他求解。
BVS设置为32位,因为一个Int占32位。
不要忘记再给寄存器传入我们的参数,按照源程序的顺序给即可。
import angr
import claripy
p = angr.Project("./03")
state=p.factory.blank_state(addr=0x8048980)
x = claripy.BVS("x",32)
y = claripy.BVS("y",32)
z = claripy.BVS("z",32)
state.regs.eax=x
state.regs.ebx=y
state.regs.edx=z
sm = p.factory.simgr(state)
def find(state):return b'Good Job.' in state.posix.dumps(1)
def avoid(state):return b'Try again.' in state.posix.dumps(1)
sm.explore(find=find,avoid=avoid)
if sm.found:x=sm.found[0].solver.eval(x)y=sm.found[0].solver.eval(y)z=sm.found[0].solver.eval(z)print(f"{hex(x)} {hex(y)} {hex(z)}")
说实话,跟着教程走还是比较好理解的,记住关键的函数名字即可。
5-angr_symbolic_stack
这个题目开始模拟栈。
这里的scanf的参数又直接压到了栈上。
我们只需要关心求解的未知数在栈上的布局即可,其他的栈内容无需操心,也不会影响大局。
还需要知道的是,blank_state会初始化esp,但是不会初始化ebp,我们可以用esp给ebp赋值,进而初始化栈。
根据ida的栈布局,确定esp相对位置。刚开始肯定是要esp刚好指向两个未知变量栈的下面。这样子push完之后也恰好满足原函数的栈布局。
import angr
import claripy
p = angr.Project("./04")
state=p.factory.blank_state(addr=0x08048697)
x = claripy.BVS("x",32)
y = claripy.BVS("y",32)
state.regs.ebp=state.regs.esp
state.regs.esp -= 8#根据ida确认
state.stack_push(x)
state.stack_push(y)
sm = p.factory.simgr(state)
def find(state):return b'Good Job.' in state.posix.dumps(1)
def avoid(state):return b'Try again.' in state.posix.dumps(1)
sm.explore(find=find,avoid=avoid)
if sm.found:x=sm.found[0].solver.eval(x)y=sm.found[0].solver.eval(y)print(f"{x} {y} ")
6-angr_symbolic_memroy
操作内存。这个程序直接使用memset函数在bss段开辟了一些地方存储输入的数据,这就需要控制内存
读写内存:state.memory.store/load。只需要往对应内存地带写入数据即可。
说明:可以利用store配合寄存器寻址,如state.regs.ebp-0x1c,也可以识别得到。
还需要注意的是,这里的是字符串,因此需要用cast_to强制转换,并需用 decode()
转换为可读字符串
import angr
import claripy
p = angr.Project("./05")
state=p.factory.blank_state(addr=0x08048601)
x = claripy.BVS("x",64)#这里需要改为64位,因为是8个字符
y = claripy.BVS("y",64)
z = claripy.BVS("z",64)
a = claripy.BVS("a",64)
state.memory.store(0xA1BA1C0,x)
state.memory.store(0xA1BA1C8,y)
state.memory.store(0xA1BA1D0,z)
state.memory.store(0xA1BA1D8,a)
sm = p.factory.simgr(state)
def find(state):return b'Good Job.' in state.posix.dumps(1)
def avoid(state):return b'Try again.' in state.posix.dumps(1)
sm.explore(find=find,avoid=avoid)
if sm.found:x=sm.found[0].solver.eval(x,cast_to=bytes).decode()y=sm.found[0].solver.eval(y,cast_to=bytes).decode()z=sm.found[0].solver.eval(z,cast_to=bytes).decode()a=sm.found[0].solver.eval(a,cast_to=bytes).decode()print(f"{x} {y} {z} {a}")