首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Return-into-libc 攻击及其防御(1)

Return-into-libc 攻击及其防御(1)

前言缓冲区溢出攻击是最常见的利用程序缺陷的攻击方法,并成为了当前重要的安全威胁之一。
在各种安全报告中,缓冲区溢出漏洞始终是其中很重要的一部分。缓冲区溢出攻击很容易被攻击者利用,因为 C 和 C++等语言并没有自动检测缓冲区溢出操作,同时程序编写人员在编写代码时也很难始终检查缓冲区是否可能溢出。利用溢出,攻击者可以将期望数据写入漏洞程序内存中的任意位置,甚至包括控制程序执行流的关键数据(比如函数调用后的返回地址),从而控制程序的执行过程并实施恶意行为。
缓冲区溢出的常用攻击方法是将恶意代码 shellcode 注入到程序中,并用其地址来覆盖程序本身函数调用的返回地址,使得返回时执行此恶意代码而不是原本应该执行的代码。也就是说,这种攻击在实施时通常首先要将恶意代码注入目标漏洞程序中。但是,程序的代码段通常设置为不可写,因此攻击者需要将此攻击代码置于堆栈中。于是为了阻止此种类型的攻击,缓冲区溢出防御机制采用了非执行堆栈技术,这种技术使得堆栈上的恶意代码不可执行。而为了避开这种防御机制,缓冲区溢出又出现了新的变体 return-into-libc 攻击。return-into-libc 的攻击者并不需要栈可以执行,甚至不需要注入新的代码,就可以实现攻击。因此,如果希望编写的程序可以安全运行,就需要知道什么是 Return-into-libc 攻击,它的攻击原理以及可能的防御方式和手段。
数据执行保护策略与 return-into-libc 攻击如前言所述,在缓冲区溢出攻击中攻击者需要将漏洞程序的控制流转移到攻击的代码。例如,攻击者可以通过溢出漏洞程序的缓冲区篡改函数的返回地址以使其指向已放置的恶意代码 shellcode,这样在函数返回时就会跳转到相应的恶意代码来执行。 也就是说,这种攻击方式需要首先将恶意代码注入(写入)目标程序中,并且在以后跳转到并执行此段代码。
针对上述这种攻击行为方式(先写后执行),研究者提出了数据执行保护策略(DEP)来帮助抵抗缓冲区溢出攻击。安全策略可以控制程序对内存的访问方式,即被保护的程序内存可以被约束为只能被写或被执行(W XOR X),而不能先写后执行。目前,这种安全策略已经在系统已经得到广泛的应用 。前言中所述的不可执行堆栈就是该策略的一个特例,即堆栈可写但不可执行。数据执行保护策略虽然对程序运行时的内存访问提供了安全保护,保证内存只能被写或者被执行而不能先写后执行。但是不幸的是,这种保护方式并不是完全有效的,其仍然不能抵御不违反 W  XOR X 保护策略的攻击方式。
Return-into-libc 攻击方式就不具有同时写和执行的行为模式,因为其不需要注入新的恶意代码,取而代之的是重用漏洞程序中已有的函数完成攻击,让漏洞程序跳转到已有的代码序列(比如库函数的代码序列)。攻击者在实施攻击时仍然可以用恶意代码的地址(比如 libc 库中的 system()函数等)来覆盖程序函数调用的返回地址,并传递重新设定好的参数使其能够按攻击者的期望运行。这就是为什么攻击者会采用 return-into-libc 的方式,并使用程序提供的库函数。这种攻击方式在实现攻击的同时,也避开了数据执行保护策略中对攻击代码的注入和执行进行的防护。
Return-into-libc 攻击原理Return-into-libc 攻击可以将漏洞函数返回到内存空间已有的动态库函数中。而为了理解 return-into-libc 攻击,这里首先给出程序函数调用过程中栈帧的结构。
图 1.函数调用时栈帧的结构图 1 给出了一个典型的函数调用时的栈帧结构,该栈从高位地址向低位地址增长。每当一个函数调用另一个函数向低地址方向压栈,而当函数返回时向高地址方向清栈。例如,当 main() 调用 func(arg_1,arg_2,arg_3) 时,首先将所有参数arg_1,arg_2 和 arg_3入栈。图 1 中参数从右向左依次被压入栈中,这是因为 C 语言中函数传参是从右向左压栈的。然后,call 指令会将返回地址压栈,并使执行流转到 func()。返回地址是 call 指令的下一条指令的地址,这个用于告知 func ()函数返回后从 main()函数的哪条指令开始执行。进入 func 函数后,通常需要将 main()函数的栈底指针 ebp 保存到栈中并将当前的栈顶指针 esp 保存在 ebp 中作为 func 的栈底。接下来,func 函数会在栈中为局部变量等分配空间。因此,调用函数 func()时的栈帧结构如图 1 所示。
而当 func()执行完成返回时 leave 指令将 ebp 拷贝到 esp 中清空局部变量在栈中的区域,然后从堆栈中弹出老 ebp 放回 ebp 寄存器使 ebp 恢复为 main()函数的栈底。然后 ret 指令从栈中获取返回地址,返回到 main()函数中继续执行。
攻击者可以利用栈中的内容实施 return-into-libc 攻击。这是因为攻击者能够通过缓冲区溢出改写返回地址为一个库函数的地址,并且将此库函数执行时的参数也重新写入栈中。这样当函数调用时获取的是攻击者设定好的参数值,并且结束后返回时就会返回到库函数而不是 main()。而此库函数实际上就帮助攻击者执行了其恶意行为。更复杂的攻击还可以通过 return-into-libc 的调用链(一系列库函数的连续调用)来完成。
返回列表