【reverse】逆向7 堆栈图
前言
本章就是开始画堆栈图来打基础拉,堆栈熟悉了之后就可以开始C语言的逆向了。
这一章使用的exe文件,我已经上传到了我的个人网盘中,点击下载
1、准备工作
先看这张内容
我们首先打开OD,使用F3打开helloworld.exe程序
然后在任意位置,ctrl+G打开窗口,输入0x401168地址,然后点击OK,自动跳转到这里
然后在这个地址上打一个断点(F2)
然后我们F9运行,让程序运行到这里
然后就是我们画堆栈图的时候了
2、画堆栈图
我们先在OD上看esp栈顶和ebp栈底的内存地址
根据这个内存地址我们可以画出开始时候的堆栈图(这里的黄色表示中间有一定距离,不是每格4字节)
然后我们运行push 0x2 和 push 0x1的汇编指令
运行之后的堆栈图变成了这样
然后下一步的汇编指令是call 0040100A,是我们刚刚学习的call指令,会让我们eip指向0040100A,也就是跳转到这里,并且将这个00401171地址push进堆栈中
我们这里按F7步入
所以堆栈图如下图
然后我们发现我们进入的地址0040100A的语句是jmp 00401040,也就是跳转到00401040,这里没有堆栈操作,我们直接F8步过
接下来我们jmp到了一段比较长的地方,这里我们逐步分析
首先是push ebp,这个操作让堆栈存入ebp的地址,而此时我们的ebp的地址为0019FF30,所以堆栈图如下
这一步执行完了之后,就是mov ebp,esp
这一步的的作用就是将esp的值传给ebp,让ebp = esp,堆栈图如下
然后是sub esp,0x40
这一步是让esp栈顶上升0x40个单位,也就是16个4字节,也就是在堆栈上升16位,堆栈图如下
然后就是push ebp,esi,edi,将ebp、esi、edi这些寄存器存放的东西放入堆栈中,起到保护现场的作用
堆栈图如下
下一步lea edi,dword ptr ss:[ebp-0x40],这句汇编就是让edi存储ebp-0x40之后的内存地址
当然,lea指令是不会影响堆栈的
这里的ebp-0x40是不是非常熟悉?就在刚才,esp也是减去了0x40
所以这个时候的堆栈图如下,edi是存储了0019FE94这个地址的
然后就是
mov ecx.0x10
mov eax,0xcccccccc
rep stos dword ptr es:[edi]
这三句话其实要一起说明
首先ecx是存储循环次数的寄存器,可以看出来这里循环0x10,也就是16次
然后执行mov eax,0xcccccccc,就是让eax这个寄存器存储0xcccccccc这个值
最关键的rep stos dword ptr es:[edi],就是让edi存储eax的值,每次存完的数都会根据DF标志寄存器来决定存储地址±4,DF=0就是加,DF=1就是减
这上面三句就是重复16次,也就是让堆栈中存储16个缓冲,组成缓冲区
堆栈图如下
然后就是mov eax,dword ptr ss:[ebp+0x8],这句话就是让eax存储ebp+0x8地址中的数据,也就是我们最开始存储的1
add eax,dword ptr ss:[ebp+0xC],这句话就是让eax中原有的数据加上ebp+0xC地址中的数据,也就是我们最开始存储的2,所以eax中是1+2,现在eax=0x3
这一步,堆栈图没有发生改变,但是这两句话却是函数核心,就是两参数相加
int func(int a, int b) {
return a+b;
}
然后就是pop edi,pip esi,pop ebx
这三句就是为了还原现场。
还记得我们在刚刚开始的时候push了这三个寄存器吗,现在我们把他们pop出来,还原到最开始的状态。
堆栈图如下
然后就是mov esp,ebp
这句话就是把ebp的值赋给esp,这样就是esp = ebp了
堆栈图如下
然后就是pop ebp,这句话就是把现在栈顶指向的返回地址pop给ebp
我们其实也记得,刚刚在call执行完,存完了紫色的返回地址之后,我们push了ebp
这个时候也就是让我们的ebp返回到原来的地址上去
堆栈图如下
然后就是函数的最后 retn
retn就相当于指令 pop eip,就是把返回地址给eip,而eip就是当前指向地址,这就是最后的返回地址
堆栈图如下
当我们执行完retn之后,发现我们的esp并没有回到函数开始前的模样,现在还在绿色的位置
而我们要知道,每个程序都保证了堆栈平衡,也就是,函数运行完了之后,esp和ebp的位置要和运行之前是保持一致的。
我们来看看retn完了之后,OD的页面
eip = 00401171
这里的add esp,0x8就是让esp的地址加8
这样我们的esp就回到了原来的位置,实现了堆栈平衡
这种在函数执行完了之后实现的堆栈平衡,叫做外平栈
即——谁调用函数,谁平衡堆栈
堆栈图如下
3、总结
首先我们认清计算机中函数的概念:
计算机的函数,是一个固定的一个程序段,或称其为一个子程序,它在可以实现固定运算功能的同时还带有一入口和一个出口,所谓的入口,就是函数所带的各个参数,我们可以通过这个入口,把函数的参数值代入子程序,然后根据出口返回
汇编中的函数:
来张好看点的
函数的入口
函数的出口
提醒:
-
这里ebp+4是返回地址(在pwn中是要考的!!!)
-
函数虽然有入口和出口,但是入口不一定需要传值,出口也不一定要返回(void)
-
传参不一定是push,还可能是寄存器传参等等…
-
返回值也不一定只通过寄存器返回,也可以通过内存返回
一张windows堆栈的补充图: