0207、Windows 逆向入门:堆栈、CALL/RET 与函数逆向
这一部分围绕“函数怎么被调用、参数怎么传、返回地址怎么保存”。理解堆栈之后,调试器里的调用栈、返回地址、局部变量、参数、异常回溯就都能串起来。
1、内容范围
| 小节 | 内容 | 复盘点 |
|---|---|---|
| 06.9 | 线程栈结构 | 栈顶、栈底、增长方向和线程私有栈 |
| 07.0 - 07.3 | PUSH/POP/PUSHAD/POPAD/PUSHFD/POPFD | 保存数据、寄存器和标志位 |
| 07.4 | CALL 与 RET | 返回地址、函数入口、返回流程 |
| 07.5 | MOV | 数据移动和参数/局部变量访问 |
| 07.6 | REP 与 STOS | 字符串/批量内存操作 |
| 07.7 - 07.9 | 函数调用堆栈图 | 栈帧、嵌套调用、回溯 |
| 08.5 - 08.9 | 参数、返回值和调用约定 | 参数位置、返回寄存器、还原代码 |
2、线程栈结构
每个线程都有自己的栈。栈常用于保存函数参数、返回地址、局部变量和保存现场。
1 | 高地址 |
x86/x64 下栈通常向低地址增长。
3、PUSH 与 POP
push 把数据压入栈,pop 从栈顶取出数据。
| 指令 | 作用 |
|---|---|
push reg |
保存寄存器值 |
push imm |
压入立即数 |
pop reg |
从栈顶恢复到寄存器 |
pushfd/popfd |
保存/恢复标志寄存器 |
调试时观察 ESP/RSP 的变化,可以理解栈操作。
4、CALL 与 RET
call 做两件事:把返回地址压栈,然后跳到目标函数。ret 从栈顶取回返回地址,跳回调用点。
1 | call target |
函数返回异常时,优先检查返回地址、栈平衡和调用约定。
5、函数栈帧
典型函数开头可能出现:
1 | push ebp |
含义:
| 指令 | 作用 |
|---|---|
push ebp |
保存调用者栈帧 |
mov ebp, esp |
建立当前函数栈帧 |
sub esp, xx |
为局部变量留空间 |
现代编译器可能优化掉固定栈帧,所以不要死背形式,要理解目的。
6、REP/STOS 与批量操作
rep stos 常用于批量填充内存,例如初始化局部变量、清空缓冲区。
| 指令 | 作用 |
|---|---|
stosb/stosd |
把 AL/EAX 写入 EDI 指向位置 |
rep |
按 ECX/RCX 次数重复 |
movsb/movsd |
从源地址拷贝到目的地址 |
遇到大量内存初始化时,反汇编里可能看到这些指令。
7、调用约定与参数
| 约定 | 参数 | 栈清理 |
|---|---|---|
cdecl |
多数经栈 | 调用者 |
stdcall |
多数经栈 | 被调用者 |
fastcall |
部分寄存器 | 约定决定 |
| x64 | 前几个参数经寄存器 | 调用者维护 |
逆向函数时,参数位置和清栈方式非常关键。
8、返回值
常见返回值位置:
| 类型 | 常见位置 |
|---|---|
| 整数/指针 | EAX/RAX |
| 较大结构体 | 可能通过隐藏指针返回 |
| 浮点数 | FPU 或 XMM 寄存器 |
9、函数回溯
函数回溯要把三件事对上:
- 当前
EIP/RIP在哪个函数。 - 当前栈帧的返回地址在哪里。
- 调用者传入了哪些参数。
10、学习检查清单
| 检查项 | 状态 |
|---|---|
能画出一次 call/ret 的栈变化 |
待复盘 |
| 能解释返回地址为什么保存在栈里 | 待复盘 |
能根据 [ebp+8]、[ebp-4] 区分参数和局部变量 |
待复盘 |
| 能说明调用约定不匹配为什么会崩溃 | 待复盘 |
| 能在调试器里做一次函数回溯 | 待复盘 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ruiqy~!











