解题源码以及二进制文件在:https://github.com/angr/angr-doc/tree/master/examples/cmu_binary_bomb
一道有趣的题目!
首先运行bomb文件,随便输入个字符串,bomb! 炸了!
总共有6关要闯,每次只要输入满足要求的数据才能进入下一关,否则就爆炸。
拖到IDA里面分析,发现第一关的字符串比较还蛮简单,下面几关就需要计算了。
int __fastcall phase_1(__int64 a1)
{
int result; // eax@1
result = strings_not_equal(a1, "Border relations with Canada have never been better.");
if ( result )
explode_bomb(a1, "Border relations with Canada have never been better.");
return result;
还是直接angr符号执行进行分析算了。
当然angr符号执行分析肯定也不简单,还是需要分析的。
(在我十分高估angr的时候我写出过如下代码:
大意就是模拟执行,找到包含0x400E49的路径,求解约束
import angr
import claripy
b=angr.Project('bomb',load_options={'auto_load_libs':False})
sm=b.factory.simulation_manager(b.factory.entry_state())
sm.explore(find=0x400E49)
print len(sm.found)
found=sm.found[0]
solution1 = found.posix.dumps(0)
print "print"
print repr(solution1)
Error: found=sm.found[0]
IndexError: list index out of range
呵,native!)
要想符号执行,首先要让angr能够模拟执行下去,如果都执行不下去,肯定是找不到包含目标地址的路径的啊。
那angr什么时候会执行不下去呢?我们需要提供哪些辅助呢?这个。。以后再说。先看这道题的用例都做了些什么。
首先是phase_1:
从IDA中,我们发现它的验证逻辑就是字符串比较,而且字符串以及明文在代码中了,所以逻辑非常简单。同时这个函数没有调用任何库函数以及系统调用,所以angr肯定能模拟执行的啦。
使用angr分析时,为了避免执行到angr无法模拟的函数或系统调用,用例代码只对phase_1函数进行了符号执行分析。
创建一个空状态,起始地址就是phase_1的起始地址;
创建一个符号变量,逆向发现phase_1是从reg.rdi寄存器读取输入数据的,因此将reg.rdi赋值为符号变量;
注意注意:reg.rdi存放的是地址,phase_1是先从reg.rdi取址,然后从这个地址读取字符串的。IDA里可以看到,read_line函数是从窗口读取数据放入到地址 0x603780中,然后将这个地址传递个reg.rdi的。(测试过,这个地址可以为任意值,只要不影响后面的执行就可以,毕竟只是提供了一个存放符号变量的媒介。)
初始化符号执行,找到phase_1的返回地址end = 0x400ef7,求解此时符号变量的值。
phase_2:
phase_2部分是输入6个数字进行判断,第一个数是否是1,然后后面一个数是否是前一个数的2倍。
phase_2函数调用了库 ___isoc99_sscanf,似乎angr不能处理呢,所以跳过了这个函数,符号执行的起始地址为:0x400f0a。
由于判断逻辑是从栈里面取数据,所以我们需要创建6个符号变量,并压入栈中。然后开始执行符号执行,结束地址就是phase_2函数的结束地址就可以。
phase_3:
phase_3部分是输入两个整数,switch case语句,根据第一个整数判断第二是否匹配。
与phase_2类型,不过这个函数有多个解,所以可以重点关注一下,如何求解多个解的方法。
对于explore发现的所有活跃(active)状态(or 路径)都加入队列里,分别求解它们的值。
同时对于found集合,不再只是取第一个found[0]而是对所有的found都进行遍历求解。
phase_4:
phase_4部分是输入两个整数,第二个整数不变,第一个整数递归计算,最后结果均为0。所以第二个数为0,第一个数利用angr求解为7。这里测试了一下栈数据的读取。有的时候通过found.regs.rsp - 0x18 + 0x8读取第一个参数,有的时候通过found.stack_pop()读取参数。其实它们是等价的。
rsp指向的是返回地址,所以会先found.stack_pop() 弹出返回地址 使rsp+0x8
rsp+0x8指向的是第一个参数 这时候found.stack_pop()弹出的才是第一个参数。
rsp+0xc指向的是第二个参数。
具体的内容可以学习一下:逆向基础 堆栈详解 —— https://mp.weixin.qq.com/s/PlLHRJ3XbJ6z6C5-rPXfEQ
phase_5:
phase_5需要接收长度6的字符串,遍历字符串的字符取byte值去一个字符数组中索引,拼接成一个新的字符串。新的字符串应该为"flyers"。
这次它利用了proj.kb.obj.get_symbol函数,通过函数名来获取想要分析的函数地址。
start = proj.kb.obj.get_symbol('