软件学报  2017, Vol. 28 Issue (10): 2583-2598   PDF    
ROP图灵完备的普遍可实现性
袁平海1,2, 曾庆凯1,2     
1. 南京大学 计算机科学与技术系, 江苏 南京 210023;
2. 计算机软件新技术国家重点实验室(南京大学), 江苏 南京 210023
摘要: 返回导向编程(return-oriented programming,简称ROP)被广泛用于软件漏洞利用攻击中,用来构造攻击代码.通过更新ROP构造技术,证实了图灵完备的纯ROP攻击代码在软件模块中是普遍可实现的.ROP构造功能代码的难点是实现条件转移逻辑.通过深入分析条件转移机器指令的执行上下文发现,对这些指令的传统认知存在一定的局限性.事实上,在已有代码中存在少量的条件转移指令,它们的两个分支的开始部分都是可复用的代码片段(称为gadgets),而且这两个gadgets会从不同的内存单元中取得下一个gadget的地址,因此,以这些条件转移指令开始的代码片段可以帮助ROP实现条件转移逻辑.把这种代码片段称为if-gadget.在Linux和Windows系统上的实验结果表明,if-gadget普遍存在,即使在代码量很小的日常可执行程序中也存在.在Binutils程序集上的实验结果表明,引入if-gadget后,构造图灵完备的ROP代码要比用传统方法容易得多.在Ubuntu这样的主流操作系统上,由于可执行程序上默认没有实施防御ROP攻击的保护机制,因此,攻击者可以在这些软件模块中构造纯ROP攻击代码来发动攻击.由此可见,ROP对系统安全的威胁比原来认为的严重得多.
关键词: 软件漏洞利用     返回导向编程     图灵完备计算     条件转移逻辑    
Universal Availability of ROP-Based Turing-Complete Computation
YUAN Ping-Hai1,2, ZENG Qing-Kai1,2     
1. Department of Computer Science and Technology, Nanjing University, Nanjing 210023, China;
2. State Key Laboratory for Novel Software Technology (Nanjing University), Nanjing 210023, China
Foundation item: National Natural Science Foundation of China (61772266, 61572248, 61431008, 61321491); National Key Technology R & D Program of China (2012BAK26B01)
Abstract: Return-Oriented programming (ROP) is widely applied in modern software vulnerability exploitations.This work demonstrates that Turing-complete ROP code is universally available in everyday software.A big challenge for applying ROP is to construct the functionality of conditional jumps.Because conditional branch instructions are abandoned as they are deemed no use for achieving this functionality, existing works resort to some awkward methods which suffer from high risk of failure.By analyzing the execution context of conditional branch instructions, this study finds that the traditional viewpoint on these instructions only partially reveals the truth.In fact, there are some conditional branch instructions in which two branches each starts a reusable gadget, and these two gadgets fetch the next gadget from different memory cells.Hence, the code snippets beginning at these conditional instructions can implement the conditional jumps for ROP code.Such a code snippet is named if-gadget.Experimental results show that if-gadgets are widely available in executables of Linux and Windows platforms.Evaluations on programs of Binutils demonstrate that, Turing complete ROP code can be achieved with the help of if-gadgets while existing techniques even fail to gather Turing complete gadgets.On platforms such as Ubuntu, because the executables running on them do not support ASLR by default, attackers can construct Turing-complete ROP code on these executables and then mount an attack.Therefore, ROP-based attacks pose a great threat to modern platforms, which is far more serious than originally thought.
Key words: software vulnerability exploitation     return-oriented programming     Turing-complete computation     conditional jump    

软件漏洞依然普遍存在, 甚至有些被广泛使用的动态库也一直受到软件漏洞的困扰.以glibc为例, 这两年的漏洞已经超过15个, 其中, CVE-2015-8779是栈上的缓冲区溢出漏洞, 可以让攻击者用长的目录名来触发漏洞并执行任意代码[1].在软件漏洞利用攻击中, 攻击者成功获取了程序的控制权后, 可能会在受害系统上创建后门或安装恶意软件, 以便进一步窃取隐私信息或实施金融敲诈.因此, 保护日常软件免于漏洞利用攻击是系统安全的一个重要课题.

当前, 数据执行保护(data execution prevention, 简称DEP)[2]和地址空间布局随机化(address space layout randomization, 简称ASLR)[3]等安全机制已被主流系统广泛配置.这些安全机制可以有效地缓解漏洞利用攻击.DEP通过设置进程页表, 可去除数据段所在页的可执行属性.该机制可以有效地阻止传统的代码注入攻击; 该攻击以输入数据的方式在数据段上注入shellcode并执行之.为了绕过DEP, 攻击者发明了代码复用攻击技术; 这类攻击通过复用受害进程地址空间中已有的代码来构造攻击代码.返回导向的编程(return-oriented programming, 简称ROP)是一种典型的代码复用技术[4], 它通过串接以RET指令结束的代码片段(称为gadget)来构造恶意代码.ASLR是缓解ROP攻击的有效策略.ASLR将软件模块加载到随机选择的地址空间, 使得gadget的地址不可预测, 因此可以显著提高ROP的攻击门槛.在Linux系统上, 几乎所有的动态库都支持ASLR.但是, 由于性能问题[5]和兼容性问题, 很多Linux发行版本中的可执行程序默认地并不支持ASLR(当可执行程序被编译为“位置无关的可执行程序”后, 能够支持ASLR.但是, 在很多Linux发行版本中, 这并不是默认配置.例如, 在Ubuntu系统中, 大部分可执行程序不支持ASLR.Windows系统下的情况似乎更为严重, 由于系统设计机制的限制, 很多重要动态库的加载地址只能在系统启动时做一次随机化.本文以Linux为例讨论安全问题, 所提及的技术在Windows系统上也是通用的).

由于用shellcode构造恶意代码要比ROP技术容易得多, 因此攻击者现在普遍采用ROPcode+shellcode的模式构造攻击代码.具体地, ROP代码用来绕过DEP保护机制, shellcode则用来实现更多的恶意企图.典型地, ROP代码调用mprotect函数为shellcode所在的数据段增加可执行属性, 之后再执行shellcode.防御者已经找到了有效的方法来防范这种攻击.由于日常可执行程序通常不会使用动态生成的代码, 即没有为内存段动态增加可执行属性的需求, 因此可以约束对mprotect系统函数的调用以遏制这种攻击模式.事实上, Linux系统正在应用该策略来限制对内存页属性的动态修改[6].该机制启动后, 将阻止进程进行以下操作:(1) 改变未被初始化为可执行的内存页的可执行状态; (2) 使只读的可执行页可再次写入; (3) 在分配的动态内存中创建可执行页; (4) 让重定位后只读的数据页可再次写入.该机制启动后, 一种可能的攻击方式是复用不支持ASLR的可执行程序中的gadgets来构造纯ROP攻击代码.考虑到用ROP构造复杂代码的困难性, 以及可执行程序的代码量通常较少的事实, 这种攻击普遍认为很难实现.这也是默认情况下Ubuntu等操作系统中的可执行程序不支持ASLR的一个重要理由.

用ROP技术构造功能代码要比用程序语言编写功能代码难得多.ROP代码的执行流是通过RET指令(或等价的指令序列)串接起来的.该指令从栈上取得控制流转移的目标地址, 即下一个gadget的地址.ROP代码需要控制栈指针寄存器(stack pointer register, 简称SP)的值, 让RET指令从指定的栈上内存单元(在攻击者劫持程序控制流前, 有效载荷即payload已经存放到栈上)取出目标地址.ROP技术构造功能代码的最大挑战是实现条件转移逻辑功能.传统观点认为, 条件转移机器指令只能修改指令指针寄存器(instruction pointer register, 简称IP)的值, 而不能有条件地改变SP的值, 因此它们不能帮助ROP代码实现条件转移逻辑[4, 7, 8].在x86架构下, 攻击者用已有方法实现简单的条件转移逻辑也需要串接11个不同的gadgets[4].在构造过程中, 缺少某些类型的gadgets或者不能完全消除gadgets之间的副作用, 都会导致构造失败.

本工作通过更新ROP实现条件转移逻辑的方案, 证实了图灵完备的纯ROP攻击代码在软件模块中是普遍可实现的, 即上文提及的攻击也是普遍可行的.通过深入分析条件转移机器指令的执行上下文我们发现, 对这些指令的传统认知存在局限性.尽管大部分条件判断指令不可用于帮助ROP代码实现条件转移逻辑, 但仍然存在着少部分条件判断指令:它们的每个分支的开始部分都是一个可复用的经典gadget, 这两个gadgets会从不同的内存单元(内存单元的内容可能已经被加载到寄存器中)中取得下一个gadget的地址.因此, 这样的代码片段可以帮助ROP实现条件转移逻辑.我们把这种以条件判断指令开始的代码片段称为if-gadget.引入if-gadget后实现条件转移逻辑要比用传统方法简单得多.典型地, 只需要两个代码片段就能实现该功能.本文阐述if-gadget实现条件转移逻辑的原理, 讨论引入if-gadget后, gadget的查找算法、payload布局以及if-gadget的调度.

在Linux和Windows上的实验结果表明, if-gadget在软件模块中普遍存在.甚至在一个简单的HelloWorld程序中, 都存在若干if-gadgets.在Binutils程序集上的实验结果表明, 引入if-gadget后, 在每个可执行程序中都能找到图灵完备的gadgets, 且可实现图灵完备的功能.与之相比, 用传统方法几乎无法在这些程序中找到图灵完备的gadget集合.由此可见, 图灵完备的ROP攻击代码在软件模块中是普遍可实现的.另外, 在Ubuntu等主流系统上, 攻击者可以复用不支持ASLR的可执行程序中的gadgets来构造出纯ROP攻击代码.因此, ROP攻击对现实系统的安全威胁要比原来认为的严重得多.

1 背景知识与动机 1.1 ROP代码复用技术

ROP通过控制栈指针寄存器的值来控制执行流, payload由gadgets的地址以及喂给gadgets的参数组成.图 1给出了一个简单的ROP攻击实例, 它调用system库函数来执行“uname-a”命令, 之后调用exit库函数结束进程.假设攻击者已经利用缓冲区溢出漏洞把payload填充到栈上, 且图中偏移为0的内存单元覆盖了原来的函数返回地址.当原程序中的函数执行ret指令返回时, 将执行ROP代码中的第1个gadget, 即payload的第1个数据0x40067f指向的代码片段“pop %rdi; ret”.该gadget执行完后, 寄存器rdi的值将指向字符串“uname-a”, 同时其末尾的ret指令取出下一个gadget的地址并执行.此时, 代码片段“jmpq *0x200ae2(%rip)”被执行.这个gadget将调用库函数system.该函数的参数是要执行的命令所对应的字符串, 在x86-64架构上, 这个参数存放在寄存器rdi中, 因此, system将执行命令“uname-a”.执行完该命令后, 将执行下一个gadget, 即“pop %rdi; ret”, 之后, ROP代码将调用库函数exit(0x21) 结束进程.

Fig. 1 A ROP instance. The stack grows downwards 图 1 ROP攻击实例.栈向地址空间生长

ROP实现条件跳转的传统方法很复杂.Shacham等人[4]特意为此设计了一个方案, 其核心内容是把条件标志的值传递到栈指针寄存器SP上.他们通过3个步骤来实现该功能:(1) 进行某些操作来设置或清除感兴趣的标志; (2) 把寄存器EFLAGS上的标志传递到一个通用寄存器上, 以便将该标志隔离开来; (3) 使用该标志来产生一个期望的增量(当标志清除时, 增量为0), 再将该增量加到SP上, 这样就可以有条件地修改SP的值.为了证实该方案, Shacham等人在glibc中找到了11个不同的gadgets, 并将它们串接起来以便实现一个简单的条件转移功能.其他工作也采用类似方案实现条件转移功能[7, 8].需要强调的是, 这些工作都是在大型软件模块上实施有效性评估的, 因为这样的模块可以提供足够丰富的gadgets以便于构造功能代码.

串接数十个强相互依赖的gadgets是一个非常有挑战性的工作.通常, 每个gadget除了完成预想的计算外, 还会额外地读写内存或更新寄存器内容.这些额外的操作, 我们称其为副作用.有些副作用并不能简单地克服.如果一个gadget的副作用会冲刷前面的gadgets设置的寄存器值, 那么这些gadgets就不能一起工作.随着需要串接的gadgets数量的增多, 找到可以一起工作的组合将变得非常困难.因此, 有的ROP编译器尝试利用约束求解器来查找兼容的组合[9].即使如此, 目前也没有实用的编译器可以为条件判断逻辑自动生成ROP代码.

1.2 动机

默认配置下, Ubuntu等主流系统中的可执行程序并不支持ASLR.攻击者可以复用这些应用程序中的代码片段构造纯ROP攻击代码.由于很多实用程序和部分服务器程序都属于超级用户, 而且有些程序拥有SUID(set user ID)属性.攻击者在这些进程上实现攻击后, 可以对系统安全造成严重破坏.本工作的主要动机是评估这种攻击的可实现性.该工作将对评价主流系统的安全状态有重要意义(实用程序是系统软件的一部分, 用于分析、配置、优化和维护一个计算机系统.Linux系统的软件源通常也提供一些常用的服务器程序.例如, Ubuntu 14.04的软件源就包含有xinetd和nginx这两个软件.Xinetd是新一代的网络守护进程服务程序, nginx是一个高性能HTTP服务程序, 但两者都不支持ASLR).

实施该评估的有效方法是检测图灵完备ROP功能代码在日常软件, 特别是代码量较小的日常用可执行程序上的可构造性.值得注意的是, 已有好几项工作研究了ROP的图灵完备性[4, 7, 8, 10].但它们的关注点是构造图灵完备的gadget集合, 这只是实现图灵完备功能的前提条件.有些图灵基本功能需要串接多个gadgets才能实现.不能成功消除gadgets之间的副作用, 就不能实现这些功能.但是, 即使如此, 这些工作也只能在代码量大的可执行文件和动态库中才能找到图灵完备的gadgets.因此, 这些工作不能在代码量相对较小的日常软件上给出有效结论.

1.3 图灵完备功能

一种计算机语言能够实现任意计算的前提条件是:它提供了一套图灵完备的指令集.最简单的图灵完备指令集只有一个指令[11], 该指令的定义(位于下面所示左半部分)及伪代码(位于下面所示右半部分)如下.

subleq a, b, c  ;Mem[b]=Mem[b]-Mem[a]

      ;if (Mem[b]≤0) goto c

该指令subleq对b指向的内存单元做减法运算, 减去a指向的内存单元的值.如果运算结果小于等于0, 则跳转到c指向的代码处.但是, 该指令在已知的硬件架构上几乎都不存在, 因此我们要把它转化成一个等价的、实际存在的指令集合.把该指令的原子操作分开, 我们可以得到3个指令.

1.在内存上的减法运算:   Mem[b]=Mem[b]-Mem[a]

  2.与0做比较操作:   Mem[b]≤0

      3.条件跳转:   if (TURE) goto c

第1个指令使用两个内存操作数进行运算, 但是现有的通用架构, 如x86, 并不支持两个内存操作数.为此, 我们需要增加实施内存读写操作的指令.另外, 为了支持输入/输出操作, 我们还需要增加一条指令实现系统调用.这样, 我们还有如下3个指令.

4.加载内存到寄存器:   load Reg, Mem[addr]

5.存储寄存器到内存:   store Reg, Mem[addr]

  6.系统调用指令:   Syscall

这6条指令在所有的架构上几乎都存在, 因此可以作为我们要搜索的最小gadget集合.这6条指令所对应的功能, 就是本工作要用ROP技术实现的图灵基本功能.

2 条件判断指令执行上下文与if-gadget工作原理 2.1 条件判断机器指令执行上下文

程序语言, 如C/C++, 在实现条件判断逻辑时, 先测试条件的真假, 再依据测试结果选择不同的执行分支.对应的机器语言代码则先执行条件测试指令来设置EFLAGS中的标志位, 紧接着执行条件转移机器指令并依据相应标志的状态来修改指令指针的值.x86架构提供了数十个条件转移机器指令, 不同的指令测试不同的标志位或标志位的组合.如JZ测试ZF标志是否为0;JA测试ZF和CF是否同时为0.当测试条件成立时, 执行流跳转到真分支; 否则, 转向(fall-though)到假分支.

表 1给出了一个段用C语言描述的代码以及与其对应的汇编指令序列.机器先执行TEST指令来测试EAX寄存器是否为0, 然后执行JLE指令来判断条件标志位的状态.如果条件成立(即为真), 则跳转到0x80483f9处对EAX赋1后返回; 否则, 自动执行后续指令(fall-through), 做乘法运算后返回.

Table 1 The execution context of a conditional branch instruction 表 1 条件判断指令的执行上下文

在该实例中, 条件判断指令JLE的两个分支执行完后都产生相同的栈偏移, 因此, 两个RET指令都从相同的内存单元中取得控制流的转移目标地址.为此, 以JLE开始的代码片段不能帮助ROP实现条件转移逻辑.在现实中, 更常见的情况是至少有一个分支以直接控制流指令或非法指令结束(ROP攻击会从合法指令的中间开始寻找gadgets, 因此时常会遇到以非法指令结束的情况).在glibc上的统计表明, 在所有的91 536个条件控制流指令中(计数包含从合法指令中间开始的截断指令), 有90 989个指令的至少一个分支是以直接控制流指令或非法指令结束的, 即有超过99.4%的条件判断指令不能帮助ROP实现条件转移逻辑.在剩下的条件转移指令中, 大部分也不能帮助ROP实现条件转移逻辑.例如, 有192个条件判断指令的两个分支都是经典gadgets, 但其中有165个属于表 1所示的情况.但我们发现, 有少量条件转移指令的两个分支可以从不同的内存单元取得下一个gadget的地址(该glibc来源于Ubuntu 14.04 32bit系统, 版本号2.19, 运行在x86硬件架构上.代码量0x1a7a60字节).

表 2给出了一个这样的实例.左栏给出了一段用C语言描述的代码.代码功能很简单, 它只是简单地用函数指针实现函数调用:当函数指针不空时, 调用子函数.

Table 2 A code snippet with an if-gadget 表 2 包含if-gadget的程序片段

右栏给出了对应的汇编代码.在汇编代码中, 条件指令JE(地址0x8048408) 的真分支是指令序列“ADD $0XC, %ESP; RET”; 假分支则是指令序列“CALL *%EAX; ADD $0XC, %ESP; RET”.从ROP攻击的视角来看, 真分支是一个以RET指令结束的经典gadget, 而假分支则是以“CALL *%EAX”结束的经典gadget.因为这两个gadgets可以从不同的栈上内存单元获取下一个gadget的地址, 因此, 以该条件跳转指令开始的代码片段能够帮助ROP实现条件转移逻辑.下一节以表 2中的代码片段为例, 阐述if-gadget的工作原理并给出if-gadget的定义.

2.2 If-Gadget原理与定义

攻击者可以将表 2中以JE开始的代码片段视为一个复合代码片段.在构造payload时, 攻击者可以使用常见的经典gadgets(如“POP *%EAX; RET”)把EAX的值设置成某一个gadget的地址, 这样就能确保条件为真时与条件为假时, 攻击代码能从不同的内存单元取得下一个gadget的地址, 即分别从0x804840f处的RET指令访问的地址和“POP *%EAX”访问的地址中取出下一个gadget的地址.同时, 攻击者可以根据实际情况, 通过加法(ADD)/减法(SUB)/比较(CMP)/测试(TEST)等指令设置EFLAGS寄存器中的ZF标志位, 之后再将控制流转移到如上所述的两个gadgets, 这样就能实现条件转移逻辑.这种构造方法实现了ROP代码的条件转移逻辑, 此即if-gadget的工作原理.我们将这种可以实现条件转移逻辑的复合代码片段称为if-gadget.

具体地, 本工作定义了3种类型的if-gadget.这些类型都是我们的gadget查找系统能够找到的.

If-Gadget类型Ⅰ.

以条件转移机器指令J0开始;

J0的两个分支都是经典gadgets(定义见表 3), 分别为G1G2;

Table 3 The gadgets can be found by our algorithm 表 3 我们的查找算法能够找到的经典gadget类型

G1G2从不同的内存单元取得下一个gadget的地址.

表 2中以0x8048408处指令JE开始的代码片段就是这种类型的if-gadget.其真分支是指令序列“ADD $0XC, %ESP; RET”, 这是一个AddConstantGadget的经典gadget(是ArithConstG的一个子类); 假分支是“CALL *%EAX”, 这是一个RegFuncallG类型的经典gadget.由上述分析可知, 这两个gadget会从不同的内存单元取得下一个gadget的地址, 可以帮助ROP实现条件转移逻辑.定义中的约束非常重要.表 1中以指令JLE开始的代码片段满足前面两个要求, 但不满足后面的约束条件, 即它的两个分支是从同一个内存单元取得下一个gadget的地址, 因此它不是if-gadget.另外, 由该定义可见, if-gadget与传统的经典gadget不同, 它有两个出口.值得注意的是, if-gadget的真分支(发生跳转的分支)的目标地址可能在条件转移机器指令之前, 因此, if-gadget并不一定是一个连续的代码块.

If-Gadget类型Ⅱ.

以条件转移机器指令J0开始;

J0的一个分支为经典gadget G0, 另一个分支是以条件跳转机器指令J1结束的基本块, 该基本块无内存读写操作;

J1的执行条件可以被控制, 且J1的假(pass-through)分支为经典gadget G1;

G0G1从不同的内存单元取得下一个gadget的地址.

事实上, 类型Ⅱ是类型Ⅰ的扩展.在利用这种if-gadget时, 攻击者需要通过控制条件标志位使得J1的假分支被执行, 这样, J1将等效为一条无操作指令(no-operation), 以J1开始的代码块的行为语义与G1等价.根据if-gadget类型Ⅰ的定义, 这种类型的代码片段可以帮助ROP实现条件转移逻辑.另外, 在该定义中, 我们要求以J1结束的基本块无内存读写操作, 该约束的目的是减少到达G1的代码路径可能产生的副作用.攻击者在利用这种if-gadget时需要控制两个条件判断指令的执行.尽管有些复杂, 但相对而言, 这仍然是一个简单的约束求解问题.

If-Gadget类型Ⅲ.

以条件转移机器指令J0开始;

J0的两个分支分别是以条件跳转机器指令J1J2结束的基本块; 两个基本块都无内存读写操作;

J1J2的执行条件可控, J1的假分支为经典gadget G1, J2的假分支为经典gadget G2;

G1G2从不同的内存单元取得下一个gadget的地址.

该类型的if-gadget是前两种类型的扩展.施加的各种约束的目的就是要确保J0的两个分支的行为语义分别与G1G2等价.由于这类if-gadget需要满足的约束更多, 在实际代码中出现得很少.

在类型Ⅱ和类型Ⅲ的if-gadget定义中, 我们要求J1(和J2)的假(false)分支为经典gadget.当我们取消这个要求时, 可能可以得到更多的if-gadgets, 但是这些新增if-gadgets的质量可能会很差, 即不容易被操作.例如, 如果我们反过来, 在定义中仅考虑使用J1的真(true)分支, 即让真分支为经典gadget, 则有可能让J1依赖J0, 构成循环依赖.另外, 如果允许J0J1的路径上存在内存访问操作, 则会让检查约束的过程变得复杂.

需要再次强调的是, 各个定义中的约束是很重要的, 满足约束的代码块才有实际意义.通过对glibc的观察, 我们得到如下结果:(1) 有192个条件判断指令满足定义Ⅰ的前两个要求, 即它们的两个分支都是经典gadgets, 但满足约束的只有27个; (2) 有242个条件判断指令满足类型Ⅱ的前两个要求, 但只有19个指令满足后面的约束; (3) 有94个条件判断指令满足类型Ⅲ的前两个要求, 但仅有4个指令满足后面的约束.

3 图灵基本功能的构造

本节以x86架构为例, 讨论各种图灵基本功能的实现方法.重点讨论引入if-gadget后, gadget查找和调度以及payload的布局.这些方法与技术在其他架构下也是适用的.

3.1 图灵基本功能的构造方案

现代计算机架构都提供丰富的指令集, 远远超过了实现图灵完备功能的要求.这样的指令集合有助于程序用简洁的机器语言实现给定功能.ROP代码的构造也受益于这样的指令集.图灵基本功能中定义的“减法运算”有多种实现.在x86架构上, SUB指令可以直接实现减法操作.如果实际需要的是一个加法操作, 那么可以直接用ADD指令实现, 而不需要用类似“SUB(x, SUB(0, y))”的方式来实现.对其他的算术逻辑运算更是如此.“比较操作”也有多种实现方法.在x86架构上, CMP、TEST、ADD和SUB这些指令都可以修改条件标志位, 因此, 都可以实现与0进行比较的操作.

条件转移功能可以借助if-gadget来实现.典型地, 一个if-gadget与一个常见的经典gadget结合能够实现条件转移逻辑.

加载内存内容到寄存器或者存储寄存器内容到内存, 都可以用MOV指令来实现.因为x86的指令支持一个内存操作数, 因此也可以使用带内存操作数的算术逻辑运算指令把内存内容加载到寄存器中, 或者相反地, 存储寄存器的内容到内存中.

用户态代码可以通过特殊指令(INT, SYSENTER和SYSCALL)实现系统调用.另外, 在系统库(如Linux系统的libc)中存在着丰富的封装函数, 它们也可以实现系统调用功能.因此, ROP代码可以调用库函数来实现系统调用.即使在部署了地址空间布局随机化安全机制的系统上, ROP代码仍然可以采用这种通用的方法实现系统调用[10].我们倾向于用库函数实现系统调用, 因为任何gadget都可将控制流传递给这些函数, 实现系统调用.

3.2 Gadget类型与定义

表 3(其中, M[addr]表示访问addr指向的内存单元.◇b表示二元算术逻辑运算)按行为语义定义了本工作用到的各类经典gadgets(if-gadget不在其中).注意, 在ROP代码执行流下的“语义定义”与正常代码下不同.以NopG为例, 它被定义为“无操作”是指它不改变SP和IP以外的寄存器和内存状态, 其行为与执行RET指令等价.默认情况下, 表中的AddrR、InR和OutR都不包含IP和SP寄存器. ArithG代表了在两个寄存器上进行算术逻辑运算的所有gadget类型.第1.3节定义的“减法运算”和“比较运算”都可以用该类gadget实现.另外, ArithConstG表示寄存器操作数与常数进行算术逻辑运算的gadgets; ArithLoadG表示gadgets完成寄存器操作数与内存操作数进行算术逻辑运算之后, 将结果加载到寄存器中; ArithStoreG则表示将结果存储到内存单元中.

与Q[9]相比, 我们增加了JumpG2、MemJumpG、RegFuncallG和MemFuncallG这4种gadget类型.通常, 以间接CALL和间接JMP指令结束的代码片段会被映射到这几类gadget上.与其他gadget类型不同, 它们允许栈指针在执行后减小一个常数值, 并改写payload上已被使用过的数据; JumpG2虽然不会让栈指针在执行后变小, 但同样会改写payload上的旧数据.在对payload进行布局时, 我们会对这几类gadget进行额外的处理, 以防止不必要的内存改写操作.

3.3 Gadget的查找

每一个gadget除了满足相应的语义定义外, 还要满足一系列的约束条件.我们对每一个gadget有4个属性要求, 这些属性与Q[9]定义的类似.

1) 功能性.每一个gadget都有如表 3定义的功能.每一种gadget的类型都是通过语义分析得到的, 都必须满足定义中的最弱前置条件.

2) 控制保持.每一个gadget都可以将控制传递给下一个gadget.这些gadget以间接CALL、间接JMP或RET指令结束.

3) 已知的副作用.Gadget必须不存在未知的副作用, 包括改写任何不希望改写的内存.

4) 常量栈偏移.Gadget需要栈指针在执行后增加一个常数值, 新增加的4类gadget除外.新增的gadget类型允许栈指针在执行后减小一个常数值.

当查找完经典gadgets之后, 就可以查找if-gadgets.为了查找if-gadgets, 需要预先记录下目标软件中所有条件判断指令的起始地址及其分支的目标地址.另外, 为了查找类型Ⅱ和类型Ⅲ的if-gadgets, 还需要记录下所有以条件判断指令结束的代码块的起始地址.

3.4 Payload的布局

通常, 每个gadget执行过程中, 会从栈上取得参数并获取下一个gadget的地址, 这些参数信息都存储在payload中.Payload布局就是为这些参数进行栈空间分配的过程.

顺序执行的ROP代码, 它们的payload布局过程很简单.只要按照gadget执行的前后顺序, 为每一个gadget的参数和自身地址逐个分配栈空间即可.值得注意的是, 新增加的4种gadget类型在执行后产生负的栈偏移.当它们运行时, 可能会修改payload上的原有数据, 特别是修改指向gadget的指针.例如, RegFunCall类型的代码片段“CALL *%EAX”会重写payload上指向自身的gadget指针.拥有循环功能的ROP代码需要极力避免这种情况的发生.一种有效的方法是在执行这类gadget之前执行一个“RET $n”的代码片段.这个代码片段会分配一块私有“栈帧”给下一个代码片段.对私有栈帧的重写不会破坏payload上的原有数据.在x86架构下, “RET $n”这类代码片段很多.

为了更容易理解该过程, 图 2给出了一个实现条件判断的payload实例.偏移12字节处的payload内容指向一个if-gadget.其假分支需要调用的下一个gadget通过指令“CALL *%EAX”来调用, 为此, 我们使用“POP %EAX; RET”把该gadget的地址预先加载到EAX寄存器中; 其真分支要调用的下一个gadget的地址存放在payload的末尾, 将通过位于0x804840f处的RET指令来调用.由于if-gadget的假分支是一个RegFunCall类型的gadget, 所以我们使用了一个额外的gadget(即“RET $4”)来分配局部栈帧.指令“RET $4”将取出if-gadget的地址0x8048408并将SP值加4, 之后再执行if-gadget.这样, 在if-gadget执行时, 它将拥有一个4字节的私有栈帧(偏移16字节处开始的4个字节); 0x804840a处的“CALL *%EAX”指令对这4个字节的修改不会对原有payload造成破坏.因此, 这段ROP代码可以放在循环语句中反复执行.

Fig. 2 The layout of payload when using if-gadget to implement conditional jump 图 2 使用if-gadget构造条件判断功能所对应的payload布局

3.5 Gadget的调度

Gadget调度将被分类的gadget组合起来, 逐一实现每一个目标功能.我们通过迭代被分类的gadgets来找到实现一个目标需要的gadget序列.例如, 如果要写一个值到内存, 我们可以用两个类型为LoadMemG的gadgets来分别设置目标地址和数值, 再用一个StoreMemG完成写内存操作.我们需要跟踪所有寄存器的状态, 来保证一个在使用中的寄存器不会被后续执行的gadgets更新.

在实现条件判断功能时, 需要考虑条件转化问题.在x86平台下, 条件判断是通过测试EFLAGS寄存器上的标志位实现的.如果当前需要判断的标志并不能用已有的if-gadget直接测试, 则需要将该标志移动到其他标志上.Shacham等人[4]使用的方法是在CF(carry flag)标志位上进行测试.如果需要测试其他标志, 则需要通过按位逻辑运算将那些标志位转移到CF上.

引入if-gadget后, 条件转化的解决方法要比Shacham等人提出的传统方法更简单.通常, 在日常的可执行文件中能够找到好几种if-gadgets, 它们进行不同的条件测试.例如, 在x86架构上运行的Ubuntu 14.04系统用gcc编译生成的HelloWorld程序中能够找到4个if-gadgets, 它们对CF和ZF这两个最常用的标志位进行测试.它们可以直接完成“ > ”“==”和“!=”这3种条件判断; 如果对分支进行反转, 则可以进行“ < ”条件判断; 组合起来, 能实现所有无符号比较操作.所谓的“分支反转”是将gadget的真(假)分支对应到实际代码为假(真)的逻辑.以表 2中使用的if-gadget为例, 如果要进行JNE判断, 只需要让该if-gadget的假分支在执行后跳向攻击代码逻辑为真时所需要执行的代码块即可.最常见的if-gadget以JE或JNE开始.因此, 在最坏情况下, 我们可以通过按位逻辑运算把要比较的条件挪到ZF标志位上, 再运行if-gadget.

4 实现

我们用python语言开发了一个可移植的通用的gadget查找系统.通过软件包PyVex[12], 该系统将机器代码提升为中间语言, 然后再作语义分析和约束求解, 得到经典的gadgets.在处理二进制文件的时候, 该系统也同时收集条件判断指令的信息; 当找完所有经典gadgets之后, 结合这些信息就能找到合法的if-gadgets.图 3是该系统的框图.因为是在中间语言的层次上作分析, 该系统可以在多种硬件架构上的程序中查找gadgets, 包括个人电脑上常见的x86架构和移动终端流行的ARM架构.

Fig. 3 The framework of our gadget searching system 图 3 查找gadget的系统框图

4.1 约束条件

每一类gadget除了符合相应的语义定义外, 还要满足对栈指针的修改约束和对内存的访问约束.总体上来说, 栈指针SP的增量必须对齐到硬件架构的字长(例如, 在x86-32架构上为4字节).默认情况下, 表 3中寄存器AddrR、InR、OutR都不包含IP和SP寄存器, 但对于某些gadget类型, 操作数可以使用SP寄存器.例如“MOV %ESP, %EAX; RET”是一个合法的MoveRegG类型的gadget.这些gadget还需要满足对内存读写访问约束.多数类型的gadgets需要满足的内存访问约束条件是:只能读写旧栈值SPold到新栈值SPnew之间的栈上区域, 只有LoadMemG和StoreMemG等类型的gadgets才可以访问该区域之外的指定内存.

4.2 技术细节

本工作基于pyrop[13]实现了一个gadgets查找系统.与原系统相比, 我们改写了对gadget进行分类的大部分核心代码; 修改了实施约束的代码; 增加了查找if-gadget的代码.与pyrop一样, 我们使用最弱前置条件来对经典gadgets进行分类:gadgets除了满足语义定义外, 还要满足内存访问约束.我们为每一个以间接控制流指令结束的代码片段生成一个可能的gadget集合, 然后具体执行该代码片段若干次.每次具体执行都通过随机数来初始化每个使用到的寄存器和内存读操作, 输出寄存器和内存写用来评估前置条件的真假.在多次具体执行中, 一直存在的gadgets则被视为可复用的gadgets.

任何必需的元数据都被视为gadget属性的一部分, 这些数据已从代码片段中抽取出来.这些元数据包括输入寄存器、输出寄存器、被该gadget冲突的寄存器、下一个gadgets的地址相对于SPold的偏移以及参数信息.这些信息是自动化调度gadget所不可缺少的, 为我们在下一步工作中实现可实用的图灵完备ROP编译器奠定了基础.

为了查找if-gadgets, 我们记录下所有的条件判断机器指令的信息:包括当前指令的起始地址、两个分支的起始地址以及所实施的条件检测.为了找到类型Ⅱ和类型Ⅲ的if-gadgets, 我们还记录下所有可以用条件判断机器指令结束的代码块的起始地址.查找完所有的经典gadgets之后, 我们会结合这些信息来查找if-gadgets.

5 实验 5.1 实验用例与参数设置

为了评估if-gadget在日常软件模块中的存在性, 我们在glibc上进行了评估.同时, 为了在代码量相对较小的可执行程序上进行评估, 我们选取了Coreutils中的10个最常用的应用程序作为基准测试集, 查找if-gadget在每个程序中的数量.这些软件模块都来源于Ubuntu 14.04的32位系统, 运行于x86架构上.另外, 我们在不同的硬件架构(即x86和ARM)和不同的操作系统(即Linux, Windows和MacOS)上, 评估了if-gadget的存在性, 并且观察了if-gadget的可移植性.

我们以Binutils为基准测试集, 评估引入if-gadget后构造图灵完备ROP代码的可行性.为了与传统的方法进行对比, 我们也在该测试集中评估了用Shacham方法构造图灵完备gadget集合的可能性.

5.2 If-Gadget在日常软件模块中的存在性

我们在glibc上统计了if-gadget的数量, 总共找到50个可用的if-gadgets.对攻击者来说, 这个数量是非常丰富的.但是, 该结果并没有让人特别惊讶, 毕竟glibc是一个大型的软件模块, 拥有长度为1 735 264个字节的代码段.为了验证if-gadget在较小的软件模块中的存在性, 我们进行了更多的实验评估.

Coreutils中的应用程序实现了最基本的文件和文本操作, 提供了用户与Shell交互的功能, 这些程序在每一个Linux系统中都会提供.我们以Coreutils中的10个最常用的应用程序为基准测试集, 评估if-gadget在日常二进制可执行程序中的存在性.这些程序包括cat、ls、rm和ping等, 涵盖了广泛的应用功能.同时, 我们也在程序bash中统计了if-gadget的数量.bash是一个命令语言解释器, 是最常用的Shell, 是Linux系统中最重要的程序之一.

表 4列出了在这些程序中if-gadgets的数量.为了展示该数量与代码量的关系, 我们也在表中列出了每个程序的代码量大小.数据表明, 在这些程序中都存在if-gadget, 即使在只有23KB的echo程序中也有4个if-gadgets.在应用程序bash中存在的if-gadget最多, 多达35个.其代码量也相对较大, 有984KB.大体上, if-gadget的数量随着代码量的增大而增多.

Table 4 The number of if-gadgets found in Coreutils programs 表 4 工具集Coreutils的最常用的可执行程序中的if-gadget数量

我们发现, 在每一个可执行程序中都有4个if-gadgets是来自于库函数.在库函数register_tm_clones和deregister_tm_clones中各有2个if-gadgets.由于这两个库函数会静态链接到几乎所有的可执行程序中, 因此, 在Linux系统中, if-gadget的存在性是普遍的.至少, 在进行该实验的硬件架构上(即x86-32) 是如此.

5.3 If-Gadget的存在性与平台的相关性

我们进一步在Windows(Windows 10教育版)和MacOS(OSX EI Capitan 10.11.6) 操作系统下评估了if-gadget的普遍存在性.我们采用的策略是查看静态链接到程序中的运行时代码中是否存在if-gadget.该策略给出的是一个“保守”结果:即如果这些代码中不存在if-gadget, 则不能给出有效结论; 否则, 表示if-gadget普遍存在.我们在这两个操作系统下编译了HelloWorld程序.在Windows下, 用VS 2015编译生成了一个调试版本和一个发行版本.在MacOS下, 用gcc进行编译.这些编译过程都采用默认选项.Windows系统下的调试版本和发行版本各有6个和4个if-gadgets; 而MacOS下则没有.由此, 可以得到这样的结论:在Windows系统下的可执行程序中, if-gadget普遍存在; 在MacOS系统中, 由于静态链接到可执行文件的代码太少, 不能给出有效结论.

为了进一步评估if-gadget的存在性与硬件架构的关系, 我们在x86-32、x86-64和ARMv7下做了进一步测试:观察同一个if-gadgets的存在性是否因硬件架构不同而有所改变.

在该测试中, x86硬件架构上都运行Ubuntu 14.04操作系统; 在ARM上运行Debian Jessie 8操作系统.这两个操作系统都是流行的Linux系统的发行版本, 而且可以配置成使用相同的静态链接库版本.在每个系统中, 都用gcc的默认选项来编译一个HelloWorld应用程序, 并在生成的二进制文件中查找if-gadgets.可以发现, 从静态链接库中引入的if-gadgets有可移植性, 即同一个if-gadget在不同硬件架构上都存在.这是一个很有意思的观察结果, 可以为开发跨平台的ROP代码提供有价值的参考信息.

表 5给出了函数register_tm_clones中的一个if-gadget在不同架构下的出现情况.在x86-32架构下, 该gadget从地址0x80483b0开始, 真分支以RET指令结束, 假分支以CALL *%EDX结束.在x86-64架构下, 该gadget从地址0x4004cc开始, 真分支以RETQ指令结束, 假分支以JMP *%RDX结束.在ARM上的出现非常简单, 该gadget从地址0x10390开始, 真分支以指令bx lr结束, 假分支以bx r3结束.

Table 5 The appearances of the same if-gadget across different CPU architectures 表 5 同一个if-gadget在不同硬件架构上的出现

5.4 图灵完备ROP代码的可构造性

图灵完备ROP代码在大型软件模块中的可构造性已被证实[4, 7, 8, 10].本节主要评估图灵完备ROP代码在代码量较小的可执行程序上的可构造性.如第1节所述, 该评估对评价当前主流系统的安全状态有重要意义.

在Ubuntu 14.04系统下, 我们以Binutils为基准测试集, 评估可执行程序中图灵基本功能的可构造性. Binutils是一组对二进制文件进行操作的工具, 广泛用于二进制文件的编译生成和分析中.我们统计了可以实现第1.3节所述的6种基本功能的gadget的数量, 表 6给出了其中4种功能对应的gadget的出现.由于比较运算通常可以由算术逻辑运算来实现, 因此表中只列出了进行算术运算的gadgets的数量(即第3列“Arith”).由于系统调用几乎可以用任何gadget调用库函数实现, 因此可实现方式与可复用gadget数量相同, 在表中省略了该信息.第4列(“Load”)表示加载内存内容到寄存器; 第5列(“Store”)表示存储寄存器内容到内存; 第6列是if-gadget的数量.由表 6可见, 在每一个可执行程序中, 每一个图灵基本功能都存在若干个实现.特别地, 在代码量仅有约20KB的addr2line, c++filt, elfedit, size, strings等程序中, 都可以实现各种图灵基本功能.因此, 引入if-gadget后, 图灵完备的ROP代码是普遍可构造的.

Table 6 The appearances of gadgets for achieving Turing-complete functionalities in Binutils programs 表 6 在Binutils程序集上构造图灵完备功能

为了与传统构造方法在实现图灵完备的难易程度上进行对比, 我们以Shacham方法为例, 对传统构造方法进行评估.由于实现条件判断逻辑是传统构造方法的难点, 因此, 我们仅以对该功能的实现为例, 作最弱前置条件评估.具体地, 先用ROPgadget[5]在每个程序中查找所有可复用的gadgets, 然后统计Shacham方法中实现条件跳转逻辑所需的各类gadgets的出现.我们侧重于统计Shacham方法中的关键gadgets的信息, 并且总是试图查找所有可能的出现.例如, Shacham实例中用“adc %cl, %cl; ret”这样的gadget来把条件标志位存储到寄存器中.为此, 我们对所有以“adc Reg, Reg”开始的代码片段进行计数, 其中, Reg表示硬件架构提供的所有可能的通用寄存器, 如EAX, AX和AL.

表 6(其中, 左侧给出了4种基本图灵功能所对应的gadget的出现.为了与统方法进行对比, 右侧给出了Shacham方法实现条件转移逻辑功能所需要的关键gadgets的出现次数.NEGReg, ADCReg, NEGMem, ANDRegMem和ADDMemESP分别表示以命令NEGL Reg; ADC Reg, Reg; NEGL [Reg*]; AND Reg1, [Reg2]和ADD [Reg], ESP开始的可复用代码片段.每个命令模板中, Reg表示x86-32硬件架构提供的所有可能的通用寄存器)给出了Shacham方法需要的5个关键gadget类型的出现次数(二进制流相同的代码片段只计数1次).只有在dwp和ld.gold这两个较大(都有大于2MB的代码)的程序中才可以找到较多的gadgets.尽管如此, 在所有程序中都没有找全这些关键gadgets, 所有程序中都缺少对内存操作数取反的gadget(如“NEGL 0x5e(%EAX)”).虽然该操作可用其他方法实现(例如, 可以先把内存内容加载到寄存器, 再用NEGL指令处理, 然后把结果存到内存单元), 但是这会增加需要串接的gadgets的数量, 使得消除副作用的后续处理变得更难.由此可见, 用传统方法在代码量相对较小的软件模块中构造图灵完备功能ROP代码非常困难.

5.5 实验结论

通过这些实验, 我们可以得到如下重要结论.

● 在Linux和Windows操作系统的日常软件中, 包括最简单的Helloworld应用程序中, if-gadget普遍存在.因此, 引入if-gadget后, 将ROP构造中的一个最难问题变成了一个普通的简单问题.

● 有些if-gadget有可移植性, 即含有if-gadget的代码, 在不同硬件架构上的版本都含有该if-gadget.这为构造跨平台的ROP代码提供了可能.

● 在一个软件模块中, if-gadget的数量大体上随着软件代码量的增加而增多.在拥有大于200KB代码量的软件中, 应该存在足够丰富的if-gadget可以帮助ROP代码实现条件转移逻辑.

● 引入if-gadget后, 在日常软件模块中, 特别是代码量相对较小的日常可执行程序中, 可以普遍性地用ROP技术实现图灵完备功能.这是本工作的核心结论, 该结论证实了攻击者可以在可执行程序中构造纯ROP代码进行攻击.因此, ROP攻击对现实系统的威胁比原来认为的严重得多.

6 相关工作 6.1 ROP攻击与防御

自从ROP[4]在2007年提出以来, 相关的攻击和防御技术一直成为研究的热点.ROP攻击可以在众多的硬件架构上进行, 包括x86[4]、ARM[14]和SPARC[15].

另外, 不用以RET指令(或语义等价)结束的代码片段, 而用其他间接控制流指令(特别是JMP指令)结束的代码片段, 也能构造攻击代码[10].在支持脚本语言的软件(如浏览器)上, 攻击者可以动态地查找gadget并构造攻击代码, 此即JIT-ROP攻击[16].该攻击可以有效地绕过地址空间随机化(ASLR)保护机制.由于浏览器是最常用的软件之一, 对这类软件的攻击会造成大范围的危害.因此, 如何保护浏览器等软件防范JIT-ROP攻击受到了大量关注[17-19].Blind-ROP证实[20], 即便是在没有被攻击目标程序的二进制文件的情况下, 仍然可以进行漏洞利用攻击.由此可见, ROP的攻击能力和对防御技术的忍耐性强于其他已有的攻击类型.

已有大量的防御ROP攻击的技术.G-free[21]消除所有非法的间接控制流指令(从合法指令的中间开始的指令), 同时保护合法的控制流指令免于滥用.“Return-less kernel”[22]则试图消除内核代码中所有可能的RET指令; 用一个间接JMP指令来取代RET指令; 所有返回地址都用索引值来替代, JMP指令通过索引值来获得合法的返回地址.这两个工作都能很好地从根源上缓解ROP攻击, 但它们都需要重新编译源代码.

ASLR是缓解ROP攻击的有效方法[3].但是, 在32位系统上, 只有16比特可用于随机化.由于随机化的熵比较低, 暴力攻击在几分钟内就能计算出被攻击模块实际加载的地址[23].目前, 在64位系统上进行攻击也成为可能.随后, 有若干工作提出了细粒度的地址空间随机化, 以提高随机化的熵.ASLP以函数为单位进行随机化[24], IRL在指令级别[25].但是这些方案并不能在程序的每一次执行过程中进行随机化, 为了解决这个问题, Wartell等人[26]构造了一个工具STIR, 该工具在程序的每一次执行前, 对可执行文件的代码块进行随机化.信息泄露是ASLR的一个弱点.当存在一个可以被反复利用的信息泄露漏洞时, 攻击者可以在运行时动态地收集gadgets并构造ROP代码[16].相对应地, 出现了一些防止信息泄露的工作[27-30].另外, 旁信道也是攻击ASLR的一种可行方法[31-33].但是, 在很多Linux系统上, 如Ubuntu, 由于性能和兼容性问题, 可执行文件通常并不支持ASLR[5].因此, 攻击者可以在这些程序中查找gadget进行ROP攻击.

6.2 图灵完备的gadgets

ROP在理论上是图灵完备的.为了证实该结论, Shacham等人[4]在提出ROP概念时, 在libc上构造了图灵完备的gadgets集合.Checkoway等人[7]发现, x86和ARM上存在一些与RET行为等价的指令; 在实现ROP编程时, 可用以这些指令结束的代码片段构造功能代码.JOP[10]证实了可以用其他间接控制流指令(特别是间接JMP指令)结束的代码片段来构造攻击代码; JOP与ROP有同样的表达能力.Microgadgets[8]用短小的代码片段(只有2~3字节)在x86架构上构造图灵完备的gadgets集合.值得注意的是, 它们在实现条件转移逻辑时都将条件标志位转化成一个数值, 再将该数值加到SP上.这些方法都需要使用数十个不同类型的gadgets来实现该基本功能.

6.3 ROP构造工具

目前, 已有好几种工具可以用来查找可复用的代码片段, 并给出一些构造ROP链的建议信息.Q[9]可以在一个二进制文件中为给定功能自动地构造payload, 但Q不开源.pyrop[13]是一个开源的ROP编译工具, 给定目标功能和二进制文件, 可自动构造payload.但是, Q和pyrop都不支持构造图灵完备的ROP代码.ROPC[34]用传统方法来构造ROP代码, 并演示了图灵完备的ROP代码的编译过程, 但它不能在实际的二进制文件上工作.mona.py[35]和ROPgadget[36]的主要功能集中于实现gadget的查找, 会给出一些构造ROP的简单建议信息.

7 总结与结论

返回导向的编程(ROP)被广泛用于当前的软件漏洞利用攻击中.尽管主流操作系统采用了一些有效措施来防范ROP攻击, 但仍然存在一种可能的攻击方式, 即攻击者通过复用不支持ASLR的可执行程序中的gadgets来构造纯ROP攻击代码.

本工作通过更新ROP的构造技术, 证实了这种攻击是普遍可实现的.本工作突破了传统ROP构造思想对条件判断机器指令的认知, 引入了一种新的可复用的代码片段, 称为if-gadget.在Windows和Linux上的实验结果表明, if-gadget在日常软件模块中普遍存在.在Bintuitls程序集上的实验结果表明, 引入if-gadget后, 构造图灵完备功能要比用传统方法容易得多, 在每个程序中都能用ROP技术实现图灵完备功能.

因此, 本工作证实了ROP攻击对现实系统的威胁比原来认为的严重得多.在Ubuntu这样的主流系统中, 一些实用程序和服务器程序都不支持ASLR, 而且部分程序还拥有SUID属性.攻击者可以通过攻击这些程序来获取系统用户权限, 甚至超级用户权限.当攻击成功后, 攻击者可以对系统安全造成严重破坏.

在理论上, 该工作也丰富了对ROP属性的认知.已有工作证实了ROP是图灵完备的, 且可以用短小的gadget来构造图灵完备的gadget集合.本工作则探索了图灵完备ROP代码的可实现性与软件模块大小之间的关系, 证实了ROP图灵完备的普遍可实现性, 特别地, 证实了在代码量相对较小的日常可执行程序中也可以构造出图灵完备的ROP代码.

参考文献
[1]
[2]
Team P.PaX non-executable pages design & implementation.2003.http://pax.grsecurity.net/docs/pageexec.txt
[3]
Team P.PaX address space layout randomization (ASLR).2003.http://pax.grsecurity.net/docs/aslr.txt
[4]
Shacham H.The geometry of innocent flesh on the bone: Return-Into-Libc without function calls (on the x86).In: Proc.of the CCS 2007.ACM, 2007.552-561.http://doi.acm.org/10.1145/1315245.1315313
[5]
Payer M.Too much PIE is bad for performance.2012.http://e-collection.library.ethz.ch/view/eth:5699
[6]
PaX (http://pageexec.virtualave.net).2014.https://grsecurity.net/PaX-presentation
[7]
Checkoway S, Davi L, Dmitrienko A, Sadeghi AR, Shacham H, Winandy M.Return-Oriented programming without returns.In: Proc.of the CCS 2010.ACM Press, 2010.[doi: 10.1145/1866307.1866370]
[8]
Homescu A, Stewart M, Larsen P, Brunthaler S, Franz M.Microgadgets: Size does matter in Turing-complete return-oriented programming.In: Proc.of the WOOT 2012.2012.64-76.https://www.usenix.org/conference/woot12/woot12-final9.pdf
[9]
Schwartz EJ, Avgerinos T, Brumley D.Q: Exploit hardening made easy.In: Proc.of the USENIX SEC 2011.2011.https://www.usenix.org/legacy/events/sec11/tech/full_papers/Schwartz.pdf
[10]
Bletsch T, Jiang X, Freeh VW, Liang Z.Jump-Oriented programming: A new class of code-reuse attack.In: Proc.of the ASIACCS 2011.ACM, 2011.30-40.http://doi.acm.org/10.1145/1966913.1966919
[11]
Nürnberg PJ, Wiil UK, Hicks DL.A grand unified theory for structural computing.In: Hicks DL, ed.Proc.of the Int'l Symp.on Metainformatics.LNCS 3002, Berlin, Heidelberg: Springer-Verlag, 2003.1-16.[doi: 10.1007/978-3-540-24647-3_1]
[12]
Shoshitaishvili Y, Wang R, Hauser C, Kruegel C, Vigna G.Firmalice-Automatic detection of authentication bypass vulnerabilities in binary firmware.In: Proc.of the NDSS 2015.2015.[doi: 10.14722/ndss.2015.23294]
[13]
[14]
Kornau T.Return oriented programming for the ARM architecture.2010.http://www.zynamics.com/downloads/kornau-tim-diplomarbeit-rop.pdf
[15]
Buchanan E, Roemer R, Shacham H, Savage S.When good instructions go bad: Generalizing return-oriented programming to RISC.In: Proc.of the CCS 2008.ACM, 2008.27-38.[doi: 10.1145/1455770.1455776]
[16]
Snow KZ, Monrose F, Davi L, Dmitrienko A, Liebchen C, Sadeghi AR.Just-in-Time code reuse: On the effectiveness of fine-grained address space layout randomization.In: Proc.of the SP 2013.2013.[doi: 10.1109/SP.2013.45]
[17]
Niu B, Tan G. RockJIT: Securing just-in-time compilation using modular control-flow integrity. In: Proc. of the CCS 2014.ACM, 2014: 1317–1328. [doi:10.1145/2660267.2660281]
[18]
Davi L, Liebchen C, Sadeghi AR, Snow KZ, Monrose F.Isomeron: Code randomization resilient to (just-in-time) return-oriented programming.In: Proc.of the NDSS 2015, vol.2015.2015.[doi: 10.14722/ndss.2015.23262]
[19]
Maisuradze G, Backes M, Rossow C.What cannot be read, cannot be leveraged? revisiting assumptions of JIT-ROP defenses.In: Proc.of the USENIX SEC 2016.2016.139-156.https://www.usenix.org/conference/usenixsecurity16/sec16_paper_maisuradze.pdf
[20]
Bittau A, Belay A, Mashtizadeh A, Mazieres D, Boneh D.Hacking blind.In: Proc.of the SP 2014.2014.[doi: 10.1109/SP.2014.22]
[21]
Onarlioglu K, Bilge L, Lanzi A, Balzarotti D, Kirda E.G-Free: Defeating return-oriented programming through gadget-less binaries.In: Proc.of the ACSAC 2010.2010.49-58.[doi: 10.1145/1920261.1920269]
[22]
Li J, Wang Z, Jiang X, Grace M, Bahram S.Defeating return-oriented rootkits with return-less kernels.In: Proc.of the 5th European Conf.on Computer Systems.2010.195-208.[doi: 10.1145/1755913.1755934]
[23]
Shacham H, Page M, Pfaff B, Goh EJ, Modadugu N, Boneh D.On the effectiveness of address-space randomization.In: Proc.of the CCS 2004.ACM, 2004.298-307.[doi: 10.1145/1030083.1030124]
[24]
Kil C, Jun J, Bookholt C, Xu J, Ning P.Address space layout permutation (ASLP): Towards fine-grained randomization of commodity software.In: Proc.of the Computer Security Applications Conf.IEEE Computer Society, 2006.339-348.[doi: 10.1109/ACSAC.2006.9]
[25]
Hiser J, Nguyen-Tuong A, Co M, Hall M, Davidson J.ILR: Where'd my gadgets go.In: Proc.of the SP 2012.2012.571-585.[doi: 10.1109/SP.2012.39]
[26]
Wartell R, Mohan V, Hamlen KW, Lin Z.Binary stirring: Self-Randomizing instruction addresses of legacy x86 binary code.In: Proc.of the CCS 2012.ACM, 2012.157-168.[doi: 10.1145/2382196.2382216]
[27]
Backes M, Nürnberger S.Oxymoron: Making fine-grained memory randomization practical by allowing code sharing.In: Proc.of the USENIX SEC 2014.2014.433-447.https://www.usenix.org/conference/usenixsecurity14/sec14-paper-backes.pdf
[28]
Gionta J, Enck W, Ning P. HideM: Protecting the contents of userspace memory in the face of disclosure vulnerabilities. In: Proc. of the CODASPY 2015.ACM, 2015: 325–336. [doi:10.1145/2699026.2699107]
[29]
Crane S, Liebchen C, Homescu A, Davi L, Larsen P, Sadeghi AR, Brunthaler S, Franz M. Readactor: Practical code randomization resilient to memory disclosure. In: Proc. of the SP 2015, 2015: 763–780. [doi:10.1109/SP.2015.52]
[30]
Tang A, Sethumadhavan S, Stolfo S. Heisenbyte: Thwarting memory disclosure attacks using destructive code reads. In: Proc. of the CCS 2015.ACM, 2015: 256–267. [doi:10.1145/2810103.2813685]
[31]
Hund R, Willems C, Holz T. Practical timing side channel attacks against kernel space ASLR. In: Proc. of the SP 2013.IEEE, 2013: 191–205. [doi:10.1109/SP.2013.23]
[32]
Seibert J, Okhravi H, Söderström E. Information leaks without memory disclosures: Remote side channel attacks on diversified code. In: Proc. of the CCS 2014.ACM, 2014: 54–65. [doi:10.1145/2660267.2660309]
[33]
Evtyushkin D, Ponomarev D, Abu-Ghazaleh N.Jump over ASLR: Attacking branch predictors to bypass ASLR.In: Proc.of the 49th Annual IEEE/ACM Int'l Symp.on Microarchitecture (MICRO).IEEE, 2016.1-13.[doi: 10.1109/MICRO.2016.7783743]
[34]
Pakt.ROPC: A turing complete ROP compiler.2013.https://github.com/pakt/ropc
[35]
[36]