CTF_Angr
github: https://github.com/jakespringer/angr_ctf. 可以看到有每一个题目的文件夹(题目生成包,用来生成可执行文件,只需刷题的话大概没用),还有dist
和solutions
两个文件夹。
dist
里有:
XX_xxxx
:每道题的elf。scaffoldXX.py
:解题脚本挖空范例(题面,需要填空,注释用来解释语句的作用很详细)。
solutions
里各道题的文件夹里有:
XX_xxxx
:每道题的elf。scaffoldXX.py
:解题脚本挖空范例(同上)。solveXX.py
:解题脚本(正确答案=v=)。
这里面都是典型的angr使用方法,这里仅以第一个范例进行演示,剩下的可以按照相同的思路求解。
0X00-angr_find
使用IDA打开dist/00_angr_find,F5转化为伪C代码

程序输入password,经过complex_function函数处理,如果最终等于“JACEJGCS”就输出Good Job。
complex_fuction函数判断了输入的取值范围。

本题使用传统的“爆破”方法也可以解决,一位一位拼出符合题意的输入(“JXWVXRKX”)。脚本如下:
1 | str1 = "JACEJGCS" |
知道目标输出,构造合适的输入,可以使用Angr这个符号执行工具解题。
①.新建一个Angr工程,并且载入二进制文件
1 | path_to_binary = 'D:\WinAFL\angr_ctf\dist\00_angr_find' |
②.创建程序入口点的state
1 | state = p.factory.entry_state(add_options={angr.options.SYMBOLIC_WRITE_ADDRESSES}) |
③.将要求解的变量符号化
本题目中可以省略这步操作(当然也可以保留,但后面就不能用dump,要用eval),因为不是手动设置的输入,是在程序运行时用户任意输入的。
详细解释见下“注”。
1 | u = claripy.BVS("u", 8) |
④.创建SimulationManager进行程序执行管理
1 | sm = p.factory.simulation_manager(state) |
⑤.搜索满足我们目标的state
目标state是”good job”, IDA中确定地址为0x8048678

1 | print_good_address = 0x8048678 |
⑥.求解程序执行到state时,符号化变量所需要的约束条件
1 | sm.explore(find=print_good_address) |
⑥.解出约束条件,获得目标值
solution_state.posix.dumps(0)
代表该状态执行路径的所有输入
solution_state.posix.dumps(1)
代表该状态执行路径的所有输出
1 | if sm.found: |
完整代码见 "./angr_ctf/solutions/00_angr_find/solve00.py"
, 与爆破得到的结果一致。
注:本题中不是手动设置的输入,而是程序运行过程中进行的输入,因此可以使用dump(0) dump标准输入来得到输入。但如果题目修改为char s1[9]="00000000"
,并去掉scanf,这时就要将输入符号化,使用angr求解器(solver)提供的eval函数,得到满足条件的可能的输入。
solver.eval()函数详细说明:https://blog.csdn.net/doudoudouzoule/article/details/79394447
baby-re(DEFCON 2016 quals)
github:https://github.com/angr/angr-doc/tree/master/examples,这里有很多例子,之前已经实践过sym_write,这次选择一个复杂点的,defcon2016quals_baby-re,作为一道经典的CTF真题,《从0到1 CTFer成长之路》一书也对这个例子进行了详细的分析。
首先IDA打开二进制文件,F5查看C伪代码,了解程序逻辑。输入13个字符,然后通过CheckSolution判断输入字符是否满足约束条件。(截图为部分程序代码)

分析可知成功的地址是0x4028E9,失败的地址是0x402941


然后用最朴素的方式构造payload,代码如下:
1 | import angr |
程序输出正确的结果,但用时较长,下面要想办法优化。

LAZY_SOLVES
在LAZY_SOLVES打开的情况下,只要路径在基本块的末尾分支为几个,angr就不会主动检查每个成功的状态是否可以满足。相反,angr认为他们都是可以满足的,并且继续创造成功的路径。LAZY_SOLVES指在不运行的时候检查当前的条件是否能够成功到达目标位置。这样无法避免一些无解的情况,但是可以显著提高脚本的运行速度,是在路径爆炸(创建更多路径)和花费在约束求解上的时间之间的平衡。
在早期angr版本中默认LAZY_SOLVES是开启的,它可以加快我们分析的速度,因为它不会对每个状态都进行判断。新版本可通过下面的语句开启:
1
sm.one_active.options.add(angr.options.LAZY_SOLVES
经过实践,时间大大缩短,从449.51s降低到15.78s
blank_state
有些时候程序的输入数据被分类很多组,或者程序获取输入的逻辑实现的很复杂,严重干扰了angr分析,这时候就要跳过那些输入指令,加载程序,提高符号执行效率。创建
initial_state
时,使用factory的blank_state方法,传入地址,表示从该地址的状态开始,实现任意位置加载程序。1
state = proj.factory.blank_state(addr=0x4025E7) #main函数地址
hook
在scanf函数前面存在fflush函数,它的功能是清空缓冲区,将输入写入stream。注意到在每次输入时,都会调用printf和fflush这两个对程序关键算法分析没有帮助的函数浪费时间,因此hook这两个函数,让他们直接return。
1
2proj.hook_symbol('printf',angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](return_value=0), replace = True)
proj.hook_symbol('fflush',angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'](return_value=0),replace = True)优化scanf:用自己的
my_scanf()
来代替__isoc99_scanf
,我们在保持scanf功能不变的情况下,将我们的符号变量存储进去。1
2
3
4
5
6
7
8
9class my_scanf(angr.SimProcedure):
def run(self, fmt, des): # 参数为 (self + 该函数实际参数)
simfd = self.state.posix.get_fd(0) # 创建一个标准输入对对象
data, real_size = simfd.read_data(4) # 从标准输入中读取一个整数,返回值第一个是读到的数据内容 第二个数内容长度
self.state.memory.store(des, data) # 将数据保存到相应参数内
return 1 # 返回原本函数该返回的东西
proj.hook_symbol('__isoc99_scanf', my_scanf(), replace=True)
# 这里%d对应int 是4个字节 但是读取到一个int所以返回1 所以这完全是模拟的原来的函数因为我们这里Scanf是要向内存写入数据的,于是我们利用使用
state.memory.store(addr, val)
接口将数据保存到相应参数内。优化scanf就可以达到和控制返回值类似的效果,对于速度提升效果明显,时间缩短到4.86s
Claripy
angr中通过claripy模块手动构造输入,claripy.BVS可以直接创建符号变量,第一个参数为变量名,第二个参数是位数,可以这样创建用户输入,然后把这些变量保存在内存地址。
1
flag_chars = [claripy.BVS('flag_%d' % i, 32) for i in range(13)]
1
2
3
4
5class my_scanf(angr.SimProcedure):
def run(self, fmt, ptr):
self.state.mem[ptr].dword = flag_chars[self.state.globals['scanf_count']]
self.state.globals['scanf_count'] += 1
proj.hook_symbol('__isoc99_scanf', my_scanf(), replace=True)1
simulation.one_active.globals['scanf_count'] = 0
这样程序每次调用scanf时,其实就是在执行my_scanf就会将flag_chars[i]存储到self.state.mem[ptr]当中,这其中ptr参数,其实就是本身scanf函数传递进来的。为了控制下标,我们设置了一个全局符号变量scanf_count记录scanf函数的数目,存储在state.globals里面,使得第i个scanf函数的state.mem赋值第i个符号变量。
这种优化方式时间缩短,效果不明显,平均5.02s
综合上述分析优化,完整程序代码见
./angr-doc/examples/defcon2016quals_baby-re/solve.py
参考资料
https://blog.csdn.net/doudoudouzoule/article/details/79977415
https://b0ldfrev.gitbook.io/note/symbolic_execution/angr-jin-jie
https://www.anquanke.com/post/id/214288
https://blog.csdn.net/doudoudouzoule/article/details/79394447