谈谈著名的缓冲区溢出

谈谈著名的缓冲区溢出

缓冲区溢出也是大家耳熟能详的一种软件漏洞了,我们经常会看到“某某软件系统存在缓冲区溢出漏洞,攻击者可能依据此漏洞获得系统权限,请尽快下载安装最新版本……”这样的新闻。

其实,缓冲区溢出的基本原理并不复杂。缓冲区就是操作系统为函数执行专门划分出的一段内存,包括栈(自动变量)、堆(动态内存)和静态数据区(全局或静态)。其中缓冲区溢出发生在栈里,栈存放了函数的参数、返回地址、EBP(EBP是当前函数的存取指针,即存储或者读取数时的指针基地址,可以看成一个标准的函数起始代码)和局部变量。结构如下:

谈谈著名的缓冲区溢出

当函数中对局部变量的赋值超过了为其分配的存储空间,超出的部分就会覆盖栈里其他部分的数据,也就是发生了缓冲区溢出。如下图所示:

谈谈著名的缓冲区溢出

在上图中,原来函数的返回地址数据被其他数据覆盖了,函数无法找到正确的返回地址,就会发生分段错误(Segmentation fault)。举个简单的代码例子:

#include "stdafx.h"

#include <string.h>

char name[] = "This is a buffer overflow test.";

int _tmain(int argc, _TCHAR* argv[])

{

char buffer[8];

strcpy(buffer, name);

printf("%s\n",buffer);

return 0;

}

代码里只给buffer数组分配了8个字节的长度,但拷贝到buffer里的name长度超过了8个字节,执行这段代码,就会发生缓冲区溢出。虽然name的值输出了,但程序结束时会崩溃。

谈谈著名的缓冲区溢出

如果用VS调试,会直接提示发生了缓冲区溢出的错误。

谈谈著名的缓冲区溢出

如果缓冲区溢出只是导致程序执行出错,那么看起来危害还不是那么大。但如果溢出到返回地址的数据是另一段函数或代码的入口地址,那么这段函数或代码就会被执行,这就是缓冲区溢出漏洞真正的危害所在。如下图。

谈谈著名的缓冲区溢出

缓冲区溢出攻击就是利用了上述原理,通过精心构造溢出数据,将函数返回地址修改为其他函数或代码的地址,从而达到运行的目的。而且被攻击的程序如果有管理员权限,那么新指向的函数或代码也会继承其权限,这就是缓冲区溢出攻击容易获得系统最高权限的原因。在实际应用中,攻击者一般将是返回地址指向一个shell程序,这样通过shell就可以执行任意的操作了。

但实际上攻击者是没办法确定新指向函数或代码地址的,因为操作系统每次加载程序到进程空间的位置都是无法预测的,栈的位置实际是不固定的,因此通过直接修改函数返回地址的方法是不可行。攻击者需要其他方法来确保程序能执行到正确的地址,这里面最常用的就是借助跳板。大体方法是这样:通过构造溢出数据,将栈里的函数参数修改成要执行的程序代码(如shell),返回地址修改为系统特殊的指令jmpesp的地址,而jmpesp就是跳转到栈寄存器。被攻击程序执行后,首先将执行jmpesp命令,而jmpesp命令会使程序跳转回esp所在位置,而这时esp位置就是函数参数位置(具体原因请参阅堆栈及寄存器相关知识),而函数参数已经被shell代码所覆盖,这样shell就被执行了。如下图:

谈谈著名的缓冲区溢出

缓冲区溢出的根本原因是运行的程序的代码和数据在计算机内存中是一样的,系统无法分别两者,这就使通过数据修改代码成为可能。而缓冲区溢出的直接原因是程序中没有仔细检查用户输入的参数的值,造成了数据的溢出。因此,对于别人的系统和程序,防范缓冲区溢出的方法就是勤打补丁和升级,堵塞漏洞。而对于自己开发的系统,有编译器的边界检查、指针完整性检查等方法。但我认为最好的方法是在代码层次实施完善的参数及数据检查,以及良好的编写规范(凸显了程序代码编写规范上一篇的重要性),这些都可以有效地防范缓冲区溢出攻击。