007、变量在内存中的位置和访问方式
本章主线
反汇编里没有“局部变量”“全局变量”这样的源码标签,只有地址和访问动作。判断变量身份时要回答三个问题:
- 地址从哪里来?
- 这块内存何时创建、何时失效?
- 哪些函数能够访问它?
变量类型的恢复,本质上是对存储区域、生命周期和访问宽度的联合推断。
全局变量
全局变量通常位于模块映像的静态数据区:
- 已初始化且可写的数据常在
.data。 - 未显式初始化的数据可能位于零初始化区域。
- 只读常量常在
.rdata,但具体节名取决于编译器和链接器。
x86 中常见绝对地址访问,x64 中常见 RIP 相对寻址:
1 | mov eax, dword ptr [global_value] |
分析时把 VA 转成 RVA,再判断地址属于哪个节。ASLR 会改变模块基址,但同一映像内的 RVA 通常保持稳定。
栈上的局部变量
局部变量常位于线程栈中,传统未优化函数可能使用:
1 | mov eax, dword ptr [ebp-4] |
典型特征:
- 地址相对当前栈帧计算。
- 生命周期通常受函数或语句块约束。
- 同一槽位可能在不同阶段复用给多个源码变量。
- 优化后变量可能只存在于寄存器中,根本没有内存地址。
“看到 [ebp-x] 就命名为一个独立变量”并不可靠。编译器可能复用栈槽,也可能把临时值、保存寄存器和对齐空间放在同一区域。
局部静态变量
局部静态变量具有局部作用域,但存储期贯穿整个进程。反汇编中通常表现得更像带初始化保护的全局变量:
1 | 检查初始化标志 |
C++11 以后,函数内静态对象的首次初始化还需要满足线程安全要求,可能出现运行库守卫函数或同步逻辑。具体形式依编译器版本而变。
堆变量
堆变量的地址通常来自分配函数:
- C:
malloc/calloc/realloc。 - C++:
operator new/operator new[]。 - Windows:
HeapAlloc/VirtualAlloc等。
逆向时要追踪完整生命周期:
1 | 分配 -> 保存指针 -> 读写 -> 传递所有权 -> 释放 |
free、delete、HeapFree 和 VirtualFree 并非可以随意混用。识别分配器与释放器是否配对,是发现内存错误的重要步骤。
寄存器中的变量
优化构建会尽量让热点数据留在寄存器:
- 参数进入函数后可能一直不落栈。
- 循环计数器常驻寄存器。
- 多个源码变量可共享同一寄存器的不同时段。
- 一个源码变量也可能在寄存器和栈槽之间迁移。
因此变量恢复应该使用“值的定义与使用链”,不能只靠地址命名。
访问宽度反推类型
| 指令访问 | 候选宽度 |
|---|---|
byte ptr |
1 Byte |
word ptr |
2 Byte |
dword ptr |
4 Byte |
qword ptr |
8 Byte |
再结合 MOVSX、MOVZX、比较方式、算术指令和后续 API 原型判断有无符号及语义类型。
变量恢复记录表
| 字段 | 记录内容 |
|---|---|
| 地址来源 | 模块、栈、堆、寄存器或 TLS |
| 访问宽度 | 1/2/4/8 Byte 或向量宽度 |
| 首次定义 | 首次被赋值的位置 |
| 使用范围 | 读取、修改和传递位置 |
| 生命周期 | 创建到最后一次有效使用 |
| 候选类型 | 整数、指针、对象、数组等 |
| 置信度 | 已确认 / 高概率 / 待验证 |
学习检查清单
| 检查项 | 状态 |
|---|---|
| 能根据节区和寻址方式识别全局数据 | 待复盘 |
| 能区分栈变量与寄存器临时值 | 待复盘 |
| 能说明局部静态变量的初始化守卫 | 待复盘 |
| 能沿分配与释放追踪堆对象生命周期 | 待复盘 |
| 能结合访问宽度和符号扩展推断候选类型 | 待复盘 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ruiqy~!





















