函数战争:内存领地的争夺与撤退

  • 开辟战场(序言 Prologue)

  • 有序撤退(结语 Epilogue)

  • 实战避坑指南(我的错题本)

引言

逆向工程的本质,是在废墟中重建秩序。

我们面对的二进制代码,往往丢失了所有的变量名和注释,就像一座被炸毁的城市。而 '函数栈帧' ,就是这座城市地下的地基与骨架。如果不理解它是如何被 创建(Create)销毁(Destroy) 的,我们就永远只能在代码的表层游荡。

我将这一章的学习称为 '函数战争',因为每一次堆栈的开辟,都是程序在向操作系统索取资源;而每一次完美的平衡,都是为了确保程序能活着回到起点。这篇笔记,是我试图解构这一微观世界秩序的尝试。"

1. 开辟战场(序言 Prologue)

1.1 立下军旗 (保存旧世界)

  • push ebp:把上一层函数的基址存起来。

  • mov ebp, esp:插旗! 从现在开始,EBP 就是我的地盘锚点,雷打不动。

1.2 挖掘战壕 (分配空间)

  • sub esp, 40h:往低地址扩地盘。心得:这里的 40h 是编译器算好的,对应 64 字节。

1.3 请保镖 (保存寄存器)

  • push ebx -> push esi -> push edi

  • 注意: 标准 MSVC 习惯把 EDI 放在最上面(最后压入)。

1.4 埋雷/装修 (Debug填充)

  • lea edi, [ebp-40h] + rep stosd (填入 0xCC)

  • 顿悟: 那些"烫烫烫"不是乱码,是防止野指针踩踏的"地雷"。

2. 有序撤退

2.1 送客 (LIFO 原则)

  • 核心逻辑: 怎么穿衣服就怎么脱衣服。

  • pop edi -> pop esi -> pop ebx

  • 我的坑: 一开始默写把顺序搞反了,要记得栈顶的先出来。

2.2 填平战壕 (回收空间)

  • add esp, 40h:手动把 ESP 拉回来。

2.3 战后安检 (Check)

  • cmp ebp, esp + call __chkesp

  • **顿悟:**这里是在查账!看上面的 add 有没有算对,或者中间有没有子函数搞破坏。

2.4 暴力归位 (Reset)

  • mov esp, ebp

  • 心得: 不管账平不平,为了活着回家,必须强行把 ESP 拉回 EBP。这是最后一道保险。

2.5 班师回朝

  • pop ebp (恢复基址) -> ret (跳回家)。

3. 实战避坑指南(我的错题本)

3.1 Debug模式下的函数跳转 (ILT)

  • 核心现象:地址是假的

    • 现象: 在 VS Debug 模式下,CALL 00401xxx 后面的地址,通常不是函数真正的入口。

    • 本质: 那是 ILT (增量链接表) 的地址,相当于"前台"或"中转站"。

    • 目的: 为了编译加速(改代码只改中转站,不用改所有调用者)。

  • 跳转流程:三步走

    • 不要以为是 CALL -> 函数,实际路线是:

    • CALL (调用者): CALL 中转站 -> 压入返回地址 (Stack +1)。

    • JMP (中转站/帐篷): JMP 真实地址 -> 不压栈,直接飞 (Stack 不变)。

    • PUSH EBP (真实函数): 这里才是真正的函数开头 (Stack 此时只有 1 个返回地址)。

  • 关键区别:CALL vs JMP

    • CALL: 会留下痕迹(压入返回地址),像去住酒店要登记。

    • JMP: 是无痕的(不压栈),像穿过一扇任意门。

    • 结果: 正因为中间用的是 JMP,函数最后的 RET 才能直接跳回 CALL 的地方,完美忽略中转站。

  • 满屏乱码:CC (int 3)

    • 位置: 如果你在中转站看到 JMP 下面有一堆 CC。

    • 实质: 汇编指令 int 3 (断点中断)。

    • 作用:

      • 凑整(对齐): 让下一个 JMP 的地址好看(4字节对齐)。

      • 地雷(防跑飞): 程序正常跑绝不会碰它;碰了就是出 Bug 了,调试器会报警。

      • 策略: 直接无视,那是填充物。

⚡️ 一句话实战口诀

"Debug 模式看 Call 别当真,那是中转站; 进去看见 JMP (帐篷) 和 CC (乱码) 别纠结; 再跳一次到 push ebp,那才是真战场!"


3.2 以为 __chkesp 只有主函数有。

  • 正解: 只要当了"老板"(调用了子函数),为了防坑,都会有这个检查。

3.3 以为销毁时是先 mov esp, ebp 再检查。

  • 正解: 先检查(暴雷),再归位(兜底)。顺序不能乱!

3.4 视觉特征

  • 看到 55 8B EC = 函数开始。

  • 看到 5F 5E 5B (连续 pop) = 准备跑路。

4. 手写函数栈帧

结语

平衡,是万物的法则,也是堆栈的法则。

SUB ESP 的借取,到 ADD ESP 的归还;从 PUSH 的入栈,到 POP 的出栈。我看到了一种极致的对称美。任何一次微小的失衡,任何一次忘记的 POP,都将导致程序的崩溃。

原来,看懂汇编不仅仅是看懂指令,更是看懂 '有借有还' 的契约精神。至此,函数栈帧的秘密已无处遁形。我也终于明白:要想控制程序的流向,首先要学会控制内存的起落。

笔记至此合上,但我的逆向之路,才刚刚启程。

相关推荐
ThornArmor3 天前
【工具篇·番外】跨语言生态的主权回收:基于 ISA 说明书的 4-bit 双向汇编系统全线封顶
c语言·开发语言·汇编·c++·重构·架构
是星辰吖~3 天前
WIN32_线程(下)
汇编
是星辰吖~4 天前
WIN32_线程(上)
汇编
AI科技星4 天前
数术工坊 · 第四卷 橡皮泥江湖(拓扑学)【完整定稿】
c语言·开发语言·汇编·electron·概率论·拓扑学
iCxhust5 天前
C# 生成命令行程序 将hex格式烧录程序转换成bin烧录格式
开发语言·汇编·单片机·嵌入式硬件·c#·微机原理
iCxhust5 天前
C#进程管理程序
开发语言·汇编·stm32·单片机·c#·微机原理
hhcgchpspk5 天前
汇编语言传递数据和地址的误区
汇编·笔记·nasm·masm
iCxhust6 天前
MTK8088单板机制作(一)时钟电路
汇编·单片机·嵌入式硬件·微机原理·8088单板机
iCxhust6 天前
8086 汇编位测试使用方法
汇编·单片机·嵌入式硬件·微机原理·8088单板机
iCxhust6 天前
用汇编在8088单板机上创建一个进程
汇编·微机原理