CTF-All-In-One

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

paper

简介

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

数据导向编程的例子

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必须在一个合法的控制流中执行,且没有必要立即执行。

C srv->typ = *type;
ASM 1 mov (%esi), %ebx // load
  2 mov %ebx, %eax // move
  3 mov %eax, 0x8(%edi) // store

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