逆向 - 函数调用约定

逆向 - C语言调用约定

基础代码如下

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int test(int a, int b, int c, int d, int f, int g) {
int ret = a + b - c - d + f + g;
return ret;
}

int main()
{
int result = test(1, 2, 3, 4, 5, 6);
std::cout << "Result: " << result << std::endl;
}

x86 部分

cdecl 调用约定

  • 参数从右向左入栈

  • 调用者清理栈

1
2
3
4
5
6
7
8
9
004F2726  push        6
004F2728 push 5
004F272A push 4
004F272C push 3
004F272E push 2
004F2730 push 1 // 参数压栈
004F2732 call test (04F1447h) // 调用 test 函数
004F2737 add esp,18h // 清理栈
004F273A mov dword ptr [result],eax // 存储返回值

stdcall 调用约定

  • 参数从右向左入栈

  • 被调用者使用 ret 清理栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
00612726  push        6
00612728 push 5
0061272A push 4
0061272C push 3
0061272E push 2
00612730 push 1 // 参数压栈
00612732 call 0061144C // 调用 test 函数
// 没有清理栈, 调用者清理栈
00612737 mov dword ptr [ebp-8],eax // 存储返回值

>> 0061144C
....
006126E1 ret 18h // 清理栈, 并返回
....

fastcall 调用约定

  • 参数从右向左入栈

  • 第1个参数和第2个参数分别存储在 ECXEDX 寄存器中

  • 被调用者使用 ret 清理栈

1
2
3
4
5
6
7
8
9
10
11
12
13
00D32726  push        6
00D32728 push 5
00D3272A push 4
00D3272C push 3 // 后4个参数压栈
00D3272E mov edx,2
00D32733 mov ecx,1 // 前2个参数寄存器存储
00D32738 call 00D31451
00D3273D mov dword ptr [ebp-8],eax // 存储返回值

>> 00D31451
....
00D326E9 ret 10h // 清理栈, 并返回
....

x64 部分

x64调用 只有 fastcall 一种调用约定

  • 参数从右向左入栈

  • 1~4 个参数分别存储在 RCX, RDX, R8, R9 寄存器中

  • 其他参数压栈

  • 被调用者使用 ret 清理栈

1
2
3
4
5
6
7
8
00007FF67AB1342C  mov         dword ptr [rsp+28h],6
00007FF67AB13434 mov dword ptr [rsp+20h],5 // 多余的参数压栈
00007FF67AB1343C mov r9d,4
00007FF67AB13442 mov r8d,3
00007FF67AB13448 mov edx,2
00007FF67AB1344D mov ecx,1 // 前4个参数寄存器存储
00007FF67AB13452 call 00007FF67AB1142E
00007FF67AB13457 mov dword ptr [rbp+4],eax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
00007FF606B62450  mov         dword ptr [rsp+20h],r9d
00007FF606B62455 mov dword ptr [rsp+18h],r8d
00007FF606B6245A mov dword ptr [rsp+10h],edx
00007FF606B6245E mov dword ptr [rsp+8],ecx // 4个参数放入 Shadow Space
00007FF606B62462 push rbp
00007FF606B62463 push rdi
00007FF606B62464 sub rsp,108h
00007FF606B6246B lea rbp,[rsp+20h]
00007FF606B62470 lea rcx,[00007FF606B7307Bh]
00007FF606B62477 call 00007FF606B6143D
00007FF606B6247C nop
00007FF606B6247D mov eax,dword ptr [rbp+0000000000000108h]
00007FF606B62483 mov ecx,dword ptr [rbp+0000000000000100h]
00007FF606B62489 add ecx,eax
00007FF606B6248B mov eax,ecx
00007FF606B6248D sub eax,dword ptr [rbp+0000000000000110h]
00007FF606B62493 sub eax,dword ptr [rbp+0000000000000118h]
00007FF606B62499 add eax,dword ptr [rbp+0000000000000120h]
00007FF606B6249F add eax,dword ptr [rbp+0000000000000128h]
00007FF606B624A5 mov dword ptr [rbp+4],eax
00007FF606B624A8 mov eax,dword ptr [rbp+4]
00007FF606B624AB lea rsp,[rbp+00000000000000E8h]
00007FF606B624B2 pop rdi
00007FF606B624B3 pop rbp
00007FF606B624B4 ret

在调用前其栈如下

rsp

当前

0

+8

+10

+18

参数6

+20

参数5

+28

在调用call后, RSP上移动, 此时栈如下

rsp

RETADDR

0

+8

+10

+18

+20

参数6

+28

参数5

+30

然后放入参数4, 参数3, 参数2, 参数1

rsp

RETADDR

0

参数1 ecx

+8

参数2 edx

+10

参数3 r8d

+18

参数4 r9d

+20

参数6

+28

参数5

+30

压入rbp和rdi

rsp

old rdi

0

old rbp

+8

RETADDR

+10

参数1 ecx

+18

参数2 edx

+20

参数3 r8d

+28

参数4 r9d

+30

参数6

+38

参数5

+40

分配栈空间

rsp

+20

+18

+10

+8

rbp

+0

…​

old rdi

+E8

old rbp

+F0

RETADDR

+F8

参数1 ecx

+100

参数2 edx

+108

参数3 r8d

+110

参数4 r9d

+118

参数6

+120

参数5

+128

即可对应到栈上的参数

1
2
3
4
5
6
00007FF606B6247D  mov         eax,dword ptr [rbp+0000000000000108h]
00007FF606B62483 mov ecx,dword ptr [rbp+0000000000000100h]
00007FF606B62489 add ecx,eax
00007FF606B6248B mov eax,ecx
00007FF606B6248D sub eax,dword ptr [rbp+0000000000000110h]
00007FF606B62493 sub eax,dword ptr [rbp+0000000000000118h]

在返回清理时, rsp恢复到old rdi位置, 并弹出rdi和rbp, 最后返回

1
2
3
4
00007FF606B624AB  lea         rsp,[rbp+00000000000000E8h]
00007FF606B624B2 pop rdi
00007FF606B624B3 pop rbp
00007FF606B624B4 ret

逆向 - 函数调用约定
https://simonkimi.githubio.io/2026/02/22/逆向-C语言调用约定/
作者
simonkimi
发布于
2026年2月22日
许可协议