一起读反汇编代码之函数的调用
不知道小白有没有这样的困惑,就是单独一条的汇编语句能读懂,但是好几条组合到一起就感觉云里雾里了那你就适合跟我们一起来读反汇编代码了,把基础指令的反汇编代码读懂才能逆向出一些更难的算法,今天我们一起来读的就是反汇编的基础部分———函数的调用
这里主要讲解的是函数调用的三方面知识:栈帧的形成、调用方式、参数以及EBP/ESP寻址
你可能有时候读反汇编代码的时候遇到一些问题例如:
经常在函数开头看见 push ebp ;mov ebp ,esp ;subesp,xxx这三行代码不知道做什么用的(分号只做分割用)
经常看见一些mov ecx,ss:;mov ecx,ss:这样的赋值语句,却不知道赋的是什么值
经常在call 之后看见add esp,xxxx也不知道有什么用
那你应该能从我的帖子中找到答案
用的IDE是VC++6.0,程序有debug版本的也有release版本的,调试器就是论坛的OD
一.关于栈帧
说到函数的调用,不得不提的就是栈帧,你可能会经常在函数的开头看见这么几行代码:
push ebp
movebp esp
sub espxxxx
这三行代码其实就是用来形成栈帧的
那么这个栈帧到底是什么东西呢,我对它的定义就是:用于存放每次函数调用信息的专属栈空间
包括在被调用函数内部声明的局部变量
上面三行代码执行后,现在栈就变成来了这样
下面来解释下什么叫做栈平衡:
当我们调用一个函数的时候,就会针对这个开辟出其所需要的栈空间(因为要存放局部变量),形成属于这个函数的栈帧,而当调用结束之后,需要清除掉这个栈空间(关闭栈帧),这一过程就叫做栈平衡。
所以为什么局部变量有自己的生命周期就是因为函数执行完之后用于存放局部变量的栈帧被清除掉了,当然还是存在于内存中,只是无法再访问它了
而如果栈不平衡会怎样呢,我们知道调用函数时,需要先将函数所需的参数压入栈中,最后压入的是函数的返回地址,如果栈不平衡,会导致函数无法正确返回,而导致程序的崩溃。
所以!在退出函数时,会进行ESP与EBP的比较,用来检查栈帧是否被正确的关闭。
在函数的末尾处开始检查栈平衡
我们跟到cmp之后的call中看看发生了什么
一个不等于则跳转,手动修改标志位看看会怎样
呃,程序报错了,这就是栈空间溢出的后果
二.函数的调用方式
这里的函数调用方式其实是关于其参数的传递与栈空间的平衡的,看到这可能有人会说了,前面不是已经讲了栈空间的平衡里吗,为什么这里又要说呢?
前面说的栈帧是栈空间的一部分,是用来记录被调用函数的相关信息的,那么函数参数的传递大部分也是通过栈传递的,我们调用完函数后,这些参数也就没有其存在的价值里,我们就应该清除它们
C++中函数的调用方式主要有以下三种:
1._cdecl:是c++中默认的调用方式,所用参数从右到左入栈,这些参数由调用者清除
2._stdcall:入栈方向一致,参数由被调用者清除
3._fastcall:是快速调用方式,一般是前两个参数由寄存器传递,其他参数还是用栈传递,参数由被调用者清除
这里的东西比较简单,其实就是addesp,xxxx 是放到被调用函数内部还是放在调用语句之后的区别
如果你有时在call之后看到addesp,xxxx就知道是消除参数用的了
三.函数的参数以及EBP/ESP寻址
被调用函数执行时需要从栈空间中取出函数参数以及局部变量,现在的ESP作为栈顶指针EBP作为栈底指针,现在是通过哪个寄存器寻址呢,答案是EBP
上面的反汇编代码中我们即将调用一个printf函数,用来输出两个整数
一个整数是定义的局部变量而另一个整数是这个被调用函数的参数,可以看见OD已经帮我们做了分析
为了观察参数与局部变量的寻址方式我们可以先取消勾选“显示函数中的参数及局部变量”
取消勾选后OD就会直接显示出变量的地址
我们发现局部变量与参数的地址对于EBP的偏移量的符号不同,我们知道栈帧是在真正开始调用函数时才开始形成的,而参数却是在栈帧形成前就已经被压入栈中了。
所以偏移量为正数的理应是被调用函数的参数,是负数的自然就是被调用函数的局部变量了。
这里我们想一下如果我们传递了两个参数,而又在函数内部声明了两个局部变量,那么它们对于ESP的偏移又是多少呢?
而和中又存放的是什么值呢?
我最后再来解答
接下来我们说说ESP寻址,你有没有一个问题就是明明有ESP作为栈顶指针可以用来寻址,根本不需要EBP来做个中间量,是为了检查栈帧平衡吗,有时候或许完全不需要EBP,只要我们按规定进行栈平衡,我们也就不需要对栈平衡进行检查
事实上还真是这样,如果我们选择release版本,优化中勾选最快速度,那么我们就不再使用EBP,而是直接使用ESP进行寻址
这时候较小地址处放的应该是局部变量,较大地址处放的是参数,因为压栈时栈顶指针会减小
好了现在说说上面问题的答案了:
中存放的应该是栈帧生成前的EBP的值,因为被调函数的开始有个push ebp
中存放的应该是函数的返回地址,因为调用函数时压入参数之后还会把下一条语句的地址压入栈中
最后再附上源码跟程序:https://www.lanzouj.com/i681dzi
好了,到这里今天的学习就结束了,能力有限,可能会有疏漏之处,欢迎在评论区指出。
感谢各位看官!
如果觉得这篇帖子对你有一点帮助的话,希望能动动小手帮忙给下免费的评分,谢谢大家了!
D:/%E6%9C%89%E9%81%93%E4%BA%91/christy_wjc@163.com/a13e3e238173428092cd46edbf8b7782/qq%E6%8B%BC%E9%9F%B3%E6%88%AA%E5%9B%BE20190917185250.png
D:/%E6%9C%89%E9%81%93%E4%BA%91/christy_wjc@163.com/a13e3e238173428092cd46edbf8b7782/qq%E6%8B%BC%E9%9F%B3%E6%88%AA%E5%9B%BE20190917185250.png 雪魔王遗风 发表于 2019-9-18 19:22
不知道咋回事啊,最后那个图片也是我自己画的
在一堆技术宅里秀恩爱。。。
是极为让人嫉妒的事情。。{:17_1061:}
很显然我不是{:17_1068:}
因为我是大白。。。
(小白又不是,大佬又不算。。很尴尬的位置) 哈哈,非常感谢。
最后帮你转译一下你后面2行是什么鬼东西。
D:/有道云/christy_wjc@163.com/a13e3e238173428092cd46edbf8b7782/qq拼音截图20190917185250.png 呃,最后两行是啥代码啊,把我邮箱漏出来了。。。 今天写的东西可能有些简单了,不过应该也算是基础部分,之后可能会写一些变量在内存中的存储访问啊,类的继承啊,还有虚函数等等的东西吧 收藏了非常好的文章 非常好的文章 非常感谢 讲的很细致,对于初学者十分友好 学习了,谢谢分享 说的很详细 学习了,感谢,感谢