首页
社区
课程
招聘
[原创]从反汇编的角度学C/C++之函数
发表于: 2021-9-29 21:29 9390

[原创]从反汇编的角度学C/C++之函数

2021-9-29 21:29
9390

    函数作为编程中重要的组成部分,我们需要频繁使用函数来完成编码。而在程序运行过程中,为了保存我们的在函数中使用的临时数据,计算机使用了后进先出被称为栈的数据结构来保存这些临时数据。为了维持栈的平衡以及使用这些临时数据,我们需要用到两个非常重要的寄存器,分别是esp和ebp。它们分别指向了栈的顶部和栈的底部,由于栈的增长是从高地址到低地址增长的,所以栈顶的地址小于栈底的地址,也就是说esp<=ebp。而对于入栈和出栈的操作分别是push和pop,指向push就会把数据压入栈中,pop就会把数据从栈中取出来。

    那么它在内存中的表现形式究竟如何?下面先通过一个简单的函数实例来看看在内存中函数的运行机制是如何的。

    这段代码非常简单,函数add只是实现了一个加法操作,最后将返回值给了z。我们从调用函数,也就是z=add(x,y)开始看看函数是如何使用栈帧。

    在函数开始调用之前也就是运行到0x00C0109D之前,我们的esp等于0x006FF788,ebp等于0x006FF878。

    调用函数的时候,首先会从右往左依次将所需要用到的参数push到栈里面,当程序运行到0x00C010A5的时候,也就是调用函数之前,我的电脑中esp等于0x006FF780,ebp等于0x006FF878此时栈中的内容如下图所示。

                                            

    可以看出此时参数已经被压入栈中,进一步跟进函数,我们就来到了函数的首地址,0x00C01000,而在运行第一条指令之前,此时我们的esp再次改变,变成0x006FF77C,地址减少了4,我们再次查看栈中数据,如下图

                                            

    可以看出此时栈中多了一个数据,值为0x00C010AA,而这个数据其实就是我们调用函数的地址的下一条指令的地址,也就是上面call add的后面一条指令add esp, 8的地址,由此可以得出结论,函数调用的时候先是从右往左压入需要用到的参数,然后调用call指令的时候,先是把下一条指令的地址压入栈中,在跳转到需要运行的函数地址。此时我们函数的反汇编结果如下:

    可以看到,当进入一个函数得时候,首先是指向push ebp操作将原来得ebp压入栈中,然后在把esp赋值给ebp,也就是把ebp移上来。当程序运行到sub esp,0xCC得时候,此时得esp==ebp==0x006FF778。此时在从内存中查看这个地址中得内容如下:

                                            

    也就是说这个时候ebp地址中保存得数据是上一次得ebp得地址,而ebp+4地址中保存得就是返回地址,ebp+8保存的就是第一个参数,依次往后类推。

    随后在减少esp的值,此时esp到ebp之间这段内存就是这个函数的栈空间,随后程序保存了三个寄存器的值为了之后恢复并且对这段栈空间初始化为0xCC,当程序运行到0x00C0101E的时候,此时esp==0x006FF6A0,ebp==0x006FF778。此时在查看栈中内容如下:

                                    

    此时这些0xcc就是我们这个函数的栈空间,用来存储我们的局部变量。

    随后我们对ebp-8这个地址赋值为0,也就是局部变量z保存在这个地址,由于此时ebp==0x006FF778,所以为了拿到传入的参数,我们需要从ebp+8和ebp+0xC这两个地址中拿。当程序运行到0x00C01038的时候,查看ebp-8的内容,也就是z的值,可以看到此时等于7,在把这个值赋值给eax作为返回地址。

                          

    随后执行了3个pop操作把栈顶部保存的三个寄存器的值恢复回去,在对esp增加0x0CC大小的值,在把ebp的值赋给esp,此时esp与ebp的值再次相等,等于0x006FF778,也就是开辟栈空间之前的值。此时的栈空间再次如图

                       

    ebp中保存的就是上一个ebp的地址,为了成功恢复之前函数的栈空间,我们首先需要恢复ebp,所以我们执行pop ebp,把保存的ebp的值恢复到ebp中。执行完pop ebp之后,就恢复到了esp等于0x006FF77C,ebp就等于0x006FF878的状态,也就是说此时栈中的数据再一次变成这样

                          

    此时esp地址中保存的数据就是我们前面调用函数的地址的下一条指令的地址,随后执行ret,就会把0x00C010AA放入eip中,程序就会转到0x00C010AA执行,如下图

    此时esp的值等于0x006FF780,栈中数据重新恢复为

                      

    这是保存的两个值就是传入的两个参数,而此时这两个参数已经不需要使用了,所以随后就执行add esp,8。此时esp与ebp的值真正恢复到了函数调用前的状态。而此时eax保存着函数的返回值,根据程序的代码,我们需要把eax赋值给z。

    至此,整个函数从调用到返回的分析结束,整个调用过程可用下面的图来表示。

    随后我们将执行push ebp和mov ebp, esp操作来进行移动

    接下来我们sub esp, 0xCC来开辟新函数的栈空间并保存了三个寄存器的值,随后在这个栈空间中进行操作。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2021-10-20 11:20 被1900编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//