调用约定
在逆向工程中,调用约定(Calling Convention) 是一套关于函数如何“沟通”的协议。它规定了参数如何传递、由谁清理栈空间以及返回值如何处理。
在 32 位(x86)环境下,理解这些约定是你进行栈帧回溯和参数分析的基础。
1. 调用约定的三大核心任务
-
参数传递顺序:是从左往右压栈,还是从右往左?是通过栈传递,还是通过寄存器(如
ECX,EDX)? -
栈平衡负责方:函数返回后,是由调用者(Caller)清理栈上的参数,还是由被调用者(Callee)自己清理?
-
名字修饰(Name Mangling):编译器在目标文件中如何重命名函数(例如加上
@或_)。
2. 常见调用约定详解
以下是你在 32 位逆向中最常遇到的四种约定:
① __cdecl (C Declaration)
这是 C/C++ 程序的默认约定,也是最常见的一种。
-
参数传递:从右向左压入栈中。
-
栈平衡:调用者(Caller)清理。在
CALL指令之后,你会看到类似add esp, 8的指令。 -
特点:支持可变参数(如
printf),因为只有调用者知道到底压入了多少个参数。
② __stdcall (Standard Call)
这是 Windows API(Win32 API)的标准约定。
-
参数传递:从右向左压入栈中。
-
栈平衡:被调用者(Callee)清理。函数结尾通常是
ret 8(表示返回并弹出 8 字节的参数空间)。 -
特点:生成的代码体积比
__cdecl小,因为清理代码只在函数末尾出现一次。
③ __fastcall
顾名思义,为了提高速度。
-
参数传递:前两个参数分别放入
ECX和EDX,剩下的参数再从右往左压栈。 -
栈平衡:被调用者清理。
-
特点:减少了内存访问(栈操作),速度最快。在逆向时,如果你看到函数开头就直接用
ECX或EDX而没看它们被赋值,那通常就是__fastcall。
④ __thiscall
专门用于 C++ 类成员函数。
-
参数传递:参数从右往左压栈,但
this指针 必须存放在ECX中。 -
栈平衡:被调用者清理。
-
特点:在逆向 C++ 程序(如游戏逆向)时,看到
mov ecx, [某个地址]后跟一个CALL,这通常是在调用一个类方法。
3. 综合对比表
| 约定 | 参数顺序 | 栈平衡负责方 | 常用于 | 逆向识别标志 |
|---|---|---|---|---|
__cdecl |
右 -> 左 | 调用者 | 几乎所有 C 程序 | CALL 后有 add esp, XX |
__stdcall |
右 -> 左 | 被调用者 | Windows 系统 API | 函数结尾是 ret XX |
__fastcall |
寄存器 + 栈 | 被调用者 | 性能敏感函数 | 开头使用 ECX/EDX 传参 |
__thiscall |
右 -> 左 | 被调用者 | C++ 类成员函数 | ECX 存放 this 指针 |
4. 逆向中的实战意义
当你正在进行栈帧回溯时:
-
如果是
__cdecl,你可以在CALL指令后通过add esp的数值确定参数个数。 -
如果是
__stdcall,你必须进入函数内部查看最后的ret指令才能确定参数个数。 -
如果是
__fastcall,除了看栈,你还得回溯ECX和EDX在调用前被赋了什么值。