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

  • 开辟战场(序言 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,都将导致程序的崩溃。

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

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

相关推荐
止观止14 小时前
在 WSL2 上从零搭建 ARM 混合编程环境
汇编·arm开发·嵌入式开发·混合编程
say_fall2 天前
8086汇编程序设计_从基础到实战
开发语言·汇编·8086
浩浩测试一下2 天前
LoadPE &&& 原理以及作用 (ASM汇编版本)>>01
汇编·免杀·pe结构·windows编程·二进制逆向·系统loadpe
ThornArmor2 天前
【控制篇】斩断无休止空转:4-bit 指令集里的跳转律令与时序状态机
c语言·汇编·c++·单片机·嵌入式硬件
大阳1233 天前
ARM4.(通过汇编,c语言,固件库点亮LED)
c语言·开发语言·汇编
iCxhust3 天前
8086 汇编 TINY 和 SMALL 编程MODEL区别
汇编·单片机·嵌入式硬件·操作系统·微机原理·8088单板机
say_fall4 天前
从零开始学x86汇编_16位指令系统完全指南
开发语言·汇编·计算机组成·微机原理
txg6665 天前
编译无关的漏洞检测:基于 Transformer 的 LLVM-IR 与汇编鲁棒建模
汇编·深度学习·安全·transformer
浩浩测试一下6 天前
汇编 16位32位64位通用寄存器(逆向分析)
汇编·windows·stm32·单片机·嵌入式硬件·逆向·二进制