初识ROP——返回导向编程

ROP是啥?或许以前曾经在某个地方见过它的出现,不过我可以肯定自己对这个ROP完全不懂。唯一有印象的,就是前几天TX的电话面试上面提到过。既然在面试中被问到,那应该是一种比较受关注的技术。今晚找了两三篇相关的论文,再加上其它博客上的相关资料,算是对ROP有了初步的认识。

在说ROP之前,先来说一下什么是缓冲区溢出漏洞。缓冲区溢出漏洞(buffer overflow)是当今软件系统最主要的安全漏洞,利用缓冲区溢出漏洞的攻击也是攻击者最常用的攻击方式之一。所谓缓冲区溢出,是指在应用程序或者系统程序的内存中写入超长数据,从而达到恶意改变或者控制程序执行流程的目的。攻击者通过缓冲区溢出把shellcode写入程序的内存空间,并且通过覆盖栈上函数的返回地址,即EIP寄存器所指向的内存地址的值,或者覆盖SEH链表来实现程序执行流程的控制,使得植入内存的shellcode得以执行。

栈溢出(stack-based buffer overflows)算是安全界常见的漏洞。一方面因为程序员的疏忽,使用了 strcpy、sprintf 等不安全的函数,增加了栈溢出漏洞的可能。另一方面,因为栈上保存了函数的返回地址等信息,因此如果攻击者能任意覆盖栈上的数据,通常情况下就意味着他能修改程序的执行流程,从而造成更大的破坏。

对于以上的漏洞,人们找出了很多解决的办法。目前最受关注的是W⊕X技术,即通过硬件或软件的支持,来保证进程映像中的内存区域不能同时可执行或可写入。在Windows平台下,称为数据执行保护技术(Data Execution Protection,DEP);而在Linux平台下则是通过PAX项目实现。

在此基础上,出席那了新的攻击技术return-to-libc,攻击者不通过写入shellcode 到漏洞程序的进程空间,而是利用已经在内存空间中的可执行代码来执行任意操作,如libc中有一些函数可以用于执行其他的进程,例如 execve 和 system。攻击者只要找到一个栈溢出漏洞,并适当的构造函数调用参数,并使栈上返回地址指向这些函数的起始地址,攻击者就能以这个程序的权限执行任意其他程序了。(这里有一篇实现攻击的论文:Return_to_libc.pdf)。这种攻击方法也有局限性,就是需要当前代码库有 system 这样符合要求的函数,否则就凉拌了。

于是安全人员又提出了一种新的技术,就是今天需要我去学习的返回导向编程技术(Return-Oriented Programming,ROP)。所谓ROP,简单的说就是把原来已经存在的代码块拼接起来,拼接的方式是通过一个预先准备好的特殊的返回栈,里面包含了各条指令结束后下一条指令的地址。

在一般程序里面,都包含着大量的返回指令(ret),他们基本位于函数的尾部,或是函数中部需要返回的地方。而从函数开始的地方到ret指令之间的这一段序列称为二进制指令代码块(gadgets)。这些二进制指令序列使其组合成完成一些诸如读写内存、算术逻辑运算、控制流程跳转、函数调用等操作。于是,我们就可以通过利用内存空间中各个gadgets以某种顺序执行,达到进行任意操作的目的。而为了使各个gadgets“拼接”起来,我们就需要构造一个特殊的返回栈。首先让指向我们构造的栈(stack)的指针跳到gadget A中,执行其中的代码序列后ret回我们的stack中,然后下一步是跳到gadget B,执行后就到gadgets C……只要stack足够大,就能达到我们想要的效果。

ROP难以构造的地方在于,我们需要在整个内存空间中搜索我们需要的gadgets,这要花费很长的时间。一旦完成“搜索”和“拼接”的步骤,这样的攻击却是难以抵挡的,因为它用到的都是内存中合法的代码。目前,已经有实验室提出了包括一个扫描可利用代码、并把它们结合起来的Constructor,一套专用的语言,以及把这套语言编译成对应代码片段之和的编译器,最后还有一个计算实际代码地址的Loader。(传送门

至于防护的方法,我就找到两篇相关的资料。现在主流的办法分别有:解决栈溢出问题、使用“金丝雀”方法侦测和预防栈溢出、去掉所有的ret指令、增加地址随机性等。在《基于Return-Oriented Programming的程序攻击与防护》这篇文章中讲述以上方法的详细实现。传送门

资料来源: 良性代码,恶意利用:浅谈 Return-Oriented 攻击(一) Windows ROP自动生成技术的研究与应用

« 返回