C和C++安全编码(原书第2版)
上QQ阅读APP看书,第一时间看更新

2.3.9 返回导向编程

返回导向编程攻击技术与弧注入是类似的,但漏洞利用代码不是返回函数,而是返回跟在return指令后的指令序列。任何这样的可使用的指令序列都称为小工具(gadget)。对于x86架构,一个小工具的图灵完备集合已可以允许用返回导向语言编写任意程序。图灵完备的代码库小工具使用Solaris的libc片段,为构建返回导向漏洞的通用的编程语言和编译器也已经被开发出来[Buchanan 2008]。因此,存在一个假设的返回导向编程漏洞的风险,对其他架构也可能是有效的。

返回导向编程语言,由一组小工具组成。每个小工具指定要放置在栈中的某些值,供代码段中的一个或多个指令序列使用。小工具执行良好定义的操作,如装载、加法或跳转。

返回导向编程由执行所需操作的小工具共同组成。小工具由return指令与栈指针所指的小工具地址来执行。

例如,指令序列


pop %ebx;
ret

形成一个小工具,它可以用于加载一个常数到ebx寄存器,如图2.16所示。

图2.16 用返回导向编程构建的小工具

图2.16的左边显示把常量$0xdeadbeef复制到ebx寄存器所需的x86-32汇编语言的指令,并在右侧显示了等效的小工具。使用指向小工具的栈指针,CPU执行返回指令。由此导致小工具从栈中弹出该常数,并返回执行栈上的下一个小工具。

返回导向编程也支持有条件的和无条件的分支。在返回导向编程中,栈指针代替指令指针来控制执行流。一个无条件跳转需要简单地改变栈指针的值以指向一个新的小工具。这很容易使用如下指令序列来完成:


pop %esp;
ret 

图2.17对无条件分支的x86-32位汇编语言编程和返回导向编程的惯用语法进行了对比。

图2.17 无条件分支在x86-32位汇编语言(左)和返回导向编程的惯用语法

无条件分支可以用于栈上的一个较早的小工具,从而产生一个无限循环。有条件的迭代可以由一个条件分支跳出循环实现。

Hovav Shacham发表的“The Geometry of Innocent Flesh on the Bone”[Shacham 2007]包含返回导向编程的一个更完整的教程。虽然返回导向编程可能看起来很复杂,但这种复杂性可以被编程语言和编译器抽象和隐藏,使得它成为一种可行的编写利用代码的技术。