使用angr和Radare解决CMU的二进制炸弹

前言

最近在学习angr的使用,主要是如何利用angr来进行符号执行分析。发现了国外一篇比较实用的文章介绍如何使用angr和Radare(二进制分析框架)来分析CMU的二进制炸弹问题。故在此翻译一下这篇文章的工作。

angr

angr是一个使用python语言编写的二进制分析框架,它主要是进行静态和动态的符号分析,现已成为CTF比赛的一大利器。angr最主要的一个工具就是符号执行,具体的符号执行可以参考MIT的一个课程(需要梯子才可以观看)。

Radare2

radare2是从零开始重写radare,以便提供一组库和工具来处理二进制文件。

在这篇文章里是先用的Radare2对二进制炸弹进行分析,弄清二进制文件的逻辑关系,然后再用angr的符号执行工具解出答案。关于radare2工具的介绍可以参考Radare2 Book

所面临的问题

要用angr和radare2所解决的问题是CMU的二进制炸弹,我们在此只分析phase2部分,如果对其他部分有兴趣的话可以参考我的另一篇博客对二进制炸弹做了全面的分析。phase2主要是要求输入6个正确的数字。

Crack

Radare2分析

  1. 使用Radare2加载程序,并输入aaa开始分析二进制程序

  2. 首先利用Radare2中的工具afl来查找符合条件的函数。使用afl并且grep(使用符号~来代表)筛选关键字, 可以看到筛选结果中有phase2。

  3. 使用seek工具来定位到我们感兴趣的函数处,此处为sym.phase_2函数。并用pdf([p]rint [d]issembly of [f]unction)命令来展示该函数的内容。

  4. 如何要展示函数中的控制流程图可以在Radare2中使用VV(两个大写的V)指令。

  5. 为了进行符号执行,我们必须弄清楚程序的输入是什么,通过radare工具反汇编出来的代码可以看出,函数read_six_numbers很有可能就是处理输入的函数。

  6. 在radare2中使用ga指令进入read_six_numbers函数, 具体的函数代码如下所示:

可以看出该函数中以六个数字作为scanf函数的输入,所以我们就将此作为输入。

  1. 接下来我们需要分析符号执行所需要开始的代码处,符号执行不能走的路径以及要到达的目标代码处。

    从上图可以看出0x400f10和0x400f20处的代码都调用explode_bomb函数。


具体的explode_bomb函数如上图所示,我们可以看出该函数会调用exit函数,所以我们要避免走到explode_bomb函数中去。即避免走到0x400f10和0x400f20。
要到达的目标函数我们可以设置到从phase2函数中返回。我们将调用完read_six_numbers函数后一条指令即0x400f0a作为程序分析的开始处。

angr分析

具体的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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
## Binary found here: http://csapp.cs.cmu.edu/3e/bomb.tar

import angr, logging
from subprocess import Popen, PIPE
from itertools import product
import struct

def main():
proj = angr.Project('bomb', load_options={'auto_load_libs':False})

logging.basicConfig()
logging.getLogger('angr.surveyors.explorer').setLevel(logging.DEBUG)

def nop(state):
return

bomb_explode = 0x40143a

# Start analysis at the phase_2 function after the sscanf
state = proj.factory.blank_state(addr=0x400f0a)

# Sscanf is looking for '%d %d %d %d %d %d' which ends up dropping 6 ints onto the stack
# We will create 6 symbolic values onto the stack to mimic this
for i in xrange(6):
state.stack_push(state.se.BVS('int{}'.format(i), 4*8))

# Attempt to find a path to the end of the phase_2 function while avoiding the bomb_explode
path = proj.factory.path(state=state)
ex = proj.surveyors.Explorer(start=path, find=(0x400f3c,),
avoid=(bomb_explode, 0x400f10, 0x400f20,),
enable_veritesting=True)
ex.run()
if ex.found:
found = ex.found[0].state

answer = []

for x in xrange(3):
curr_int = found.se.any_int(found.stack_pop())

# We are popping off 8 bytes at a time
# 0x0000000200000001

# This is just one way to extract the individual numbers from this popped value
answer.append(str(curr_int & 0xffffffff))
answer.append(str(curr_int>>32 & 0xffffffff))

return ' '.join(answer)

def test():
assert main() == '1 2 4 8 16 32'

if __name__ == '__main__':
print(main())