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