Angr_1

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函数中存在循环,存在路径爆炸问题

image-20220225155200364

看到下面的if判断里的函数

image-20220225155727402

这里会爆炸,所以需要直接约束求解

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)
#符号化内存,直接把符号写入内存,避免scanf抽风

simulation = p.factory.simgr(init_state).explore(find = check_addr)
#这里注意,运行到调用check函数的状态

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)
#使用 state.memory 的 .load(addr, size)接口读出buffer处的内存数据

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)
#使用 state.memory 的 .load(addr, size)接口读出buffer处的内存数据

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.proceduresangr.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'))
#ZJOIPFTRNZOXIMLEWUFAOUBLOGLQCCGK

设置hook的地方是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
check_addr = 0x80486B3
#这里就是 call 检查函数的地址
jmp_len = 5
#通过16进制可以看到,这哥call指令占据了5个字节

@p.hook(check_addr, length=jmp_len)
#hook的形式,别忘了前面的@
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))
#模拟一个函数就是把它视作一个黑盒,能成功模拟输入相对应的输出即可,所以我们需要处理check函数的返回值
#这里就是对eax的一个赋值

题目: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):
#定义一个继承angr.SimProcedure的类,以利用Angr的SimProcedures,这是关键

def run(self, to_check, length):
#self之后的任何参数都将被视为要替换的函数的参数,和原函数的参数意义保持一致

in_addr = to_check
in_lenth = length
#以上是两个参数,长度和字符串的首地址

in_str = self.state.memory.load(in_addr, in_lenth)
#在SimProcedure中查找系统状态,从该状态的内存中提取出数据

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的形式,名字和定义的类,参数是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)
#使用 state.memory 的 .store(addr, val) 接口将符号位向量写入两个字符串,注意不是load;模拟输入,把两个符号写进去

self.state.globals['solutions'] = (in_1, in_2)
#设置全局变量,用集合的形式

name = '__isoc99_scanf'
p.hook_symbol(name, HookScanf())#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:
solu_init = simulation.found[0]
solu = solu_init.globals['solutions']
print(solu_init.solver.eval(solu[0]))
print(solu_init.solver.eval(solu[1]))
#这里涉及到两个输入以上的,就用solver,一个输入就用posix.dumps(0)

题目: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_optslib_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#通过ida可以看到导出函数的偏移

init_state = p.factory.call_state(validate_func_addr, buf_ptr, BVV(8, 32))#最后是一个长度
#使用.call_state创建 state 对象,构造一个已经准备好执行validate函数的状态,所以我们需要设定好需要传入的参数

根据程序中函数的原型:

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)
#就直接拓展simgr的参数就行,简单快捷,会出现紫色的log信息,没事

if simulation.found:
print(simulation.found[0].posix.dumps(0))
b'OQSUWYACEGIKMOQSUWYACEGIKMOQSUWY'

这里如果使用约束求解的话,输入在栈中,并没有一个固定的地址,不好设置符号。