0%

angr学习

早就听闻了angr,之前看wp也有大佬使用了angr,上一次打开angrctf一头雾水,还要装环境啥的,就先搁置了。现在趁着军训开始angr的学习。

tnnd两天都没配置好一个虚拟环境,还是在朋友的帮助下搞了个docker,woc真难绷,或许以后有能力了可以搞一个虚拟机,包含所有的逆向需要的环境hhh。一天后来考古,那个docker用起来着实别扭,不知道出了什么问题,不能将主机的文件拷贝到容器中,还有就是用了docker start 容器id也启动不了容器,只好另寻他路,本来想放弃的,可是一想到被这环境折磨三四天了,哎。终于,在wsl的虚拟环境成功搭建!!!

记录一下启动步骤

1
2
3
4
5
##在angr_enviroment打开环境
$ source myenv/bin/activate

##关闭环境
$ deactivate

(15条消息) 在wsl上安装angr框架_wsl2下安装angr workon_ljahum的博客-CSDN博客

Angr介绍

看一看官方文档的解释

angr is a multi-architecture binary analysis toolkit, with the capability to perform dynamic symbolic execution (like Mayhem, KLEE, etc.) and various static analyses on binaries.

angr是一个多架构二进制分析工具包,具有执行动态符号执行(例如Mayhem,KLEE等)和各种静态分析的能力。

什么叫符号执行呢?

符号执行

符号执行是一种静态分析技术,是一种计算机科学领域的程序分析技术,通过采用抽象的符号代替精确值作为程序输入变量,得出每个路径抽象的输出结果。 这一技术在硬件、底层程序测试中有一定的应用,能够有效的发现程序中的漏洞。符号执行就是给程序传递一个符号而不是具体的值,让符号伴随程序运行,当遇到分支时angr会保留所有分支以及进入分支的约束条件,最后根据约束条件对我们传递的符号约束求解。这听着有点像全自动z3。

概念有点抽象,不如直接做题。

Angr_CTF

参考链接:angr符号执行练习 00_angr_find_哔哩哔哩_bilibili

angr从入门到精通

angr核心概念即模块解读

使用步骤

  • 创建project
  • 设置state
  • 新建符号量 : BVS (bitvector symbolic ) 或 BVV (bitvector value)
  • 把符号量设置到内存或者其他地方
  • 设置 Simulation Managers , 进行路径探索的对象
  • 运行,探索满足路径需要的值
  • 约束求解,获取执行结果

00_angr_find

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
import angr
import sys
def angr00():
##创建project
path_to_binary="./home/mzyy/AngerCTF/00_angr_find/00_angr_find"
project=angr.Project(path_to_binary)

##设置state state代表实例镜像,模拟执行某个时刻的状态。
initial_state=project.factory.entry_state()

##设置SM,project只是程序最开始的一个状态,我们实际对simulation对象进行操作,他模拟程序某时刻的状态
simulation=project.factory.simgr(initial_state)

##运行,探索满足路径需要的值
print_good_address=0x8048678 ##通过ida找到输出good的地址
simulation.explore(find=print_good_address)
##通过explore(),找到能够到达某个地址的状态,同时丢弃不能达到这个地址的状态
##当启用find参数启动.explore()方法时,程序会一直执行,直到发现一个和find参数指定的条件匹配的状态
##find参数的内容可以是想要执行到的某个地址。。。

##获取执行结果
if simulation.found:
solution_state=simulation.found[0] ##
print(solution_state.posix.dumps(0))

if __name__ == "__main__":
angr00()

下面是用ipython写的截的图,是跟着B站的一位up主来的。

01_angr_avoid

反编译main函数时说函数过大无法反编译(可以通过修改ida的设置文件,提高最多分析长度来解决此问题)。这一题实际上也不需要,根据函数名,我们找到avoid_me的地址加到参数中即可。

查看avoid_me的交叉引用,发现巨多。我们可以看到全是main函数在引用,这就是main函数巨大的原因吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
import angr
def angr01():
path="./angrctf/01_angr_avoid"
p=angr.Project(path)
init_state=p.factory.entry_state()
sm=p.factory.simgr(init_state)
sm.explore(find=0x080485FC,avoid=0x080485BF)
if sm.found:
solution_state=sm.found[0]
print(solution_state.posix.dumps(0))

if __name__ == "__main__":
angr01()

这一题就是想让我们知道,避开一些错误的路径可以提高效率,我觉得将上面的avoid删掉一样可以达到目的,不过时间可能会很夸张。哎呀被打脸了,跑了十分钟左右跑出来一个killed。

好吧电脑内存不足,进程被系统杀死了,那如果内存足够大还是能跑出来的吧。提出问题和回答问题的人都好耐心好有礼貌^_^

02_angr_find_condition

很显然这次不能通过输出good job的地址来解题了,因为这一题故意设置了很多跳转,使用了多次put good job和多次put try again。根据作者的注释,在一些情况下我们可能不知道要达到的指令的地址,或者没有特定的指令目标。在这种情况下,我们只要知道一种状态,例如在某状态下二进制文件打印出“Good Job”。angr提供了一种功能强大的方法:允许搜索满足任意条件的状态。具体来讲,我们可以使用一个函数来定义一个状态,该函数接收一个state作为参数,并返回True或false表示该状态是否满足要求。当程序找到一个符合条件的状态时,他就会停止搜索。具体看下面这函数,它检查状态的标准输出是否包含字符串 “Good Job.”。

1
2
3
4
5
def is_successful_state(state):
if b"Good Job." in state.posix.dumps(1):
return True
else:
return False

find的用法

除了地址之外,find 参数还可以是一个函数,该函数接受一个路径(path)作为参数,并返回一个布尔值。当该函数返回 True 时,路径组将停止探索。例如,以下代码使用 find 方法搜索二进制文件中所有包含 win_function 函数的路径,并打印出对应的输入数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
import angr

proj = angr.Project('/path/to/binary')
state = proj.factory.entry_state()
pg = proj.factory.path_group(state)

def win_function(path):
return "Congratulations!" in path.state.posix.dumps(1)

pg.explore(find=win_function)

for found in pg.found:
print(found.state.posix.dumps(0))

在这个示例中,win_function 是一个用于检查路径是否包含特定输出的函数。在探索过程中,每当发现一个路径包含 win_function 函数并生成相应的结论时,路径组将停止探索,并输出相应的输入数据。

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
import angr
import sys
def main(argv):
path=argv[1]
p=angr.Project(path)
init_state=p.factory.entry_state()
sm=p.factory.simgr(init_state)
def isGood(sm):##sm是我们实际操作的状态,也可以用state代替
if b"Good Job." in sm.posix.dumps(1):##dumps(1)是标准输出
return True
else:
return False
def isBad(sm):
if b"Try again." in sm.posix.dumps(1):
return True
else:
return False
sm.explore(find=isGood,avoid=isBad)
if sm.found:
solution_state=sm.found[0]
print(solution_state.posix.dumps(0))

if __name__ =='__main__':
main(sys.argv)


03_angr_symbolic_registers

根据当时作者的说明,Angr目前不支持使用scanf一次读取多个变量(例如:# scanf(“%u %u))。您需要告诉仿真引擎在调用scanf后开始程序,并手动将符号注入寄存器。据说现在可以了,但学一下总没有坏处,能从中体会到angr的灵活。

首先呢确定进入的地址,就在scanf之后,0x080488C7

进去之后呢因为我们跳过了scanf所以把它的参数放到该有的位置,也就是寄存器eax,ebx,edx中

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
import angr 
import sys
import claripy

def main(argv):
path=argv[1]
p=angr.Project(path)
start_address=0x080488C7
##注意这里和之前不同,之前是factory.entry_state()
init_state=p.factory.blank_state(addr=start_address)

##创建三个向量,和z3差不多,第一个pass0是符号变量名称,第二个pass0是该位宽变量的字符串标识符,后面的32指大小
pass0=claripy.BVS('pass0',32)
pass1=claripy.BVS('pass1',32)
pass2=claripy.BVS('pass2',32)

##将符号放进寄存器
init_state.regs.eax=pass0
init_state.regs.ebx=pass1
init_state.regs.edx=pass2

sm=p.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

if sm.found:
soulution_state=sm.found[0]
##注意这里不能使用print(solution_state.posix.dumps(0)),至于为什么呢,因为我们跳过了输入?
##约束求解,pass012代表的是状态,password012则是达到对应状态的输入
password0=soulution_state.solver.eval(pass0)
password1=soulution_state.solver.eval(pass1)
password2=soulution_state.solver.eval(pass2)
##{:x},16进制输出
print("{:x} {:x} {:x}".format(password0,password1,password2))
else:
print("no found")


if __name__=='__main__':
main(sys.argv)

04_angr_symbolic_stack

做这一题之前需要回顾一下栈帧,在看《逆向工程核心原理》的时候了解过。

栈帧 函数调用

  1. 通过push指令传参
  2. 将call指令的下一条指令的地址压入栈中作为返回地址
  3. push ebp 保存ebp的原始值 ebp稍后会被用作栈帧指针
  4. mov ebp,esp 直到函数返回前ebp中的值都是esp的初始值 我们可以通过ebp安全的访问栈中的函数参数与局部变量
  5. .sub esp,x这里的x依局部变量而变,如果局部变量为两个long类型(4字节)则此处的x应该为8
  6. 借助mov指令和ebp创建局部变量
  7. 删除栈帧 mov esp,ebp(恢复栈指针),pop ebp(恢复ebp)
  8. retn
  9. 返回原来位置后,add esp,x测出的x根据步骤0穿入的参数而定,这一步的目的是将参数从栈中清理

上一个题目的scanf是单独使用的,就是将我们的输入放入栈中然后再传到寄存器,所以我们只要跳到scanf执行完之后将输入放入寄存器即可。而这一题,是直接将我们的输入当作临时变量使用,即通过栈指针访问,那么我们跳过scanf之后,函数预留的那两个临时变量的位置是空的,所以我们要对栈进行操作,将函数正确的放入栈中。看上面的图,调用scanf的时候利用寄存器从右往左传参,var_10和var_c分别是第二个参数和第一个参数,所以我们将创建的符号变量依次放入ebp+var_C和ebp+var_10处即可。

从ida我们可以看出,这个函数第二行sub esp, 18h,实际上堆栈空间是0x18,但是在实际的做题中我们只是用到了那两个参数,我们就恢复了0x8.

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
import angr
import sys
import claripy
def main(argv):
path=argv[1]
project=angr.Project(path)
start_address=0x080486AE
init_state=project.factory.blank_state(addr=start_address)

pass0=claripy.BVS('pass0',32)
pass1=claripy.BVS('pass1',32)

##由于我们跳转到了函数的中间,所以跳过了栈初始化的过程,我们要对其进行必要的还原
init_state.regs.ebp=init_state.regs.esp
padding_size=0x8
init_state.regs.esp-=padding_size

##将我们的输入放入正确的位置
init_state.stack_push(pass0) ## 先压入参数1
init_state.stack_push(pass1)

sm=project.factory.simulation_manager(init_state)

def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

if sm.found:
solution_state=sm.found[0]
password0=solution_state.solver.eval(pass0)
password1=solution_state.solver.eval(pass1)
print("{} {} ".format(password0,password1))
else:
print("no found")

if __name__=='__main__':
main(sys.argv)

疑惑

eval

  • solver.eval(expression) 将会解出一个可行解
  • solver.eval_one(expression)将会给出一个表达式的可行解,若有多个可行解,则抛出异常。
  • solver.eval_upto(expression, n)将会给出最多n个可行解,如果不足n个就给出所有的可行解。
  • solver.eval_exact(expression, n)将会给出n个可行解,如果解的个数不等于n个,将会抛出异常。
  • solver.min(expression)将会给出最小可行解
  • solver.max(expression)将会给出最大可行解

05_angr_symbolic_memory

修改了寄存器,修改了栈,这一次开始修改内存了。

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
import angr
import sys
import claripy
def main(argv):
path=argv[1]
project=angr.Project(path)
start_address=0x08048618
init_state=project.factory.blank_state(addr=start_address)
##%8s 即8个字符
passwd0=claripy.BVS('passwd0',8*8)
passwd1=claripy.BVS('passwd1',8*8)
passwd2=claripy.BVS('passwd2',8*8)
passwd3=claripy.BVS('passwd3',8*8)

passwd0_address=0x0AB232C0
passwd1_address=0x0AB232C8
passwd2_address=0x0AB232D0
passwd3_address=0x0AB232D8
##本题的不同,操作内存,store(address,value),value包含了大小
init_state.memory.store(passwd0_address,passwd0)
init_state.memory.store(passwd1_address,passwd1)
init_state.memory.store(passwd2_address,passwd2)
init_state.memory.store(passwd3_address,passwd3)

sm=project.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

##cast_to=byte 将passwd转化为字节序列,decode方法将其解码为字符串
if sm.found:
solution_state=sm.found[0]
password0=solution_state.solver.eval(passwd0,cast_to=bytes).decode()
password1=solution_state.solver.eval(passwd1,cast_to=bytes).decode()
password2=solution_state.solver.eval(passwd2,cast_to=bytes).decode()
password3=solution_state.solver.eval(passwd3,cast_to=bytes).decode()

print("{} {} {} {} ".format(password0,password1,password2,password3))
else:
print("no found")


if __name__=='__main__':
main(sys.argv)

06_angr_symbolic_dynamic_memory

新知识点:符号化动态内存。

作者的解释文档这样写着:我们可以不告诉二进制程序将数据写入使用malloc()分配的内存地址,而是直接伪造一个未使用的内存块的地址,并覆盖指向数据的指针。

思路是这样的:malloc函数返回值是一个地址,储存到了buffer里,我们伪造一个地址放到buffer即可,然后在我们伪造的地址处填入符号。

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 angr 
import sys
import claripy
def main(argv):
path=argv[1]
start_address=0x080486AF
project=angr.Project(path)
init_state=project.factory.blank_state(addr=start_address)

passwd_size=8*8
passwd0=claripy.BVS('passwd0',passwd_size)
passwd1=claripy.BVS('passwd1',passwd_size)

fake_heap_address0=0x0804A144
fake_heap_address1=0x0804A154
real_address0=0x0A2DEF74
real_address1=0x0A2DEF7C

init_state.memory.store(real_address0,fake_heap_address0,endness=project.arch.memory_endness)
init_state.memory.store(real_address1,fake_heap_address1,endness=project.arch.memory_endness)

init_state.memory.store(fake_heap_address0,passwd0)
init_state.memory.store(fake_heap_address1,passwd1)

sm=project.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

if sm.found:
solution_state=sm.found[0]
password0=solution_state.solver.eval(passwd0,cast_to=bytes).decode()
password1=solution_state.solver.eval(passwd1,cast_to=bytes).decode()
print("{} {} ".format(password0,password1))
else:
print("no found")

if __name__=='__main__':
main(sys.argv)
1
2
3
init_state.memory.store(real_address0,fake_heap_address0,endness=project.arch.memory_endness)
(原地址,我们指定的假地址,端序),原地址指的是存放malloc返回值的变量的地址,此处endness是和本项目相同

endness可选项

1
2
3
LE – 小端序(little endian, least significant byte is stored at lowest address)
BE – 大端序(big endian, most significant byte is stored at lowest address)
ME – 中间序(Middle-endian. Yep.)

07_angr_symbolic_file

新知识点:符号化文件内

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
import angr
import sys
import claripy

def main(argv):
path=argv[1]
project=angr.Project(path)
start_address=0x080488BC
init_state=project.factory.blank_state(addr=start_address)

filename='FOQVSBZB.txt'
filesize=0x40
passwd0=claripy.BVS('passwd0',filesize*8)
passwdfile=angr.storage.SimFile(filename,content=passwd0,size=filesize)

init_state.fs.insert(filename,passwdfile)

sm=project.factory.simgr(init_state)

def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

if sm.found:
solution_state=sm.found[0]
password0=solution_state.solver.eval(passwd0,cast_to=bytes).decode()
print(password0)
else:
print("no found")


if __name__=='__main__':
main(sys.argv)

新操作,创建虚拟文件并将其放入仿真文件系统

1
2
3
4
5
6
7
8
9
10
##创建同名的虚拟文件,必须要和题目中给出的文件名一致
filename='FOQVSBZB.txt'
filesize_byte=0x40
##符号化文件内容
passwd0=claripy.BVS('passwd0',filesize*8)
##创建符号化文件,参数是(文件名,内容,大小byte)
passwdfile=angr.storage.SimFile(filename,content=passwd0,size=filesize)

##将文件放入仿真文件系统,这一步我的理解就是替换,访问文件时直接访问我们创建的同名文件
init_state.fs.insert(filename,passwdfile)

08_angr_constraints

开始之前先了解一下路径爆炸。因为这次的新知识点就是:通过添加约束解决路径爆炸问题。

路径爆炸

路径爆炸(Path explosion)指的是在有限状态机或接收器的设计过程中,状态数或路径数呈指数增长的现象。当程序经历的所有可能路径数量超过计算机的处理能力时,就会出现路径爆炸的问题。

路径爆炸是软件测试和验证中一个重要的问题。在对程序进行测试或验证时,需要覆盖程序的所有可能路径,以确保程序的正确性和安全性。但是,当程序中存在复杂的控制流结构时,这个任务就变得非常艰巨。

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
import angr
import sys
import claripy
def main(argv):
path=argv[1]
project=angr.Project(path)
start_address=0x0804863C
buffer_address=0x0804A040
ckeckfun_address=0x0804857C

init_state=project.factory.blank_state(addr=start_address)

passwd_len=16
passwd0=claripy.BVS('passwd0',passwd_len*8)
init_state.memory.store(buffer_address,passwd0)

simulation=project.factory.simulation_manager(init_state)
simulation.explore(find=ckeckfun_address)

if simulation.found:
solution_state=simulation.found[0]
parameter_address=buffer_address
parameter_size_bytes=16

##.load读出buffer处的内存数据
parameter_bitvector=solution_state.memory.load(parameter_address,parameter_size_bytes)
compare_valve='OSIWHBXIFOQVSBZB'
solution_state.solver.add(parameter_bitvector==compare_valve)
solution0=solution_state.solver.eval(passwd0,cast_to=bytes).decode()
print(solution0)
else:
print('no found')


if __name__=='__main__':
main(sys.argv)

如果按照程序之前的逻辑,按字节进行比对,16byte长度的数据就会产生2^16个分支,分支呈指数级增长,因此我们不按照他的逻辑进行比对,我们的输入经过一些操作之后还是被存储在buffer里,我们直接拿处理过后的buffer与comparedata进行比较,这样这样就变成了简单的爆破,不会造成路径爆炸。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

ckeckfun_address=0x0804857C
##模拟到这个检测函数,其他的就不管了,只要让buffer满足条件即可,下面增加的约束就相当于创建了一个函数,实现的功能和之前的check是相等的,不过不是按位比较
simulation.explore(find=ckeckfun_address)
##运行到调用比较函数的状态
if simulation.found:
solution_state=simulation.found[0]
parameter_address=buffer_address
parameter_size_bytes=16

##.load读出buffer处的内存数据,即读取符号向量
parameter_bitvector=solution_state.memory.load(parameter_address,parameter_size_bytes)
compare_valve='OSIWHBXIFOQVSBZB'
##.add(约束内容),约束条件:运行到此处buffer的值与比较数据相等
solution_state.solver.add(parameter_bitvector==compare_valve)
solution0=solution_state.solver.eval(passwd0,cast_to=bytes).decode()

09_angr_hooks

新知识点:hook。

wiki:

钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。 处理被拦截的函数调用、事件、消息的代码,被称为钩子(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
import angr
import sys
import claripy
def main(argv):
path=argv[1]
project=angr.Project(path)
init_state=project.factory.entry_state()

checkfun_address=0x080486CA
##定义执行完要跳过多少字节,这意味着我们直接替换了这个函数
jump_len=5
@project.hook(checkfun_address,length=jump_len)
def fake_checkfun(state):
buffer_address=0x0804A044
buffer_len=16
usr_input_string=state.memory.load(buffer_address,buffer_len)
compare_data='OSIWHBXIFOQVSBZB'.encode()

##相等则设置寄存器eax的值为1,因为我们在对状态进行操作,所以不能直接使用if else语句
state.regs.eax=claripy.If(usr_input_string==compare_data,claripy.BVV(1, 32),claripy.BVV(0, 32) )

sm=project.factory.simgr(init_state)

def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

if sm.found:
solution_state=sm.found[0]
solution = solution_state.posix.dumps(0)
print(solution)
else:
raise Exception('Could not find the solution')


if __name__=='__main__':
main(sys.argv)

利用地址hook,最后部分模仿函数返回值,返回值储存在寄存器eax中。

在ida查看命令字节码长度。

1
2
3
4
5
6
7
8
9
10
11
##定义执行完要跳过多少字节,这意味着我们直接替换了这个函数
jump_len=5
@project.hook(checkfun_address,length=jump_len)
def fake_checkfun(state):
buffer_address=0x0804A044
buffer_len=16
usr_input_string=state.memory.load(buffer_address,buffer_len)
compare_data='OSIWHBXIFOQVSBZB'.encode()

##相等则设置寄存器eax的值为1,因为我们在对状态进行操作,所以不能直接使用if else语句
state.regs.eax=claripy.If(usr_input_string==compare_data,claripy.BVV(1, 32),claripy.BVV(0, 32) )

10_angr_simprocedures

仍然是利用hook解决路径爆炸的问题,上一题是利用地址比较麻烦,现在学习利用函数名来hook,有点像最开始的时候用函数替换good job的地址。

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
import angr
import sys
import claripy
def main(argv):
path=argv[1]
project=angr.Project(path)
init_state=project.factory.entry_state()
##定义一个继承angr.simprocedure的类
class Replacefun(angr.SimProcedure):
def run(self,to_check,length):
buffer_address=to_check
buffer_length=length

user_input_string=self.state.memory.load(buffer_address,buffer_length)
comparedata='OSIWHBXIFOQVSBZB'.encode()
return claripy.If(
user_input_string==comparedata,
claripy.BVV(1,32),
claripy.BVV(0,32)
)
check_equals_symbol='check_equals_OSIWHBXIFOQVSBZB'##函数名
project.hook_symbol(check_equals_symbol,Replacefun())

sm=project.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

if sm.found:
solution_state=sm.found[0]
solution = solution_state.posix.dumps(0)
print(solution)
else:
raise Exception('Could not find the solution')


if __name__=='__main__':
main(sys.argv)

把新知识点放下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
##定义一个继承angr.simprocedure的类
class Replacefun(angr.SimProcedure):
##后面两个参数按照原函数check_equals_OSIWHBXIFOQVSBZB来
def run(self,to_check,length):
buffer_address=to_check
buffer_length=length

user_input_string=self.state.memory.load(buffer_address,buffer_length)
comparedata='OSIWHBXIFOQVSBZB'.encode()
return claripy.If(
user_input_string==comparedata,
claripy.BVV(1,32),
claripy.BVV(0,32)
)
check_equals_symbol='check_equals_OSIWHBXIFOQVSBZB'##函数名
project.hook_symbol(check_equals_symbol,Replacefun())

11_angr_sim_scanf

和上面的一样,用来巩固。这样处理之后scanf可以接收多个参数。

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 angr
import sys
import claripy
def main(argv):
path=argv[1]
project=angr.Project(path)
init_state=project.factory.entry_state()

class myscanf(angr.SimProcedure):
def run(self,format_string,para0,para1):
input0=claripy.BVS('input0',4*8)
input1=claripy.BVS('input1',4*8)

self.state.memory.store(para0,input0,endness=project.arch.memory_endness)
self.state.memory.store(para1,input1,endness=project.arch.memory_endness)

self.state.globals['solution0']=input0
self.state.globals['solution1']=input1

scanf_symbol='__isoc99_scanf'
project.hook_symbol(scanf_symbol,myscanf())

sm=project.factory.simgr(init_state)
def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

sm.explore(find=is_good,avoid=is_bad)

if sm.found:
solution_state=sm.found[0]
stored_solutions0 = solution_state.globals['solution0']
stored_solutions1 = solution_state.globals['solution1']
solution = f'{solution_state.solver.eval(stored_solutions0)} {solution_state.solver.eval(stored_solutions1)}'
print(solution)
else:
raise Exception('Could not find the solution')


if __name__=='__main__':
main(sys.argv)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
##用myscanf来代替__isoc99_scanf
class myscanf(angr.SimProcedure):
##后面三个参数分别为格式化字符串,输入1,输入2
def run(self,format_string,para0,para1):
##符号化输入内容
input0=claripy.BVS('input0',4*8)
input1=claripy.BVS('input1',4*8)
##将符号向量载入内存buffer处
self.state.memory.store(para0,input0,endness=project.arch.memory_endness)
self.state.memory.store(para1,input1,endness=project.arch.memory_endness)
##将函数内的局部变量input转变为全局变量solution
self.state.globals['solution0']=input0
self.state.globals['solution1']=input1

scanf_symbol='__isoc99_scanf'
project.hook_symbol(scanf_symbol,myscanf())

12_angr_veritesting

之前使用hook或者添加约束来解决路径爆炸问题,现在直接在创建虚拟管理器的时候加上一个参数,simulation=project.factory.simgr(init_state,veritesting=True)

简单来说就是Veritesting结合了静态符合执行与动态符号执行,减少了路径爆炸的影响,在angr里我们只要在构造模拟管理器时,启用Veritesting了就行

不知道什么原因这个程序跑起来内存就会爆炸,看来还是没解决路径爆炸的问题,推测可能是环境的问题。

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
import angr
import sys
import claripy
def main(argv):
path=argv[1]
project=angr.Project(path)
init_state=project.factory.entry_state()
simulation=project.factory.simgr(init_state,veritesting=True)

def is_good(state):
return b'Good Job.' in state.posix.dumps(1)
def is_bad(state):
return b'Try again.' in state.posix.dumps(1)

simulation.explore(find=is_good,avoid=is_bad)

if simulation.found:
solution_state=simulation.found[0]
solution=solution_state.posix(0)
print(solution)
else:
print('no found'

if __name__=='__main__':
main(sys.argv)

13_angr_static_binary

作者文档里的内容:

这个挑战与第一个挑战完全相同,只是它被编译为静态二进制文件。通常,Angr会自动使用SimProcedures替换标准库函数,以实现更快的运行速度。

为了解决这个挑战,需要手动hook任何使用的标准库c函数。

什么叫静态什么叫动态?

拖进ida里很容易看出来,题目13function那一栏里比12多得多,这是因为静态链接库将所有依赖项都包含在目标二进制文件中,反观动态链接,同台链接是指程序在训醒时才需要加载所依赖的库,当我们使用动态链接库来编译程序时,编译器并不会将所有库函数的代码都合并为一个单独的可执行文件。相反,它只是在可执行文件中留下一些标记,以便在运行时从系统或其他位置加载动态链接库。

在静态链接库中,没有动态链接库来提供符号,我们需要手动hook任何使用的标准库c函数,并确保从main函数的开头开始执行。

在动态链接库中,动态链接器会提供符号,我们不需要手动hook标准库c函数。

这一题我们就要将main函数里所用到的标准库函数hook住,用angr的方法来取代,这能大大提高效率。

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
import angr
import sys
def main(argv):
path=argv[1]
project=angr.Project(path)
init_state=project.factory.entry_state()

printf_address=0x0804FAB0
scanf_address=0x0804FB10
strcmp_address=0x08048228
puts_address=0x080503F0
__libc_start_main_address=0x08048D60

project.hook(printf_address,angr.SIM_PROCEDURES['libc']['printf']())
project.hook(scanf_address,angr.SIM_PROCEDURES['libc']['scanf']())
project.hook(strcmp_address,angr.SIM_PROCEDURES['libc']['strcmp']())
project.hook(puts_address,angr.SIM_PROCEDURES['libc']['puts']())
##重点关注一下这里不太清楚是个啥
project.hook(__libc_start_main_address,angr.SIM_PROCEDURES['glibc']['__libc_start_main']())

def isgood(state):
return b'Good Job.' in state.posix.dumps(1)
def isbad(state):
return b'Try again.' in state.posix.dumps(1)
simulation=project.factory.simgr(init_state)
simulation.explore(find=isgood,avoid=isbad)

if simulation.found:
solution_state=simulation.found[0]
solution=solution_state.posix.dumps(0)
print(solution)
else:
print('no found')

if __name__=='__main__':
main(sys.argv)

14_angr_shared_library