《网络渗透技术》学习笔记(1)--Windows平台上一个最简单溢出程序的调试 zz

http://blog.sina.com.cn/s/blog_492101c7010002st.html

例子的C程序是这样的(写简单的C/c++程序我喜欢用CFree,因为这个工具简单易用,并且可以随时补全已定义过的或者C里面自带的标识符,比如变量、函数名等)

//simple_overflow.c
#include
#include

char largebuff[]="1234512345123456====ABCD";

int main(){

char smallbuff[16];
strcpy(smallbuff,largebuff);

}

第一眼看上去,往一个较小的字符数组写入一个较长的字符串,用的又恰恰是strcpy,而strcpy不会进行越界检查,于是肯定会引起问题,的确,此程序一运行就会崩溃。这个问题也就是我们经常听到的“缓冲区溢出”。

如果你装了VC,那么你可以直接用cl编译该程序,而不必为此也去动用你的VC。
cl simple_overflow.c
结果就是生成了simple_overflow.exe(当然还有simple_overflow.obj,这是中间结果,我们不必关心)
ok,运行一下试试,程序崩溃了,我们在程序中没有告诉windows如何处理这种异常,于是windows用自己默认的办法来处理,其结果就是你看到了一个提示你运行程序出错,是否要发送错误的窗口(XP下)。

说到这不得不再扯一扯。今天看了《异形星球》,讲的是地球上的科学家向达尔文四号星球发射了探测器,并且探测器已经相当成功地满足了科学家们的好奇心。其中一个科学家的一段话给我印象深刻,大意是“有人会说,'你们发布这个探测器,有什么用处呢?能给你带来吃的还是喝的?'。地球这个事情目前来看没太多实际意义,但是人是需要有好奇心的,如果没有,就不能称之为人了。”。同样,如果没有搞溢出的前辈当初的那种好奇心,那种钻研精神,我们到今天也只能是习以为常地看着程序崩溃了。是不是说的有点牵强?不管了,转入正题。

下面我们用OllyDbg跟踪一下程序是怎么崩溃的。
OllyDbg打开我们的simple_overflow.exe。CPU窗口当前所停位置我目前还不清楚是什么地方,我只知道我们需要的main函数的汇编代码在CPU窗口的最顶部。于是,我们就在最顶部看到了下面这段汇编代码(OllyDbg给出的main函数对应的汇编代码):

00401000 /$ 55 PUSH EBP
00401001 |. 8BEC MOV EBP,ESP
00401003 |. 83EC 0C SUB ESP,10
00401006 |. 68 30504000 PUSH simple_o.00405030 ; ASCII "1234512345123456====ABCD"
0040100B |. 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-10]
0040100E |. 50 PUSH EAX
0040100F |. E8 0C000000 CALL simple_o.00401020
00401014 |. 83C4 08 ADD ESP,8
00401017 |. 8BE5 MOV ESP,EBP
00401019 |. 5D POP EBP
0040101A \. C3 RETN

其中第一列是代码地址,第二列是代码机器码,第三大列是汇编代码,;后是OllyDbg自动加的注释。这段代码比较简单,下面我们分析一下:


PUSH EBP

;在main函数被调用前夕,主调函数就把main的返回地址(其实也就是old eip)push到栈中。那么进入main后,main所做的第一件事情就是把主调函数的栈底指针保存起来,其实也是存到了栈中。这样的话,old ebp跟old eip是仅挨着的,并且均是4字节,old ebp在old eip之上(这在下面的程序运行时栈组成示例图中可以看出)。


MOV EBP,ESP

;将当前栈顶作为自己的栈顶。因此上面old ebp实际上是保存到了main的主调函数的栈中。下面对栈的操作才是针对main的栈(下称main栈)的。


SUB ESP,10

;main栈扩展领域。windows中栈的生长方向是高址向低址。即如果你看到对ESP的减操作,则说明是在扩展栈,相反,若是加操作,则是收缩栈。同样PUSH和POP分别隐含了对ESP的减和加操作。
;这里我原来就有一个疑惑。一方面,我记得看过的文章中说函数局部变量是分配在栈中的,另一方面我印象中对栈的操作只有PUSH和POP,而此二操作均可自动调整ESP,因此类似的操作到底是怎么回事呢?通过这个例子我想明白了,这里的栈其实跟我们平时数据结构中所看到的栈有所不同,这里的栈更灵活。栈中一开始其实是局部变量的空间,这部分空间过后才属于“真正”栈元素的空间。
;由此我们还可以想到另外一个事情。我们经常可以在汇编程序中看到用[EBP+?]来访问一个函数参数,而用[EBP-?]来访问一个自己的局部变量。为什么呢?对于后者,从我们上面的分析自然可以理解。而一个函数被调用前主调函数实际已经将其参数PUSH到了自己的栈中,而这些参数刚好在被调函数EBP下面不远处。
;具体地说,这里是给smallbuff分配的空间。这里的10是16进制的,因此是10进制的16,也就是说给smallbuff分配了16字节。由于VC编译器是4字节对齐,因此,如果定义smallbuff大小不是16,而是13,那么编译器一样给它分配16字节,而定义smallbuff大小是11,那么编译器会给它分配12字节,也就是说此处就应该是SUB ESP,0C了。

;===以上三条语句也是函数开始处常见的经典组合。


PUSH simple_o.00405030

;通过OllyDbg给出的提示,我们已经清楚,这里是把largebuff(也就是那个全局字符串的地址)作为参数PUSH到栈中


LEA EAX,DWORD PTR SS:[EBP-10]

;看到了吧,这里刚好就是把smallbuff(也就是那个局部字符数组的地址,4个字节)放到了EAX,要干什么呢?往下看


PUSH EAX

;o,又一个PUSH,

;如此一来,不就刚好把strcpy的参数按照从右向左的顺序入栈了嘛,看来是要进行函数调用了。


CALL simple_o.00401020

;的确,紧接着就是一个CALL,不必说,这个CALL肯定就是调用strcpy了
;strcpy会做些什么呢?很简单,它就是把以其第二个参数为始址的一个字符串老老实实地COPY到以其第一个参数为始址的空间。
;其第一个参数不就是刚才我们用SUB ESP,10给分配的空间么?而这个空间下面紧挨着的不就是我们的old ebp,old eip么?这下好了,你应该知道strcpy后发生了什么事情了。
;我们的largebuff共16+4+4=24个字节。前面16个刚好放我们为smallbuff分配的空间中,而接下来的4个=就覆盖了我们的old ebp,再接下来的4个字节ABCD就刚好覆盖了我们的old eip,也就是main函数的返回地址,不多不少。


ADD ESP,8

;我们刚不是为了给strcpy传递参数进行了两个PUSH么?那好,现在strcpy已经完成,参数可以丢掉了。当然此处也可以执行两次POP操作。此操作执行结束后ESP应该是指向smallbuff的。


MOV ESP,EBP

;刚进入函数的时候我们保存了主调函数的栈底,并把主调函数的栈顶做了我们的栈底,现在main函数已经执行完成,该恢复主调函数的栈了。
;EBP的数据是保存在寄存器中的,因此无聊如何溢出也不会被覆盖的,因此执行此语句后,ESP正确指向函数开始处MOV EBP,ESP执行前夕ESP的值。此时刚执行了PUSH EBP,因此MOV ESP,EBP后,ESP也就指向了old ebp。


POP EBP

;既然现在ESP也就指向old ebp,那么我们POP EBP应该是非常正确的,可惜的是old ebp已经被溢出的数据覆盖,它已经面目全非了。


RETN

;从main返回。本条语句实际做了什么呢?它其实就是从当前栈顶弹出自己的返回地址到eip,然后开始从eip中的地址处继续执行。
;而这个返回地址也就是old eip已经被我们的溢出数据覆盖了。就本程序来说,eip的值已经变成了44434241(也就是ABCD的ASCII码倒过来,因为x86是little-endian的,也就是低字节存在地址,高字节存在高址)如果这个地址在本程序的地址空间内程序还不会立即崩溃,但逻辑已经变了。但如果这个地址不在本程序地址空间内,那么当CPU按照新的eip中的地址去取指令的时候必将引发一个内存访问越界异常。

;===以上四条语句也是函数结束处常见的经典组合。

罗嗦了半天,还是画个图清楚一些。

内存低址

|           ...                 |
.
.
+------------------------+ <----  esp
|smallbuff的地址(4B)  |
+------------------------+
|largebuff的地址(4B)  |
+------------------------+
|smallbuff的空间(16B)|
+------------------------+ <----- ebp
|old ebp(4B)             |
+------------------------+
|old eip(4B)              |
+------------------------+
.
.
|           ...                 |


内存高址


我们可以画出更一般的栈结构:

内存走向
------------>
...[local1][local2]...[localn][old ebp][old eip][parm1][parm2]...[parmn] ... 
|              | 
esp            ebp


程序执行中“颠峰时刻”栈的构成情况(不考虑strcpy的执行情况)


你可能感兴趣的:(缓冲区溢出)