diff --git a/doc/8.5_dop.md b/doc/8.5_dop.md index 3ce8d56..c8a0743 100644 --- a/doc/8.5_dop.md +++ b/doc/8.5_dop.md @@ -3,3 +3,223 @@ [paper](https://www.comp.nus.edu.sg/~shweta24/publications/dop_oakland16.pdf) ## 简介 + +* 上一代的攻击方式是通过代码注入来劫持程序控制流 +* ROP和DOP都是图灵完整的 +* 在程序的控制执行中执行一些简短的指令序列,使得模拟图灵机的具体操作成为可能,如赋值、运算等 + +### 非控制数据攻击(Non-control Data Attacks) + +* 在一些环境上(如现代浏览器),仅仅是几个内存字节的破坏就可能导致远程攻击者的图灵完全攻击 +* `printf`这样的格式化字符串函数,接受并解释格式化参数的这类函数,对格式化字符串“语言”来说,实际上也是图灵完整的解释器 +* 如果非控制数据攻击可以允许攻击者完全控制格式字符串参数,那么攻击者可以构造有效的payload + +### 数据导向编程的例子 + +Code1, 带有DOP Gadgets的FTP Server代码: + +```c +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, 函数将链接列表的整数字段递增给定值: + +```c +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攻击是非交互的。 + + 攻击者提供整个恶意输入,作为一个单个的数据传输。 + +* 模拟跳转操作: + + ```c + 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的定义 + + ```text + 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调度程序的定义 + + ```text + 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。但是,为了达到内存安全需要大量的开销。 + + 见参考文献: + + ```text + 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保护内核安全数据: + + ```text + 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攻击。