汇编语言里调用函数的过程和堆栈平衡问题
1、汇编实例:简单函数调用时堆栈的变化过程
我这里写了一个简单的C语言程序,调用了ADD()子函数,返回两个参数的和。
我们用OD调试它的exe文件,来查看调用函数前后堆栈的变化过程。
1.调用函数前:传入参数
我这里是直接找到main函数入口
我下断点的地方(地址:0x40DABE)就是子函数ADD();
- 函数调用前首先压入参数。
原C语言代码中ADD函数有两个参数a=1和b=2;
按f8执行,一直到函数入口
可以看到OD中地址:0x40DAA8开始到调用函数有以下代码
所以,调用函数前的PUSH函数(此程序为PUSH EAX,PUSH ECX),即为子函数传入参数。
提一下汇编的CALL函数(个人认为),比如这个程序中调用ADD()函数的CALL函数。
0040DABE CALL 草稿.0040100A
0040DAC3 ADD ESP,0x8
这里的CAll函数可以看为
PUSH 0x0040DAC3//将函数返回地址压栈
MOV EIP,0x0040100A//改变EIP指针的值
调用函数前的
栈顶指针ESP:0x0019FED0
栈底指针EBP:0x0019FF30
2.调用函数时
按f7步入函数,
这里是JMP,就继续f8
此时
栈底指针ESP:0x0019FECC
栈底指针EBP:0x0019FF30
1.开辟空间(缓存区)
开辟了长度为0x40的堆栈空间,就是缓存区,同时ESP、EBP跟着变化。
值得注意的是,连栈底指针EBP也移动了,而且栈底指针[EBP+0x4]的堆栈位置存储的实际就是函数返回地址
此时
栈底指针ESP:0x0019FE88
栈底指针EBP:0x0019FEC8
2.保存现场
将这几个寄存器的值压入堆栈,防止函数调用时将原本的参数遗漏。
此时
栈底指针ESP:0x0019FE7C
栈底指针EBP:0x0019FEC8
3.覆盖缓存区
这一步的作用是覆盖缓存区,防止缓存区溢出,因为开辟的空间里有历史数据,避免这些数据对程序造成不必要的影响。
此时
栈底指针ESP:0x0019FE7C
栈底指针EBP:0x0019FEC8
4.执行函数的功能
可以发现,它在调用参数时,并没有用EAX和ECX(在函数调入口处EAX和ECX里也存放1,2),而是运用原本压入栈的数据。
将函数返回值3,存储在EAX中。
在这个程序中,子函数的功能代码就只有这两行。
此时
栈底指针ESP:0x0019FE7C
栈底指针EBP:0x0019FEC8
5.恢复现场
就是将原本寄存器压入栈的值返回给原本寄存器,保留上一个函数的数据。
此时
栈底指针ESP:0x0019FE88
栈底指针EBP:0x0019FEC8
6.恢复空间
此时
栈底指针ESP:0019FECC
栈底指针EBP:0019FF30
7.函数结尾:RETN
实际上,RETN这个语句的意义是
POP EIP
将栈中之前存入的函数返回地址弹到EIP中,实现返回函数的目的
按f8执行
函数结束后
栈顶指针ESP:0x0019FED0
栈底指针EBP:0x0019FF30
3.总结
可以反先,调用函数前后,堆栈空间是不变的,ESP和EBP都复原了,而这个现象叫做堆栈平衡
但调用函数时产生的数据并没有管理,这些数据就是历史数据,也称垃圾数据。
2、堆栈平衡
汇编语言里,调用函数的时候会开辟新的堆栈,调用完后返回时必须要恢复堆栈,即调用函数前后,ESP和ESB不变。
如果前后发生了变化,造成堆栈不平衡,对程序危害非常大。
3、IDA堆栈不平衡问题:positive sp value has been found
造成这个问题的原因就是堆栈不平衡
我们先找到出问题的地址0x411A04,也就是警告里提示的。
IDA里按g键并输入地址
他又会弹出警告,但这是你点OK,他会自动切换到改地址对应汇编代码段。
这时,我们需要打开IDA里的Stack pointer(栈指针)选项,来查看他的堆栈情况,检测哪里不平衡。
记住是打上勾。。。
之后,就可以看到堆栈情况。
所以,我有就要向上寻找,看哪里恢复空间时,是不是ADD ESP,立即数
多了。导致堆栈不平衡。
网上看 正好看到ADD ESP,0CCh
,
所以,我们只需让ESP少加4,就正好对应最后面的-4。
(为什么是负数-4,因为开辟栈空间的时候是SUB ESP,立即数
,堆栈由高到低开辟,而恢复的时候,是ADD ESP,立即数
,。。。根据IDA里栈指针的情况,开辟栈空间的时候,栈指针变为正数,而恢复空间的时候,变为了负数,说明ADD ESP,立即数
的时候,这个立即数
大了)
具体的操作是点击ADD ESP,0CCh
,按ALT+K。(0CCh-4=0C8h)
我们在回到伪代码的子函数入口处,双击即可打开子函数。