CTF-All-In-One/doc/8.5_dop.md
2018-08-27 20:42:35 +08:00

12 KiB
Raw Blame History

8.5 Data-Oriented Programming: On the Expressiveness of Non-Control Data Attacks

paper

简介

  • 上一代的攻击方式是通过代码注入来劫持程序控制流
  • ROP和DOP都是图灵完整的
  • 在程序的控制执行中执行一些简短的指令序列,使得模拟图灵机的具体操作成为可能,如赋值、运算等

非控制数据攻击Non-control Data Attacks

  • 在一些环境上(如现代浏览器),仅仅是几个内存字节的破坏就可能导致远程攻击者的图灵完全攻击
  • printf这样的格式化字符串函数,接受并解释格式化参数的这类函数,对格式化字符串“语言”来说,实际上也是图灵完整的解释器
  • 如果非控制数据攻击可以允许攻击者完全控制格式字符串参数那么攻击者可以构造有效的payload

数据导向编程的例子

Code1, 带有DOP Gadgets的FTP Server代码

1 struct server{ int *cur_max, total, typ;} *srv;
2 int connect_limit = MAXCONN; int *size, *type;
3 char buf[MAXLEN];
4 size = &buf[8]; type = &buf[12];
5 ...
6 while(connect_limit--) {
7     readData(sockfd, buf); // stack bof
8     if(*type == NONE ) break;
9     if(*type == STREAM) // condition
10      *size = *(srv->cur_max); // dereference
11    else {
12      srv->typ = *type; // assignment
13      srv->total += *size; // addition
14 } ... (following code skipped) ...
15 }

上述代码不会在良性控制流中调用任何涉及安全的关键功能,因此漏洞仅破坏局部变量。

Code2, 函数将链接列表的整数字段递增给定值:

1 struct Obj{struct Obj *next; unsigned int prop;}
2 void updateList(struct Obj *list, int addend) {
3    for(; list != NULL; list = list->next)
4       list->prop += addend;
5 }

MinDOP

最小DOP语言

语义 C语言 DOP Gadgets
算术/逻辑运算 a op b *p op *q
赋值 a = b *p = *q
加载 a = *b *p = **q
储存 *a = b **p = *q
跳转 goto L vpc = &input
条件跳转 if a goto L vpc = &input if *p

注:p - &a; q - &b; op - 算术/逻辑运算; vpc - virtual input pointer

在MinDOP中实现了经精心选择的储存单元不是硬件寄存器的虚拟寄存器在Gadgets的控制下下使用。

在概念上数据导向Gadgets模拟了三种逻辑微操作一是加载微操作二是预期的虚拟操作语义三是储存微操作加载微操作模拟从储存器中读取虚拟寄存器操作数储存微操作将计算结果写回虚拟寄存器。

每一个Gadgets的语义都和彼此不同许多不同的x86指令序列足以模拟虚拟操作由于x86指令集支持好几中寻址模式只要微操作的顺序是正确的不同的序列也可以正常工作如指令add %eax, (%ecx) ,这一条指令就执行了加载、算术和存储三个微操作。

C srv->total += *size;
ASM 1 mov (%esi), %ebx //load
2 mov 0x4(%edi), eax //load
3 add %ebx, %eax //addition
4 mov %eax, 0x4(%edi) //store

数据导向Gadgets和代码导向中的Gadgets有两点不同一是数据导向Gadgets需要使用内存传递操作结果而代码导向Gadgets既可以使用内存也可以使用寄存器二是数据导向Gadgets必须在一个合法的控制流中执行且没有必要立即执行。

  • 模拟算术运算:

    如果语言支持条件跳转指令,那么可以更有效的模拟乘法运算(?)。MinDOP支持条件跳转那么就可以做到检查一个值大于或小于一个常数。

  • 模拟赋值运算:

    在MinDOP中赋值Gadgets从一个储存单元读取数据并直接存储到另一个储存单元在这种情况下我们可以直接跳过立即数的加载操作。

C srv->typ = *type;
ASM 1 mov (%esi), %ebx // load
2 mov %ebx, %eax // move
3 mov %eax, 0x8(%edi) // store
  • 模拟加载/存储操作:

    加载和存储在C语言中需要内存的反引用将一个寄存器作为地址并访问内存位置进行读取或写入。由于在DOP中寄存器是内存模拟的因此反引用操作通过两个内存反引用来模拟第一个内存反引用模拟寄存器的访问第二个内存反引用第一个反引用的结果寄存器值作为地址。

    C 1 LOAD1: *size = *(srv->cur_max);
    2 LOAD2: memcpy(dst, *src_p, size);
    3 STORE: memcpy(*dst_p, src, size);
    ASM(LOAD1) 1 mov (%esi), %ebx // load
    2 mov (%ebx), %eax // load
    3 mov %eax, (%edi) //store
  • Gadgets的调度程序

    Gadgets的调度程序也是x86的指令序列攻击者可以重复调用Gadgets可以模拟Gadgets调度程序的x86指令的一个常见序列是一个循环它对模拟小工具的计算进行迭代并且有一个选择器。每次迭代使用前一次迭代中Gadgets的输出执行Gadgets的子集为了将迭代i中的Gadgets的输出引导至第i+1中的Gadgets的输入选择器将第i+1的加载地址更改为第i次迭代的存储地址。

    选择器的行为由攻击者通过内存错误来控制(?)

  • 另一类DOP攻击是非交互的。

    攻击者提供整个恶意输入,作为一个单个的数据传输。

  • 模拟跳转操作:

    1 void cmd_loop(server_rec *server, conn_t *c) {
    2 	while (TRUE) {
    3 		pr_netio_telnet_gets(buf, ..);
    4 		cmd = make_ftp_cmd(buf, ...);
    5 		pr_cmd_dispatch(cmd); // dispatcher
    6 	}
    7 }
    8 char *pr_netio_telnet_gets(char * buf, ...) {
    9 	while(*pbuf->current!=\n && toread>0)
    10 		*buf++ = *pbuf->current++;
    11 	}
    

    这里的关键是找到一个合适的变量可以在每次循环迭代中被破坏的虚拟PC指针如上述代码有一个内存指针pubf -> current指向了恶意网络输入的缓冲区。在每一次循环迭代中代码从该缓冲区读取一行然后在循环体中处理它因此这个指针可以用来模拟虚拟PC指针。对于模拟非条件跳转攻击者只需要配置好内存来触发另一个操作Gadgets如加法、赋值来改变虚拟PC指针的值。

    有两种方式模拟条件跳转一种情况是使用虚拟PC指针读取内存配置是有条件的攻击者只需使用操作k将合适的变量设置为读取条件另一种情况是操作k的执行条件依赖于数据变量。

    与ROP不同DOP受限于应用的源控制流。

Gadgets的定义

Input: G:- the vulnerable program
Output: S:- data-oriented gadget set
1 S = ;;
2 FuncSet = getFuncSet(G)
3 foreach f 2 FuncSet do
4 	cfg = getCFG(f)
5 	for instr = getNextInstr(cfg) do
6 		if isMemStore(instr) then
7 			gadget = getBackwardSlice(instr, f)
8 			input = getInput(gadget)
9 			if isMemLoad(input) then
10				S = S [ fgadgetg

LLVM IR 提供了比二进制程序更多的语义也避免了解析程序源代码它还允许以任何具有LLVM前端的语言编写的源代码的语言不可知分析。

相同语义的Gadgets功能上等同于同一个MinDOP操作赋值Gadgets可以用来准备其他Gadgets的立即数有条件的Gadgets有助于简单Gadgets实现高级计算。因为不改变控制流所以DOP中没有函数调用Gadgets。

我们将Gadgets分为三类一类是全局的一类是函数参数另外一类是局部Gadgets。全局Gadgets操作全局变量内存错误可以在任意地址改变这些变量函数参数Gadgets操作被传递给函数的参数内存错误可以控制函数的参数局部Gadgets在局部变量中产生在函数内部出现的内存错误可以激发他们。

Gadgets调度程序的定义

Input: G:- the vulnerable program
Output: D:- gadget dispatcher set
1 D = ;;
2 FuncSet = getFuncSet(G)
3 foreach f 2 FuncSet do
4 	foreach loop = getLoop(f) do
5 		loop.gadgets = ;
6 		foreach instr = getNextInstr(loop) do
7 			if isMemStore(instr) then
8 				loop.gadgets [= getGadget(instr)
9 			else if isCall(instr) then
10 				target = getTarget(instr)
11 				loop.gadgets [= getGadget(target)
12 		if loop.gadgets != ; then
13 			D = D [ floopg

攻击的构造

  1. 准备Gadgets自动 发现一个内存错误从程序代码中定位到该函数然后我们确定是否包含易受攻击代码并收集数据导向Gadgets的Gadgets调度程序。

  2. 构造攻击链 我们将预期的恶意MinDOP程序为输入每一个MinDOP操作由相同功能的数据导向Gadgets实现并根据优先级选择合适的Gadgets。

  3. 可协作的验证 一旦我们获得一系列数据导向Gadgets来完成我们想要的功能我们将验证每一个围绕它们的调度程序完成拼接是否可能。向程序提供构造好的输入来触发内存错误来连接相应的Gadgets如果攻击不起作用回滚步骤2来选择不同的Gadgets并再次尝试。

DOP的潜在防御

内存安全

内存安全首先通过检测恶意内存损坏来防止出现内存错误。DOP利用大量的内存错误来粘合各种数据导向Gadgets因此内存安全执行将防止所有可能的漏洞攻击包括DOP。但是为了达到内存安全需要大量的开销。

见参考文献:

L. Szekeres, M. Payer, T. Wei, and D. Song, “SoK: Eternal War in Memory,” in Proceedings of the 34th IEEE Symposium on Security and Privacy, 2013.

数据流完整性(DFI)

在程序执行之前DFI生成数据流图(DFG)DFG是关于定义-使用关系的数据库DFI在程序的检测之前检查每个存储单元是否有合法的指令定义。通过这种方式DFI可以防止破坏程序内存的恶意行为。然而完整的DFI保护依然需要很大的开销。

参考文献使用DFI保护内核安全数据

C. Song, B. Lee, K. Lu, W. R. Harris, T. Kim, and W. Lee, “Enforcing Kernel Security Invariants with Data Flow Integrity,” in Proceedings of the 23th Annual Network and Distributed System Security Symposium, 2016.

细粒度的数据面随机化

细粒度的数据面随机化可以缓解DOP攻击因为DOP仍然需要获取某些非控制数据指针的地址。然而数据面上的细粒度随机化可能会导致高性能开销因为所有数据包括控制数据和非控制数据应该常被随机化。 高性能和强安全性保证的数据面随机化仍然是一个悬而未决的问题。

硬件错误和软件错误隔离

内存隔离被广泛用于防止未经授权访问高权限资源只有合法的代码区域才能访问特定的资源这样可以防止一些直接的数据破坏攻击但是DOP不依赖于安全关键数据的可用性 - 它可能会损坏指针只能针对数据导向Gadgets。为了防止这种攻击内存隔离必须保护所有指针不受纯数据影响。

然而精确识别二进制代码中的指针是一个挑战此外一个程序中有成千上万的指针保护所有的指针将带来很大的开销因此当程序被指针隔离正确保护时隔离只能防止部分DOP攻击。