013、框架—C 语言与汇编程序框架
从 C 程序看执行框架
一个最小 C 控制台程序看起来只有 main:
1 |
|
但生成 EXE 后,操作系统通常不会直接跳到源码中的 main。在使用 C 运行库时,大致流程是:
1 | Windows Loader |
源码入口
main与 PE 入口点 OEP 不是同一个概念。逆向时经常需要从启动代码继续跟踪,才能定位真正的业务函数。
MASM x86 基本框架
1 | .586 |
| 声明 | 作用 |
|---|---|
.586 |
允许使用相应代际的 x86 指令 |
.model flat, stdcall |
平坦内存模型与默认调用约定 |
option casemap:none |
保留标识符大小写 |
.data |
定义已初始化数据 |
.code |
定义代码 |
main proc/endp |
定义过程范围 |
end main |
告诉汇编器源文件结束及入口符号 |
MASM x64 基本框架
x64 不再使用 .model flat, stdcall 区分常见调用约定,Windows x64 采用统一的寄存器传参规则。
1 | option casemap:none |
这里的 sub rsp, 28h 同时照顾了栈对齐和被调用函数需要的影子空间。后续学习 x64 调用约定时再详细展开。
Visual Studio 的 x64 C/C++ 通常不支持传统内联汇编。需要把汇编写入独立
.asm文件,交给 MASM 构建,再与 C/C++ 目标文件链接。
构建过程
1 | C/C++ 源文件 --编译器--> .obj |
编译负责把高级语言转换为目标代码,汇编负责把助记符转换为机器指令,链接负责合并目标文件并解析符号引用。
指令集与工作模式
| 项目 | x86 | x64 |
|---|---|---|
| 通用寄存器宽度 | 32 Bit | 64 Bit |
| 指针常见宽度 | 4 Byte | 8 Byte |
| 指令指针 | EIP | RIP |
| 栈指针 | ESP | RSP |
| Windows 常见用户态 | 保护模式 | 长模式 |
CPU 支持某条指令,不代表当前工具链和目标模式一定允许直接使用。分析时要同时确认:
- PE 是 32 位还是 64 位;
- 当前反汇编器按哪种模式解码;
- 编译器启用了哪些扩展指令集;
- 操作数宽度与地址宽度是否一致。
外部函数与动态调用
汇编代码调用 Windows API 时,需要解决两个问题:
- 告诉汇编器函数原型或外部符号名称;
- 告诉链接器符号来自哪个导入库。
1 | ExitProcess PROTO :DWORD |
链接完成后,PE 导入表会记录依赖。程序加载时,Windows Loader 再把实际函数地址写入对应的导入地址位置。
因此在反汇编里看到的 API 调用,可能是经过 IAT 的间接调用,而不是直接写死系统 DLL 中的绝对地址。
混合编程的接口边界
C/C++ 与汇编互相调用时,双方必须约定一致:
- 符号名称;
- 参数顺序和参数位置;
- 返回值位置;
- 哪些寄存器需要保存;
- 栈由谁清理;
- 结构体和数据的对齐方式。
任何一项不一致,都可能表现为参数错误、返回地址损坏或函数返回后崩溃。
学习检查清单
| 检查项 | 状态 |
|---|---|
能区分源码 main 与 PE 入口点 |
待复盘 |
能解释 .data、.code、proc/endp、end |
待复盘 |
| 能说出编译、汇编和链接的区别 | 待复盘 |
| 知道 x86 与 x64 的寄存器和指针宽度差异 | 待复盘 |
| 能说明调用外部函数为何需要原型和导入库 | 待复盘 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ruiqy~!











