> For the complete documentation index, see [llms.txt](https://firmianay.gitbook.io/ctf-all-in-one/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://firmianay.gitbook.io/ctf-all-in-one/ba-xue-shu-pian/8.5_dop.md).

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

[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的定义

```
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攻击。
