1 |
|
初看到这个代码,完全不理解这是个什么意思,学习过函数的栈帧后,就觉得很容易理解了。
其结果为程序运行了surprise函数。
过程其实很简单。 首先main函数调用test函数。 在test函数中,先定义了一个int型变量,然后定义了一个指针p,这个指针指向tmp地址加4的地址,然后把这个地址里的数据改为surprise函数的指针。 其实这个地址存的就是main函数调用test函数之后要返回的指令的地址。
无图无真相,下面是我在vs2017中分析的过程:
首先我们在main处设置断点,以便于接下来的调试。
对于main之前的过程这里不做分析。
接下来按f5进入调试,在代码上右键点击选择转到反汇编。
我们就会看到如下汇编代码:
1 | int main() |
其中有两个指针寄存器 ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。 EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
我们可以看到前三句指令就是为main函数在内存中分配了一个大小为0C0h的栈。 接着将ebx,esi,edi入栈,这里暂且不管这三个寄存器作用。 然后下面到call之前的代码是将之前分配的栈初始化。
call指令调用test函数(02A1384h) ,这里我们先记下return指令的地址:002A17E3
在VS2017中选择 “调试–>窗口–>内存–>内存1 ” 进入内存查看窗口:
运用逐语句及逐过程调试菜单将汇编代码运行至call指令处,过程中在内存窗口中我们可以通过esp所指向的地址来查看相应内存变化。 可以观察到,0x0135FC5C - 0x0135FD24为main函数的栈。
接着按f11(逐语句)执行call命令, 我们会发现在main函数的栈顶压入了一个地址002A17E3, 对,就是刚才main中test()的下一条指令return的地址。
划重点,后面在test函数中就是通过改变这个地址的值为surprise函数的地址, 使程序在test函数执行后跳转至surprise函数的。
逐步执行程序,可以得知如下图所示信息,test()的栈帧是紧接着main()的栈帧的,因为内存中栈是由高地址向低地址扩展的,因此test的栈底紧挨着main的栈顶, 从而可以在test函数中根据tmp的地址增加4来找到main的返回地址,进而改变程序的运行。
在test的汇编中有这么几句,这几句暂时还不清楚有什么作用, 不过这就是为什么在VS2017中要加4而在VC++6.0中要加2的原因。
mov eax,dword ptr [__security_cookie(02AA000h)]
mov eax,ebp
mov dword ptr [ebp-4],eax
到此,关于这个代码的分析就结束了。
当然,日常编码中我觉得没人会这么干。 不过,这个有趣的代码加深了我对栈帧的理解。