【逆向工程】C/C++的反汇编表示详解(1)函数调用,栈平衡,变量与参数的内存布局

很多人学完汇编,去看C/C++的反汇编就会很懵,发现单独看一条指令看的明明白白,但连在多条指令连在一起就不知道有什么作用了,如

push ebp
mov ebp,esp
sub esp,40h
lea edi,[ebp-40h]
mov ecx,10h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]

这里用VC6.0是最好的,更能看出本质,VS系列也可以,VS的话这里要把优化给关掉。在反汇编指令方面会有一定的差别,但其执行的逻辑还是一致的,我这里用的是VC6.0
在这里插入图片描述
下一篇:【逆向工程】C/C++的反汇编表示详解(2) 整数,字符串,分支语句

函数的调用

源代码

一个简单的加法函数

#include <stdio.h>
int Add(int a,int b)
{
   
	return a,b;
}
int main()
{
   
	Add(1,2);
	return 0;
}

堆栈图
在这里插入图片描述

反汇编

开始调用函数

C/C++以及MFC默认使用_cdecl函数调用约定,即调用者负责清理线程栈(外平衡),参数从右往左入栈,寄存器EAX, ECX和EDX被指定在函数中使用,返回值放置在EAX中。WIN32API调用约定为_stdcall,两种调用约定主要区别是_stdcall采用被调用者负责清理线程栈(内平衡),后续内容中会介绍。

/*
main函数中调用Add函数
*/
00401068   push        2 //将参数压栈
0040106A   push        1
0040106C   call        @ILT+0(Add) (00401005) //调用一条JMP指令
00401071   add         esp,8 //函数调用结束后恢复堆栈 使其堆栈平衡
							//后面会说明该命令作用

VS和VC编译器在调用函数时,并不会直接跳转到函数所在地址而是会在函数调用后生成一条JMP指令,用来跳转到函数所在的地址,JMP指令不改变堆栈操作

/*
0040106C   call        @ILT+0(Add) (00401005) 
指令跳转的一条JMP指令
*/
00401005   jmp         Add (00401020)

在这里插入图片描述

开始执行函数

/*
提升堆栈 为函数的执行创造内存
其中提升堆栈之前ESP与提升后ESP的差值所对应的栈空间 即40H叫做缓冲区
用来存放局部变量的值
*/
00401020   push        ebp  //保存栈底 为函数执行后恢复堆栈做准备
00401021   mov         ebp,esp 
00401023   sub         esp,40h //缓冲区的大小
							 //在启用编译器优化的情况下可能会用
							// push reg指令来开辟栈空间

在这里插入图片描述

/*
保存寄存器的值,确保函数执行完毕后,
使后面的程序可以再使用寄存器执行该函数之前的值
*/
00401026   push        ebx
00401027   push        esi
00401028   push        edi
/*
将缓冲区中的内容初始化为CCCCCCCC即int3中断 防止缓冲区溢出
*/
00401029   lea         edi,[ebp-40h] //初始化edi的值 即将堆栈提升后的ESP赋给EDI
0040102C   mov         ecx,10h //重复执行的次数为30H
00401031   mov         eax,0CCCCCCCCh //初始化EAX也就是缓冲区要填充的内容
00401036   rep stos    dword ptr [edi] //将缓冲区全部填充为int3中断 防止缓冲区溢出

在这里插入图片描述

/*
执行return中的内容a+b 并根据cdecl调用约定将返回值保存在EAX中
若返回结果超过EAX寄存器的容量,则其高32位就会放到EDX寄存器中
*/
00401038   mov         eax,dword ptr [ebp+8]    //[ebp+8]即参数a
0040103B   add         eax,dword ptr [ebp+0Ch]	//[ebp+0Ch]即参数b
/*
恢复堆栈,使ESP,EBP回到函数刚开始也就是提升栈空间前的值 
注意这里并不是指堆栈平衡
*/
0040103E   pop         edi //取回保存的寄存器的值
0040103F   pop         esi
00401040   pop         ebx 
00401041   mov         esp,ebp //恢复ESP的值
00401043   pop         ebp //恢复EBP的值
00401044   ret //等于将EIP的值变为0x00401071  函数返回

在这里插入图片描述

函数执行完毕

/*
函数调用结束后恢复堆栈 使其堆栈平衡 回到函数调用之前的堆栈状态
这里属于外平衡 因为_cdecl函数调用约定栈平衡的方式为外平衡 
_stdcall内平衡的话会在返回时调用 ret 8 这个指令
*/
00401071   add         esp,8 //此时堆栈中只剩下参数A和B,
							//因为A,B一共占八个字节所以加8

在这里插入图片描述

为什么需要栈平衡?
在程序运行过程中,栈内空间会被函数重复利用,如果函数调用只申请栈空间而不释放它,那么随着函数调用次数的增加,栈内存会很快就会耗光,程序会因此无法正常运行。平衡栈的操作,目的是保证函数调用会栈顶位置和函数调用前位置一致,这样就可以重复利用栈的内存空间了。
需要注意的是,在X64环境下,某些汇编指令对栈顶的对齐值有要求,因此VS编译器在申请栈内存时,会尽量保证栈地址的对齐值可以被16整除。

这里说一下rep 和 stos指令
rep是重复执行指令,其重复执行次数与ECX寄存器的值有关,等同于

START:
	DEC ECX
	JNZ START

stos是串储存指令,是将EAX中得数据传送到目的地址默认为ES:[EDI]寄存器,等同于

MOV ES:[EDI],EAX  //ES附加段地址寄存器
ADD EDI,4 //或者 SUB EDI,4
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘乙兵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值