Angr学习,解决路径爆炸问题和一些hook方法:
题目:08_angr_constraints
路径爆炸
1.就是求解过程中,产生了太多了路径,导致求解过程异常缓慢,这通常由带有循环的判断引起的
2.比如说一个32次的for循环进行单字节比较,每一次比较都会产生两个路径,这就是2的32次方的路径,一般电脑无法达到这么大的算力。
3.解决:
- 更换电脑
- hook技术
- 用约束条件取代这个判断函数
约束求解
angr中提供了可以用加入一个约束条件到一个state中的方法(state.solver.add
);类似于Z3-Solver的solver.add()。
实际是通过 .add
对 state 对象添加约束,并使用 .eval
接口求解,得到符号变量的可行解。
ida分析可得,main函数中存在循环,存在路径爆炸问题
看到下面的if判断里的函数
这里会爆炸,所以需要直接约束求解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| from angr import * from claripy import *
p = Project('./08_angr_constraints', auto_load_libs = False)
start_addr = 0x8048625 init_state = p.factory.blank_state(addr = start_addr)
check_addr = 0x8048565 in0_addr = 0x804A050
in0len = 16 in0 = BVS('in0', 16*8)
init_state.memory.store(in0_addr, in0)
simulation = p.factory.simgr(init_state).explore(find = check_addr)
if simulation.found: solution_state = simulation.found[0]
para_addr = in0_addr para_size = 16 para_bitvector = solution_state.memory.load(para_addr, para_size) para_cmp_value = 'AUPDNNPROEZRJWKB' solution_state.solver.add(para_bitvector == para_cmp_value)
solution = solution_state.solver.eval(in0, cast_to = bytes) print(solution) b'LGCRCDGJHYUNGUJB'
|
关键:
1 2 3 4 5 6 7 8
| para_addr = in0_addr para_size = 16 para_bitvector = solution_state.memory.load(para_addr, para_size)
para_cmp_value = 'AUPDNNPROEZRJWKB' solution_state.solver.add(para_bitvector == para_cmp_value)
|
这里就是把数据读出来,然后通过add添加的约束条件进行比较,最后得到结果,避免程序内部的路劲爆炸
题目:09_angr_hooks
HOOK技术:通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。
这里就是用自己设计的函数去取代被hook的函数
ida分析,这里取代那个循环判断函数
Hook地址
1.hook engine
来处理hook的情况。默认情况下,angr 会使用 SimProcedures
中的符号摘要替换库函数,即设置 Hooking,这些 python 函数摘要高效地模拟库函数对状态的影响。可以通过 angr.procedures
或 angr.SimProcedures
查看列表,具体我没看。
2.SimProcedure
其实就是 Hook 机制,可以通过 proj.hook(addr,hook)
设置
3.proj.hook(addr)
作为函数装饰器,可以编写自己的 hook 函数。还可以通过 proj.hook_symbol(name,hook)
hook 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import sys from claripy import *
p = Project('./09_angr_hooks', auto_load_libs = False) init_state = p.factory.entry_state()
check_addr = 0x80486B3 jmp_len = 5
@p.hook(check_addr, length=jmp_len) def skip_checkfunc(state):
in0_addr = 0x804A054 size = 16
in0 = state.memory.load(in0_addr, size) cmp_str = 'XKSPZSJKJYQCQXZV'
register_size = 32 state.regs.eax = If(in0 == cmp_str, BVV(1, register_size), BVV(0, register_size))
def issu(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False
def isfa(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False
simulation = p.factory.simgr(init_state).explore(find=issu, avoid=isfa)
if simulation.found: solution_init = simulation.found[0] print(solution_init.posix.dumps(0).decode('utf-8'))
|
设置hook的地方是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| check_addr = 0x80486B3
jmp_len = 5,
@p.hook(check_addr, length=jmp_len)
def skip_checkfunc(state):
in0_addr = 0x804A054 size = 16
in0 = state.memory.load(in0_addr, size) cmp_str = 'XKSPZSJKJYQCQXZV'
register_size = 32 state.regs.eax = If(in0 == cmp_str, BVV(1, register_size), BVV(0, register_size))
|
题目:10_angr_simprocedures
利用函数名进行hook,而不是复杂的利用函数的调用地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| from angr import * import claripy import sys
p = Project("./10_angr_simprocedures", auto_load_libs = False)
init_state = p.factory.entry_state()
class HookCheckFunc(SimProcedure):
def run(self, to_check, length): in_addr = to_check in_lenth = length in_str = self.state.memory.load(in_addr, in_lenth)
check_str = 'ORSDDWXHZURJRBDH'
return claripy.If(in_str == check_str ,claripy.BVV(1, 32), claripy.BVV(0, 32))
hook_func_name = 'check_equals_ORSDDWXHZURJRBDH' p.hook_symbol(hook_func_name, HookCheckFunc(init_state))
def issu(state): if b'Good Job.\n' in state.posix.dumps(1): return True else: return False def isfa(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False
simulation = p.factory.simgr(init_state).explore(find=issu, avoid=isfa)
if simulation.found: print(simulation.found[0].posix.dumps(0)) b'MSWKNJNAVTTOZMRY'
|
HOOK函数名
这次的核心是根据函数的名字进行hook,当函数被多次调用的时候,通过名字进行hook的效果更好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class HookCheckFunc(SimProcedure):
def run(self, to_check, length): in_addr = to_check in_lenth = length in_str = self.state.memory.load(in_addr, in_lenth)
check_str = 'ORSDDWXHZURJRBDH'
return claripy.If(in_str == check_str ,claripy.BVV(1, 32), claripy.BVV(0, 32)) hook_func_name = 'check_equals_ORSDDWXHZURJRBDH' p.hook_symbol(hook_func_name, HookCheckFunc(init_state))
|
Hook上check_equals函数, angr会自动查找与该函数符号关联的地址.
题目:11_angr_sim_scanf
1.思路同上,找到函数名,定义类,利用hook_symbol()
进行hook。
2.为什么要hook这个:scanf对复杂字符串的处理对angr不友好,所以要学会hook她
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| from angr import * from claripy import * import sys
p = Project("./11_angr_sim_scanf", auto_load_libs = False)
init_state = p.factory.entry_state()
class HookScanf(SimProcedure): def run(self, fmt, in1, in2): in_1 = BVS('in_1', 32) in_2 = BVS('in_2', 32)
in1_addr = in1 in2_addr = in2
self.state.memory.store(in1_addr, in_1, endness = p.arch.memory_endness) self.state.memory.store(in2_addr, in_2, endness = p.arch.memory_endness) self.state.globals['solutions'] = (in_1, in_2)
name = '__isoc99_scanf' p.hook_symbol(name, HookScanf())
def issu(state): if b'Good Job.\n' in state.posix.dumps(1): return True else: return False def isfa(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False
simulation = p.factory.simgr(init_state).explore(find=issu, avoid=isfa)
if simulation.found: solu_init = simulation.found[0] solu = solu_init.globals['solutions'] print(solu_init.solver.eval(solu[0])) print(solu_init.solver.eval(solu[1]))
|
题目:13_angr_static_binary
Hook静态库函数
1.学习如何使用angr解出静态编译的题目,学习如何Hook静态库函数
2.一般来说,angr会用SimProcedure来代替标准库函数,但是静态编译的文件没法替换,angr提供了一些hook
1 2 3 4 5 6 7 8 9 10 11
| angr.SIM_PROCEDURES['libc']['malloc'] angr.SIM_PROCEDURES['libc']['fopen'] angr.SIM_PROCEDURES['libc']['fclose'] angr.SIM_PROCEDURES['libc']['fwrite'] angr.SIM_PROCEDURES['libc']['getchar'] angr.SIM_PROCEDURES['libc']['strncmp'] angr.SIM_PROCEDURES['libc']['strcmp'] angr.SIM_PROCEDURES['libc']['scanf'] angr.SIM_PROCEDURES['libc']['printf'] angr.SIM_PROCEDURES['libc']['puts'] angr.SIM_PROCEDURES['libc']['exit']
|
我们只需要手动找到程序中用到静态函数的地址,将其利用simprocedure提供的函数Hook掉即可
首先来看如果正常写会怎样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from angr import * from claripy import * import sys
p = Project('./13_angr_static_binary', auto_load_libs = False)
init_state = p.factory.entry_state()
def issu(state): if b'Good' in state.posix.dumps(1): return True else: return False def isfa(state): if b'Try' in state.posix.dumps(1): return True else: return False
simulation = p.factory.simgr(init_state, veritesting=True).explore(find=issu, avoid=isfa)
if simulation.found: print(simulation.found[0].posix.dumps(0))
|
结果就是跑了好久也没有得到答案,因为库函数没有被angr自动hook,导致跑着跑着就到了库函数里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| from angr import * from claripy import * import sys
p = Project('./13_angr_static_binary', auto_load_libs = False)
init_state = p.factory.entry_state()
p.hook(0x804ED40, SIM_PROCEDURES['libc']['printf']()) p.hook(0x804ED80, SIM_PROCEDURES['libc']['scanf']()) p.hook(0x804f350, SIM_PROCEDURES['libc']['puts']()) p.hook(0x8048D10, SIM_PROCEDURES['glibc']['__libc_start_main']())
def issu(state): if b'Good' in state.posix.dumps(1): return True else: return False def isfa(state): if b'Try' in state.posix.dumps(1): return True else: return False
simulation = p.factory.simgr(init_state, veritesting=True).explore(find=issu, avoid=isfa)
if simulation.found: print(simulation.found[0].posix.dumps(0)) b'PNMXNMUD'
|
需要注意的就是不要忘了函数__libc_start_main
复习一下linux里的C程序如何启动的:
- execve开始执行
- execve加载二进制程序,加载
.interp
指定的动态加载器
- 动态加载器把需要的so都加载起来,特别是把 libc.so.6 加载
- 调用到libc.so.6里的
__libc_start_main
函数,开始真正的执行程序
- 初始化之后开始main函数的执行
题目:14_angr_shared_library
学习如何使用angr求解函数是外部导入在动态库(.so)里的题目
动态链接
1.把程序按照模块拆分成相对独立的部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有的程序模块都连接成一个单独的可执行文件。
2.ELF动态链接文件被称为动态共享对象(DSO,Dynamic Shared Object),简称共享对象,它们一般都是.so为扩展名的文件。
3.共享库中的所有地址都是base + offset,其中offset是它们在文件中的偏移地址,和ret2libc一样。
分析题目,看到引用了一个在so文件中的函数,ida中看不到原码,问题在于如何让angr处理这个外部函数
Hook动态库函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from angr import * from claripy import * import sys
base = 0x8048000
p = Project("./lib14_angr_shared_library.so", load_options = { 'main_opts':{'custom_base_addr':base} })
buf_ptr = BVV(0x3000000, 32)
validate_func_addr = base+0x6d7 init_state = p.factory.call_state(validate_func_addr, buf_ptr, BVV(8, 32))
pawd = BVS('pawd', 64) init_state.memory.store(buf_ptr, pawd)
simulation = p.factory.simgr(init_state)
su_addr = base+0x783 simulation.explore(find=su_addr)
if simulation.found: solu_init = simulation.found[0] solu_init.add_constraints(solu_init.regs.eax != 0) print(solu_init.solver.eval(pawd, cast_to=bytes))
|
pre-binary 选项
使用 main_opts
和 lib_opts
参数进行设置。
backend
– 指定 backend
base_addr
– 指定基址
entry_point
– 指定入口点
arch
– 指定架构
1 2 3
| base = 0x8048000
p = Project("./lib14_angr_shared_library.so", load_options = { 'main_opts':{'custom_base_addr':base} })
|
程序本身没有开PIE保护,所以地址可以得到base,
1 2 3 4
| validate_func_addr = base+0x6d7
init_state = p.factory.call_state(validate_func_addr, buf_ptr, BVV(8, 32))
|
根据程序中函数的原型:
1
| _BOOL4 __cdecl validate(char *s1, int a2)
|
通过 BVV(value,size)
和 BVS( name, size)
接口创建位向量:
先创建一个缓冲区buffer作为参数char *s1
,因为设定的缓冲区地址在0x3000000,又因为32位程序里int类型为4字节,即32比特,故得:
1
| buf_ptr = BVV(0x3000000, 32)
|
用BVS创建一个符号位向量,作为符号化的传入字符串传入我们之前设定好的缓冲区地址中
1 2
| pawd = BVS('pawd', 64) init_state.memory.store(buf_ptr, pawd)
|
最后,让函数执行到返回地址,然后添加约束条件
1 2 3 4 5 6 7 8 9 10
| simulation = p.factory.simgr(init_state)
su_addr = base+0x783 simulation.explore(find=su_addr)
if simulation.found: solu_init = simulation.found[0] solu_init.add_constraints(solu_init.regs.eax != 0) print(solu_init.solver.eval(pawd, cast_to=bytes))
|
题目:12_angr_veritesting
使用Veritesting
的技术解决路径爆炸问题
Veritesting
- 动态符号执行(DSE):生成路径公式,摘要路径汇合点上两条分支的情况
- 静态符号执行(SSE):生成语句公式,为两条分支fork两条独立的执行路径
Veritesting结合了静态符合执行与动态符号执行,减少了路径爆炸的影响。
在angr里我们只要在构造模拟管理器时,启用Veritesting了就行
1
| p.factory.simgr(init_state, veritesting=True)
|
本题的判断在main函数中,没有独立的函数,没有办法直接hookcheck函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from angr import * from claripy import * import sys
p = Project("./12_angr_veritesting", auto_load_libs = False)
init_state = p.factory.entry_state()
def issu(state): if b'Good' in state.posix.dumps(1): return True else: return False def isfa(state): if b'Try' in state.posix.dumps(1): return True else: return False
simulation = p.factory.simgr(init_state, veritesting=True).explore(find=issu, avoid=isfa)
if simulation.found: print(simulation.found[0].posix.dumps(0)) b'OQSUWYACEGIKMOQSUWYACEGIKMOQSUWY'
|
这里如果使用约束求解的话,输入在栈中,并没有一个固定的地址,不好设置符号。