
实验环境
操作系统:Windows XP SP2
编译器:Visual C++ 6.0
编译选项:默认编译选项
Build版本:debug版本
栈溢出原理
栈溢出是一种缓冲区溢出。函数的局部变量通常保存在栈上,如果这些保存函数局部变量的缓冲区发生溢出,就是栈溢出。最经典的栈溢出利用方式是覆盖函数的返回地址,以达到劫持程序控制流的目的。
X86架构中一般使用指令call调用一个函数,并使用指令ret返回。CPU在执行call指令时,会先将当前call指令的下一条指令的地址入栈,再跳转到被调用函数。当被调用函数需要返回时,只需要执行ret指令,CPU会执行出栈操作,将栈顶的地址赋值给EIP寄存器。此时存入的地址叫做返回地址,是用来告诉被调用函数自己应该返回到调用函数什么位置的地址。理想情况下,取出的地址就是之前调用call存入的地址,这样程序就可以返回到父函数继续执行了。编译器会试图保证即便子函数使用了栈并修改了栈顶的位置,也会在子函数返回父函数前将栈顶恢复到刚进入子函数时的状态,从而确保取到的返回地址不会出错。
栈溢出实验
进行实验需要先编写一个存在栈溢出漏洞的程序,其程序源码如下:
运行结果如下:
此程序为一个简单的密码验证的程序,它读取password.txt中的数据传入verify_password(char* password)进行密码验证。此函数存在栈溢出的点为它调用了strcpy(buffer,password),将password复制到buffer数组中,但是没有检查password边界导致buffer数组溢出。
通过OLLYDBG调试到strcpy(buffer,password)返回。观察到的栈如下图
下图为当前函数栈的布局图:
如果我们通过填充buffer[],将函数的返回地址覆盖成buffer[]的首地址,那么当函数返回并调用RETN时,EIP就会被设置为buffer[]的首地址,从而改变程序的执行流程。
Payload内容如下图:
当password.txt文件内的payload被程序执行strcpy()填充进buffer[]后,栈如下图所示:
当执行RETN指令后,CPU会到0012FAF0去执行代码,从而改变了程序的运行流程,转而去执行我们填充的payload。Payload执行完如下图:
此次实验尝试了简单的静态定位返回地址的方法,存在一定的局限性,还有一些可以动态定位payload的方式,例如利用“跳板”——jmp esp去动态的定位payload。
作者
李国浩
华清信安
精彩回顾
1
2
3


