案例
打开OD,点option,debuging options,选中winmain,打开
找指令
选中一条,鼠标左键点一下,ctrl+G,把地址输入进去,点OK
然后就可以定位了,这时候不能按F8,因为CPU还没有跑到这一行
用鼠标左键点一下,F2下断点,CPU还在程序打开OD那,点播放键到这个断点位
画堆栈
这个时候画堆栈图要关注ESP和EBP
画出初始的堆栈图,如下图所示,黄色代表的是初始堆栈,而ESP和EBP之间还有很多内存地址,这里用一块来表示
按下F8
栈底没有变化,但是栈顶变化了,如图为执行push 2后的堆栈图
再往下F8,执行了push 1,而ESP减4
这个时候不能按下F8,,按下以后程序就执行完了
这个时候,我们按下F7
call指令相当于 mov dword ptr ds:[esp-4],[eip + 当前call指令字节数]
执行后如下:
jmp指令不影响堆栈图,直接按下enter
此时如图
下一个指令,EBP变化,变成ESP
。。。(此处写错了,是EBP=ESP)
下一步
此处,深蓝色的区域为缓冲区,这一块空间是给程序执行内存,缓冲区大小不确定,但是肯定比程序需要多一些,编译器对缓冲区设置不一样
如图为这一步OD实验结果
PUSH ebx、 push ESI、 push EDI,保存现场:执行另一个程序时,把这些寄存器的值保存下来,等那个程序执行完后再拿出来
其他两行也是一样的道理
lea edi,dword ptr ss:[EBP-40],取地址操作,之前ESP也减过40
此时堆栈图无变化
前面有mov ecx,10
mov eax,CCCCCCCC
下一步rep stos dword ptr es:[edi]
将eax的值传给EDI这个地址指向的内存,此时EDI自动加4或者减4,看DF位,D位为0加,D位为1减
重复执行16次,执行一次ecx减1,往EDI里存一个CCCCCCCC,ecx减到0时就停止
如图为执行一次后的堆栈图,此时ECX=F,EDI+4(往下移)
执行完后如图
用CCCCCCCC填满缓冲区,防止缓冲区溢出,未填满前缓冲区不是0,是一堆垃圾数据
如果写递归时,截止条件没有写好会导致栈溢出
mov eax,dword ptr ss:[EBP+8]
此时EBP+4是函数的返回地址,如上图
之前push两个值,就是这个函数的参数,因为这个程序要用到这两个值,这个程序就叫函数
此时EBP+8就用这个参数,不会改变栈底栈顶,也就是把ebp+8的值,就是之前的push 1,即1,放到eax里面
add eax,dword ptr ss:[EBP+C]
即之前的push 2 此处就执行的是1+2,此时,函数的功能已经完成,实际上真正干活的就这两行。
pop edi,pop esi ,pop ebx ,这三个寄存器的值都在栈顶,把他们依次打出来,刚好保存到对应的寄存器:恢复现场,但是这三个数还在内存,所以这就是垃圾数据
mov ebp,esp
此时,叫恢复堆栈
pop ebp
此时ebp就是最开始的那个
RETN:相当于pop,eip
此时eip是返回地址,这个地址是必争之地
回到之前
上图的call就是函数的调用,此时2和1就是参数,此时和调用前的堆栈图进行对比
此时,两边堆栈不一样
堆栈平衡:调用一个函数之前,和调用一个函数之后,它的堆栈应该是一样的,是没有变化的,栈顶指针和栈底指针应该一样
我们看上面的OD图,下面还有一个add esp,8,就是让esp+8
此时平衡,在函数执行完后平衡堆栈,这种堆栈平衡叫外平栈,即谁调用函数,谁平衡堆栈
海哥的总结
堆栈作用
函数
传参数不一定通过堆栈,也可以通过寄存器
返回值我们可以存入寄存器,也可以通过内存,eax值叫做返回值,一个函数可以没有参数也没有返回值