3、返回VStudio IDE,在调用overflow函数处设置断点,再次选择"Run"菜单项,这时程序在调用overflow前停止。(下面的学习你需要不断地翻看上面的 Asm程序段)现在看一下在调用overflow之前的几个需要注意的参数,把它们加入"Watch"窗口。
esp 0x0012ff34(注意:这些值在不同的机器上运行时可能会不一样)
ebp 0x0012ff80
buf 变量尚未分配
overflow 0x00401020
main 0x00401070
4、按F11跟踪进入overflow,让程序停在6:
现在再看一下几个主要参数:
esp=0x0012ff30,其它未变(指我们watch的几个标识符,这时eip一定是会变化的)很显然堆栈里压了一个dword(4字节)数据,看看它是什么,打开memory窗口,输入esp,右击窗口内容,选"Long Hex Format",当前的堆栈顶内容0x0040108d,现在请看一下call overflow的下一行,如果找不到请从头搜索"15:"字符串,看到了吗!压入的是call overflow的下一指令地址,也就是我们通常说的"函数返回地址".
再按F11(执行push ebp),再看一下几个主要参数
esp=0x0012ff2c,现在堆栈顶中是ebp的值0x0012ff80,
再按F11(执行下面的语句),程序将当前esp值保存在ebp中: mov ebp,esp
然后就开始分配局部变量了
sub esp,4ch;分配了76(0x4c)个字节这个地方我不太清楚为什么始终要保留64(0x40)个字节,其实只有12(0x0c)字节可用,随后的7句指令:
00401026 53 push ebx
00401027 56 push esi
00401028 57 push edi
00401029 8D 7D B4 lea edi,[ebp-4Ch]
0040102C B9 13 00 00 00 mov ecx,13h
00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401036 F3 AB rep stos dword ptr [edi]
将这76个字节以dword(4)为单位填充为0xcccccccc,共填充76/4=19(0x13)次让我们在执行完rep stos dword ptr [edi]时先停下来.在watch窗口里加入eip和一个表达式"ebp-0ch",会发现在"ebp-0ch"和buf的地址一样,这就是编译程序在堆栈中为我们分配的局部内存变量的起始地址(如果你懂编译原理,这里很容易理解),在memory窗口里输入ebp-0ch(变量起始地址),右击窗口选 "Byte Format",可以看到里面有12个字节是被0xcc填充过的.
好!现在跟踪执行完call strcpy,再看看Memory窗口的内容,有11个字节被填充,前10个填充为0x61即ASCII字符'a',后一个字节为0这验证了C字符串操作函数总是产生一个空终止字符。
再往下看,右击选"Long Hex Format"看到它们分别是 0x0012ff80和0x0040108d,什么?有点熟?对啊!我也觉得有点面熟,为什么呢?请回头看一下第4小节的开始部分,找到答案了?对!是" 老的ebp"和"函数返回地址",继续跟踪将执行以下几个动作,恢复主要寄存器内容,add esp 4ch销毁了局部内存变量恢复老的ebp(这时堆栈顶的内容为0x0040108d),再ret返回(其实ret相当于执行了一次"pop eip",但并没有这样的指令)执行完这条指令后eip的内容变为0x0040108d,这时已经回到了主函数中,在主函数中将执行几乎同样的动作,最后完成程序执行。
有人可能会问overflow需要回到main所以用了一个ret,可是main中的ret是做什么用的呢?其实初学者可能并不知道我们的C程序编译后程序的空间结构(简化后的)是这么一个样子的.
----------------------------------
//程序入口点(Program Entry Point)
.
.
.
call _main
push eax
call _ExitProcess
.
----------------------------------
//void overflow(void)
push ebp
.
.
.
call _strcpy
.
.
.
ret
----------------------------------
//int main(void)
push ebp
.
.
.
call _overflow
.
.
.
ret
----------------------------------
overflow中的ret让程序回到main,而main中的ret是为了回到入口点那段程序,以返回操作系统。
小结:
在这一部分里我们学习到了一些为理解缓冲区溢出打基础的东西,如局部内存变量是如何分配的,它于堆栈的关系以及函数调用、函数返回地址与堆栈的关系,把这些东西搞懂了以后我们可以进行一些简单的应用,出于学习原理的目的,接下来我们将用缓冲溢出来实现一个命令控制台窗口(cmd.exe)。
