Calling Conventions For Reverse Engineering

什么是函数调用约定

函数调用约定(Calling Convention)是不同的编程语言或系统平台为函数调用所约定的规范。

主要决定了函数调用过程中参数如何传递、返回值如何传递、寄存器如何使用和保护,以及栈帧的管理方式

  1. prologue

prologue指的是函数开始部分的代码,用于准备函数的执行环境,设置栈空间和保存调用环境

  • 保存ebp:保存调用者的ebp,以便在epilog中可以恢复。

  • 设置栈指针:将栈指针调整为当前函数的栈框架,为局部变量预留空间。

  • 分配栈空间:为函数的局部变量和栈上参数分配所需空间。

push ebp
mov ebp, esp
sub esp, <stack_space>

在这个例子中:

  • push ebp:将调用者的基址寄存器ebp压入栈,以便在函数返回时恢复。
  • mov ebp, esp:将栈指针esp复制到基址寄存器ebp,创建一个新的栈帧基址。
  • sub esp, <stack_space>:从栈指针中减去一段空间,留给函数的局部变量。
  1. epilogue

epilogue通常指的是函数的结束部分代码,负责清理栈空间和恢复调用之前的状态。

  • 释放栈空间:释放函数使用的栈空间。

  • 恢复寄存器:将prolog中保存的寄存器恢复到调用之前的状态。

  • 返回:通过指令如ret返回到调用该函数的地址。

mov esp, ebp
pop ebp
ret
  • mov esp, ebp将栈指针恢复到函数调用前的状态

  • pop ebp从栈中恢复基址寄存器的原始值,

  • ret返回到调用者位置。

  1. 调用方(Caller)和被调用方(Callee)
  • 调用方(Caller):是指发起函数调用的那一方。调用方负责将参数传递给函数、执行调用指令,并在函数返回后继续执行剩余代码。
  • 被调用方(Callee):是指被调用的函数本身。被调用方负责根据传入的参数执行操作,并返回计算结果给调用方。

例如:

int add(int a, int b) {
return a + b;
}

int main() {
int result = add(5, 10); // main是调用方,add是被调用方
return 0;
}

main函数是调用方,因为它调用了add函数。

add函数是被调用方,因为它是被main调用的函数。

初学

all

cdel

cdecl(C Declaration)是C语言默认的函数调用约定,函数参数从右到左入栈,调用方负责清理栈,参数全部储存在栈上,返回值通过寄存器传递。

简单c语言加法,

int add(int a, int b) {
return a + b;
}

函数调用

; 调用 add(5, 10)

push 10 ; 将第二个参数 b=10 入栈
push 5 ; 将第一个参数 a=5 入栈
call add ; 调用 add 函数
add esp, 8 ; 调用方清理栈,释放两个参数的空间(4字节*2)

函数内部

add:
mov eax, [esp+4] ; 将第一个参数 a 加载到 eax 中
add eax, [esp+8] ; 将第二个参数 b 加到 eax 中
ret ; 返回,返回值在 eax 中

stdcall

stdcall的传参方式类似于cdel,都是从右到左,且参数都·储存在栈中。但与cdecl不同的是,在stdcall中,被调用函数负责栈的清理。

int __stdcall add(int a, int b) {
return a + b;
}

函数调用

; 调用 add(5, 10)

push 10 ; 将第二个参数 b=10 入栈
push 5 ; 将第一个参数 a=5 入栈
call add ; 调用 add 函数
; 在stdcall中,由被调用函数清理栈,不需要调用方调整ESP

函数内部

add:
mov eax, [esp+4] ; 将第一个参数 a 加载到 eax 中
add eax, [esp+8] ; 将第二个参数 b 加到 eax 中
ret 8 ; 返回并清理栈,将栈指针ESP增加8字节,释放两个参数的空间

fastcall

函数参数从左到右入栈,头两个参数储存在ecx和edx上,其余的按照从右到左的方式储存在栈上,调用方清理栈。

int __fastcall add(int a, int b,int c,int d) {
return a + b;
}

函数调用

; 调用方代码
mov ecx, 1 ; 将 a = 1 放入 ECX
mov edx, 2 ; 将 b = 2 放入 EDX
push 4 ; 将 d = 4 压入栈
push 3 ; 将 c = 3 压入栈
call add ; 调用函数

函数内部

; add 函数内部代码
add:
mov eax, ecx ; 将 a 的值 (ECX) 复制到 EAX
add eax, edx ; 将 b 的值 (EDX) 加到 EAX 中
ret ; 返回,EAX 存储返回值

清理栈

; 调用方代码继续
add esp, 8 ; 清理栈上参数 c 和 d 占用的空间

msfastcall

类似于fastcall,但可以多储存俩个参数到寄存器内,共计4个参数,一次放在rcx,rdx,r8,r9