缓冲区溢出分析第02课:缓冲区溢出的原理

来源:互联网 发布:mac暗影格斗2无限钻石 编辑:程序博客网 时间:2024/04/29 03:47

创建含有缓冲区溢出隐患的程序

        这里我们为了达到实验的要求,先来编写一个最简单的存在缓冲区溢出隐患的程序。这个程序我使用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

        可见,程序虽然也能够正确输出,但是却弹出了错误提示对话框。为什么会出现这种情况?我们接下来就来研究一下。

 

判定main函数的地址

        我们首先来研究一下正常的程序(OverrunTest_1.exe)。打开OllyDbg,载入上面所编写的exe程序。由于我们需要从main函数开始分析,但是OD并没有在main函数处停下,而停在了一系列初始化的反汇编代码中,如下图所示:


图3

        这些都是系统自动生成的,与我们的实验无关,我们在此也无需关注这些代码的功能。对于本次实验来说,我们只要找到main函数,从而进一步分析即可。那么应当如何寻找main函数呢?当然我们可以不断地按F8单步执行,通过观察获取,但是这样未免需要一定的经验,而且也比较麻烦。所以这里不妨利用IDA Pro来打开我们的实验程序,如下图所示:


图4

        可见,IDA已经帮我们获取了main函数的入口地址,即0x00401010,那么我们此时可以在OD中,跳到该地址,按F2下一个断点。如下图所示:


图5

 

定位调用main函数的语句

        由上面的截图,我们除了可以知道main函数的位置外,我们还从下面那段话“Jump from 00401005”得知main函数是由位于0x00401005位置处的语句跳过来的。由于缓冲区溢出是与栈空间紧密相关的,所以我们现在应当分析调用(CALL)main函数前后,栈空间的情况,所以这里我们就需要定位究竟是哪条语句调用了main函数。如果仅仅通过OD,我们是比较难定位的,所以这里我还是使用IDA Pro。

        由于已经知道main函数的地址是0x00401010,那么我们在IDA中,用鼠标在该地址点一下,之后利用快捷键“Ctrl+X”打开“交叉引用窗口”,就来到了jmp到此的函数位置:


图6

        然后在0x00401005的地址处,再次利用“交叉引用”功能,我们就能够找到调用main函数的位置了:


图7

        现在就已经知道,是位于0x00401694处的语句调用了main函数,那么我们下一步的工作就是分析该语句执行前后,堆栈的情况。

 

分析CALL语句对于栈的影响

        在OD中,执行到0x00401694位置处,如下图所示:


图8

        可以看到,CALL下面的语句的地址是0x00401699。这个地址之所以重要,是因为我们的程序在进入每一个CALL之前,都会首先将CALL下面那条语句的地址入栈,然后再执行CALL语句。这样当CALL执行完后,程序再将该地址出栈,这样就能够知道下一步应该执行哪条指令。我们一般也将这个地址称为“返回地址”,它告诉程序:“CALL执行完后,请执行这个地址处的语句。”

        我们先看一下当前栈的情况:


图9

        注意栈空间由下至上是高地址往低地址处走的。然后我们按下F7,步入这个CALL,此时再看一下栈空间:


图10

        可见,返回地址0x00401669已经入栈。这就是CALL语句对于栈空间的影响,而这个返回地址在后面的漏洞利用中,其影响至关重要,请大家牢记。

 

分析main函数中的缓冲区情况

        因为我们在源程序中创建了一个8个字节大小的数组,因此进入main函数后的首要工作就是为这个局部变量分配空间。由于我们的程序是以Debug形式编译的,所以它会多分配一些空间(Release版本则会分配正好的空间)。结合本程序可以看到,它为我们分配的局部变量空间大小为0x4C。可以看一下分配完成后我们的栈空间的情况:


图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弹出了错误对话框,提示我们该地址出错,这与我们直接执行程序时所弹出的错误对话框有几分类似。

 

缓冲区溢出漏洞原理总结

        至此,大家应该已经了解了缓冲区溢出漏洞的原理,它就是因为我们输入了过长的字符,而缓冲区本身又没有有效的验证机制,导致过长的字符将返回地址覆盖掉了,当我们的函数需要返回的时候,由于此时的返回地址是一个无效地址,因此导致程序出错。

        那么依据这个原理,假设我们所覆盖的返回地址是一个有效地址,而在该地址处又包含着有效的指令,那么我们的系统就会毫不犹豫地跳到该地址处去执行指令。因此,如果想利用缓冲区溢出的漏洞,我们就可以构造出一个有效地址出来,然后将我们想让计算机执行的代码写入该地址,这样一来,我们就通过程序的漏洞,让计算机执行了我们自己编写的程序。而具体的关于漏洞利用的知识,我会在下一节课中给大家详细讲解。
2 0
原创粉丝点击