2011 年第1 期
栈溢出攻击原理及编写ShellCode
几个月前笔者曾经为一个网站写过一个服务器端程序,程序的大体作用是监听一个端
口,客户端通过 socket 向服务器端发送数据报,服务器端收到数据报后为每一个客户端启
动一个线程并处理客户端请求。当时笔者还没有接触到缓冲区溢出的概念,并不知道溢出为
何物,后来学习了溢出方面的知识才发现那个程序里面存在一个严重的栈溢出漏洞。于是把
那个程序加以修改,写一篇栈溢出方面的文章分享一下自己的学习经验。缓冲区溢出方面的
文章黑防以前的几期上也有,但很少讲到如何去写自己的 ShellCode,这篇文章将分为以下
两个部分,一是栈溢出攻击的原理和思路,二是如何去写自己的 ShellCode。文章所附带的
源代码用VC6.0 编译通过,另外要用到反汇编工具OllyDBG,PE 文件分析工具ExeScope ,
这些工具可以去网上下载。
应用程序运行原理
这一节里将会讲一些 C 语言代码映射到二进制代码的细节,这些知识是理解栈溢出的
基础。应用程序的内存被划分为三个部分:代码区,数据区,堆栈区。程序中定义的全局变
量以及字符串编译后运行时被放在了数据区中,局部变量放在了栈中,代码放在代码段中,
这三个部分在内存中是独立存放的。变量存取的操作就被映射为对这些地址的及对堆栈的操
作。先看下面一个很简单的C++代码: 处
#include void main()
线
#include {
出
void test(char *q) char *p = "This is test!";
防
{ 明
char buf[20]; test(p);
客
strcpy(buf , q); 注 return;
黑return; }
} 请
程序在被windows 加载时首先初始化数据区的内容,也就是在数据区中为字符串”This is
test!” 申请空间并赋值。然后做一些进入main 函数之前的预处理工作,包括初始化异常处理
载
函数的地址等。做完这些才进入main 函数执行自己的代码。第一句代码包含了以下内容:
在栈中为变量p 申请一个4 个字节的内存(windowsXP 中地址为32 位,也就是4 个字节,
所以任何一种类型的指针都占4 个字节),并把字符串”This is test!” 的地址赋值到这块内存
转
中。第二句调用函数时首先把变量p 的值,也就是字符串”This is test!” 的地址压入到栈中,
然后把test(p)下一句代码的地址(函数执行完后的返回地址)压入栈中,然后转到test 函数
开始执行。进入test 后变量q 中存放的是数据区中字符串”This is test!” 的地址,test 函数的
第一句话为在栈中申请一个20 个字节的空间,第二句为把数据区中的字符串复制到栈中。
复制完后会从栈中弹出刚才压到栈里的返回地址,并转到该地址继续执行主函数。
程序识别一个字符串时是以字符’\0’为结束标志,也就是说虽然字符串”This is test!”只有
13 个字符,但它其实占了14 个字节,’!’后面有一个空字符’\0’。当用strcpy 复制字符串时,
它并不去检查buf 这个缓冲区有几个字节,而是当遇到源字符串的结尾标志时才结束复制。
假如这个程序中的buf 数组共有 10 个元素,当进行复制时