🎨 颜色标记说明

  • 粉色:特别重要、主线必掌握、优先复盘的内容。
  • 蓝色:核心概念、术语、分类名。
  • 橙色:需要关注的边界、易错点、注意事项。
  • 绿色:解释类内容、帮助理解知识点。

CALL 做了什么

CALL 的核心动作不是单纯跳转,而是:

  1. 保存下一条指令的地址,也就是返回地址。
  2. 把执行流转移到目标函数。

可以近似理解为:

1
2
push 下一条指令地址
jmp 目标函数

识别函数调用时,返回地址是串联调用者与被调用函数的关键证据。

直接调用与间接调用

E8 相对调用

常见的 E8 rel32 使用有符号相对位移:

1
目标地址 = CALL 下一条指令地址 + 有符号 rel32

例如调试器把某条指令显示为:

1
00401000  E8 2B000000  call 00401030

CALL 长度为 5 字节,下一条指令地址是 00401005,再加相对位移得到目标地址。

FF 间接调用

FF /2 形式从寄存器或内存中取得目标地址:

1
2
call eax
call dword ptr [00403000h]

逆向时,这类调用常见于:

  • 函数指针。
  • 导入地址表调用。
  • 虚函数调用。
  • 回调函数和跳转表。

RET 如何返回

RET 从栈顶取出返回地址,并继续执行:

1
pop eip       ; 仅用于理解,不能直接这样编码

ret 0Ch 除了取出返回地址,还会额外调整栈顶:

1
弹出返回地址,再让 ESP 增加 0x0C

这通常意味着被调用函数还负责清理一部分栈参数。

如果栈顶不是正确的返回地址,RET 就会把错误数据当成下一条指令地址。

经典 x86 栈帧

未优化的 32 位函数经常使用下面的入口:

1
2
3
push ebp
mov ebp, esp
sub esp, 20h

含义依次是:

  1. 保存调用者的 EBP
  2. 建立当前函数的固定基准。
  3. 为局部变量预留空间。

对应的退出过程可能是:

1
2
3
mov esp, ebp
pop ebp
ret

也可能直接写成:

1
2
leave
ret

LEAVE 基本等价于 mov esp, ebppop ebp

参数和局部变量定位

对典型 32 位栈帧而言:

地址样式 常见含义
[ebp] 调用者保存的 EBP
[ebp+4] 返回地址
[ebp+8] 第一个栈参数
[ebp+0Ch] 第二个栈参数
[ebp-4] 第一个局部变量或临时空间

因此看到:

1
2
mov eax, [ebp+8]
add eax, [ebp+0Ch]

可以先推测函数接收两个整数参数并返回它们的和,再结合调用点和返回值验证。

MOV、REP 与 STOS 在函数中的作用

这一阶段的视频还会遇到 MOVREPSTOS 等指令,它们经常出现在函数初始化和内存处理中。

1
2
3
4
xor eax, eax
mov ecx, 10h
lea edi, [ebp-40h]
rep stosd

这段代码可能是在把一段局部缓冲区清零:

  • EAX = 0 提供填充值。
  • EDI 指向目标内存。
  • ECX 提供重复次数。
  • REP STOSD 连续写入双字。

逆向时应把连续指令还原成“初始化缓冲区”这样的整体语义,而不是逐条翻译。

嵌套调用与回溯

假设调用关系是:

1
main → A → B → C

每次 CALL 都会留下一个返回地址。传统栈帧还会把上一层 EBP 串成链,因此调试器可以沿栈帧逐层回溯。

1
2
3
4
5
6
7
C 的栈帧
保存的 B.EBP
返回到 B 的地址

B 的栈帧
保存的 A.EBP
返回到 A 的地址

不过,编译器启用优化后可能省略帧指针、内联函数或重排代码。此时不能只靠 EBP/RBP 链判断调用关系,还需要结合符号、栈展开信息和调用点。

栈由谁清理

32 位调用约定中,栈参数可能由调用者或被调用者清理:

1
2
call sub_401000
add esp, 8 ; 调用者清理

或者:

1
ret 8             ; 被调用者清理

这不仅影响栈平衡,也能帮助推断函数的调用约定和参数总宽度。

调试练习

选择一个简单函数:

1
2
3
4
int add(int a, int b)
{
return a + b;
}

在关闭优化的 32 位 Debug 构建中观察:

  1. 调用前参数如何准备。
  2. CALL 执行前后的 ESP 和栈顶。
  3. 函数入口如何建立栈帧。
  4. 参数位于哪个偏移。
  5. 返回值写入哪个寄存器。
  6. RET 后是否回到原调用点。

学习检查清单

检查项 状态
能说明 CALL 保存返回地址和转移控制流的过程 待复盘
能计算 E8 相对调用的目标地址 待复盘
能区分直接调用和间接调用 待复盘
能解释 RET 与 RET n 的差别 待复盘
能画出经典 x86 函数栈帧 待复盘
能从 EBP 正负偏移区分参数和局部变量 待复盘
能在调试器中完成一次函数调用回溯 待复盘