这里我们为了达到实验的要求,先来编写一个最简单的存在缓冲区溢出隐患的程序。这个程序我使用VC++6.0进行编写,并在Windows XP下执行。(这里请大家注意的是,如果你使用的是新版本的VC,由于微软加入了GS机制来防止缓冲区溢出情况的出现,那么本实验就无法实现。)
首先新建一个Win32控制台应用程序,然后输入以下C语言代码:
#include "stdio.h" #include "string.h" char name[] = "jiangye"; int main() { char buffer[8]; strcpy(buffer, name); printf("%s\n",buffer); getchar(); return 0; }编译生成Debug版,运行结果如下:
图1
可见程序已经得到了正确的执行与输出。但是我在程序中所创建出来的是一个8字节长度的数组,而我在程序中的输入是7个字节。如果我的输入超过八个字节会怎么样呢?不妨试一下。
这次再次运行程序,尝试输入“jiangyejiangye”,运行结果如下:
图2
可见,程序虽然也能够正确输出,但是却弹出了错误提示对话框。为什么会出现这种情况?我们接下来就来研究一下。
图3
这些都是系统自动生成的,与我们的实验无关,我们在此也无需关注这些代码的功能。对于本次实验来说,我们只要找到main函数,从而进一步分析即可。那么应当如何寻找main函数呢?当然我们可以不断地按F8单步执行,通过观察获取,但是这样未免需要一定的经验,而且也比较麻烦。所以这里不妨利用IDA Pro来打开我们的实验程序,如下图所示:
图4
可见,IDA已经帮我们获取了main函数的入口地址,即0x00401010,那么我们此时可以在OD中,跳到该地址,按F2下一个断点。如下图所示:
图5
由上面的截图,我们除了可以知道main函数的位置外,我们还从下面那段话“Jump from 00401005”得知main函数是由位于0x00401005位置处的语句跳过来的。由于缓冲区溢出是与栈空间紧密相关的,所以我们现在应当分析调用(CALL)main函数前后,栈空间的情况,所以这里我们就需要定位究竟是哪条语句调用了main函数。如果仅仅通过OD,我们是比较难定位的,所以这里我还是使用IDA Pro。
由于已经知道main函数的地址是0x00401010,那么我们在IDA中,用鼠标在该地址点一下,之后利用快捷键“Ctrl+X”打开“交叉引用窗口”,就来到了jmp到此的函数位置:
图6
然后在0x00401005的地址处,再次利用“交叉引用”功能,我们就能够找到调用main函数的位置了:
图7
现在就已经知道,是位于0x00401694处的语句调用了main函数,那么我们下一步的工作就是分析该语句执行前后,堆栈的情况。
图8
可以看到,CALL下面的语句的地址是0x00401699。这个地址之所以重要,是因为我们的程序在进入每一个CALL之前,都会首先将CALL下面那条语句的地址入栈,然后再执行CALL语句。这样当CALL执行完后,程序再将该地址出栈,这样就能够知道下一步应该执行哪条指令。我们一般也将这个地址称为“返回地址”,它告诉程序:“CALL执行完后,请执行这个地址处的语句。”
我们先看一下当前栈的情况:
图9
注意栈空间由下至上是高地址往低地址处走的。然后我们按下F7,步入这个CALL,此时再看一下栈空间:
图10
可见,返回地址0x00401669已经入栈。这就是CALL语句对于栈空间的影响,而这个返回地址在后面的漏洞利用中,其影响至关重要,请大家牢记。
图11
在上图中,比较重要的是最后两行。其中最后一行在之前已经讲过了,是非常重要的返回地址,它决定了当main函数执行完毕后,程序所要执行的语句的地址,而倒数第二行是父函数的EBP,关于这个,我们知道即可,再往上,就是我们的main函数的局部变量空间。这里大家可能会有疑惑,既然是分配给我们的空间,那么为什么还会有其它的数据呢?关于这个大家不要急,当我们执行完0x0040DA36的语句后,再看一下这段栈的空间:
图12
可见这段空间都被0xCC填充了。程序为了容错性与保持自身的健壮性,于是利用0xCC,即int 3断点来填充满这段区域,这样一来,如果有未知的程序跳到这片区域,就不会出现崩溃的情况,而是直接断下来了。当然,这个问题与我们的缓冲区溢出没什么关系,大家知道即可。
然后继续执行,查找反汇编代码strcpy函数的位置,先来看一看正常情况下,执行这个函数前后,堆栈的情况:
图13
这里可以看到,strcpy的第二个参数,就是所接收的字符串所保存的地址位置,其保存位置为0x0012FF78。接下来看看当“jiangye”这段字符串拷贝到这段区域时,栈中的情况:
图14
对比上一张图可以发现,栈中的地址0x0012FF20位置处,保存的是strcpy第二个参数的地址,OD帮我们解析出了,其内容为“jiangye”。而在栈中地址为0x0012FF78处,则是我们真实的保存“jiangye”这段字符串的内存空间。这并没有什么问题,程序能够获得正常执行。那么如果我将strcpy的第一个参数改写为“jiangyejiangye”会如何呢?利用OD打开OverrunTest_2.exe,来到同样的位置,如下图所示:
图15
可以发现,由于我们所输入的字符串过长,使得原本位于栈中0x0012FF80处的父函数EBP以及原本位于栈中0x0012FF84处的返回地址全都被改写了。这里我们主要关注位于0x0012FF84处的返回地址,原来它所保存的值为0x00401669,也就告诉了程序,在执行完main函数后,需要执行该地址处的指令。可是现在那个栈中的内容被破坏了,变成了0x00006579,即当main函数执行完毕后,程序会跳到地址为0x00006579处继续执行。那么会发生什么问题呢?我们不妨继续执行看看:
图16
到这里,main函数需要返回,可以看到它要返回到0x00006579的地址处,来执行该地址处的指令,我们再单步运行一下:
图17
此时我们发现了两件事,一件是OD中的反汇编代码窗口是空的,说明0x00006579地址处不存在指令,或者说它就是一个无效地址。第二件事是OD弹出了错误对话框,提示我们该地址出错,这与我们直接执行程序时所弹出的错误对话框有几分类似。
至此,大家应该已经了解了缓冲区溢出漏洞的原理,它就是因为我们输入了过长的字符,而缓冲区本身又没有有效的验证机制,导致过长的字符将返回地址覆盖掉了,当我们的函数需要返回的时候,由于此时的返回地址是一个无效地址,因此导致程序出错。
那么依据这个原理,假设我们所覆盖的返回地址是一个有效地址,而在该地址处又包含着有效的指令,那么我们的系统就会毫不犹豫地跳到该地址处去执行指令。因此,如果想利用缓冲区溢出的漏洞,我们就可以构造出一个有效地址出来,然后将我们想让计算机执行的代码写入该地址,这样一来,我们就通过程序的漏洞,让计算机执行了我们自己编写的程序。而具体的关于漏洞利用的知识,我会在下一节课中给大家详细讲解。原文地址:http://blog.csdn.net/ioio_jy/article/details/45419127