C/C++逆向:函数逆向分析-调用约定分析

在进行函数逆向分析时,分析其函数调用约定具有非常重要的作用,因为调用约定直接影响了函数的参数传递、返回值、栈管理、寄存器使用等多个方面,不同的编译器和平台可能有不同的默认调用约定,识别调用约定可以帮助判断代码是由哪种编译器生成的。例如:

①Windows API 函数通常使用 stdcall 调用约定。
②在 x64 平台,Windows 使用的调用约定与 Linux 的 System V 调用约定有所不同。
③通过调用约定,可以更好地识别代码的上下文,甚至推断出目标程序使用的编译器和编译选项。

调用约定描述了函数调用和参数传递的方式,掌握这些约定是正确分析函数的基础,常见的调用约定如下:

cdecl:参数通过栈从右向左传递,调用者负责清理栈。
stdcall:参数通过栈从右向左传递,函数本身负责清理栈。
fastcall:部分参数通过寄存器传递,通常第一个和第二个参数通过 ecx 和 edx 寄存器传递(在windows x86 平台上),剩余参数从右到左压入栈中。 (在 Windows x64 平台上,前四个参数使用 rcx, rdx, r8, r9 寄存器传递。)函数可能负责栈的清理,具体情况与实现相关。

下面是一个 C 代码的示例,通过使用 cdeclstdcallfastcall 三种调用约定来演示它们的区别。在代码中,我们使用 Microsoft 编译器提供的关键字来指定不同的调用约定:

#include <stdio.h>
​
// 使用 cdecl 调用约定
int __cdecl add_cdecl(int a, int b) {
    return a + b;
}
​
// 使用 stdcall 调用约定
int __stdcall add_stdcall(int a, int b) {
    return a + b;
}
​
// 使用 fastcall 调用约定
int __fastcall add_fastcall(int a, int b) {
    return a + b;
}
​
int main() {
    int x = 3;
    int y = 4;
​
    // 调用 cdecl 函数
    int result_cdecl = add_cdecl(x, y);
    printf("cdecl result: %d\n", result_cdecl);
​
    // 调用 stdcall 函数
    int result_stdcall = add_stdcall(x, y);
    printf("stdcall result: %d\n", result_stdcall);
​
    // 调用 fastcall 函数
    int result_fastcall = add_fastcall(x, y);
    printf("fastcall result: %d\n", result_fastcall);
​
    return 0;
}

接下去我们将代码进行编译,生成exe文件后放入x32dbg中进行动态调试,去具体分析这三种调用约定的区别。

在文件载入完毕后,我们需要先进行main函数的定位(定位main函数的方法请看笔者前面C/C++逆向:定位main函数文章);此时我们需要重点关注的是红线以下代码。

mov dword ptr ss:[ebp-8],3
mov dword ptr ss:[ebp-14],4

代码中首先初始化了两个四字节(dword ptr)的局部变量ebp-8ebp-14;这两个局部变量就对应上述C代码中的两个int型变量x(ebp-8)y(ebp-14)

①cdecl调用约定

相关代码如下:

mov eax,dword ptr ss:[ebp-14]
push eax
mov ecx,dword ptr ss:[ebp-8]
push ecx
call function_x86.281050
add esp,8

这串代码首先将局部变量ebp-14(y)的值加载到eax中,接着将 eax 寄存器中的值压入栈中,这是即将调用函数的第一个参数。然后将另一个局部变量ebp-8(x)的值加载到 ecx 寄存器中;将 ecx 寄存器中的值压入栈中,作为调用函数的第二个参数,接着通过call指令调用名为 function_x86.281050 的函数。通过对比上述的函数调用C代码;

int result_cdecl = add_cdecl(x, y);

cdecl调用约定在进行参数传递时,调用函数所使用的参数是从右到左推入栈中,正如这个例子所展现的,我们调用函数add_cdecl(x, y)时,反汇编代码中先压入栈中的参数是ebp-14(y),接着才是x。并且在调用函数完毕后需要由调用者来恢复栈指针;add esp,8调用函数后,恢复栈指针。因为调用之前压入了两个参数,每个 4 字节,共计 8 字节,所以通过 add esp, 8 来调整栈指针,清理掉这些参数。

②stdcall调用约定

相关代码:

mov eax,dword ptr ss:[ebp-14]
push eax
mov ecx,dword ptr ss:[ebp-8]
push ecx
call function_x86.281235

可以看到stdcallcdecl调用约定一样,都是从右往左依次将参数压入栈中,但是与cdecl不一样的是,stdcall的平栈操作是在函数内完成的。这个时候我们进入函数function_x86.281235进行查看:

我们可以看到在函数结尾有一个ret 8指令;该指令在 x86 汇编中表示返回到调用函数的地址,并在返回之前从栈中移除 8 个字节,这是一种同时进行函数返回和栈清理的指令。

③fastcall调用约定

相关代码:

mov edx,dword ptr ss:[ebp-14]
mov ecx,dword ptr ss:[ebp-8]
call function_x86.2810F5

fastcall 调用约定中,部分参数通过寄存器传递,而不是全部通过栈来传递。在该程序的函数调用中,第一个ebp-8(x)和第二个参数ebp-14(y)分别通过 ecxedx 寄存器传递,接着使用call指令调用函数即可。若有更多参数,超出寄存器能够处理的范围,这些额外的参数会被压入栈中;对于通过栈传递的额外参数,栈的清理方式可以与 stdcall 类似,即由被调用者来负责清理栈上的参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值