🎨 颜色标记说明

  • 粉色:特别重要、主线必掌握、优先复盘的内容。
  • 蓝色:核心概念、术语、分类名。
  • 橙色:需要关注的边界、易错点、注意事项。
  • 绿色:解释类内容、帮助理解知识点。

返回:全书总览 · 上一章:基本数据类型 · 下一章:表达式求值

PE 入口不等于 main

PE 可选头中的 AddressOfEntryPoint 指向映像的入口 RVA:

1
入口虚拟地址 = ImageBase + AddressOfEntryPoint

Windows 把控制权交给入口后,通常先执行 C/C++ 运行库启动代码,而不是立刻进入用户编写的 main

逆向时要区分加载器入口、CRT 启动函数和用户入口三个层次。

CRT 启动代码负责什么

典型职责包括:

  • 初始化安全 Cookie 和运行库状态。
  • 初始化全局及静态 C++ 对象。
  • 准备命令行参数和环境变量。
  • 选择并调用 mainwmainWinMainwWinMain
  • 处理返回值、退出函数和清理逻辑。
1
2
3
4
5
PE 入口
→ CRT 初始化
→ 全局构造
→ 用户入口
→ 全局析构与退出

寻找 main 的方法

从入口向下跟踪

在启动代码中寻找一次具有明显用户代码特征的调用,调用后返回值通常会进入退出流程。

从字符串和功能向上回溯

main 很难直接识别,可以从用户界面字符串、文件路径或关键 API 的交叉引用找到业务函数,再向上回溯调用者。

使用符号和运行库特征

带符号版本会直接显示名称;无符号版本可以结合编译器运行库函数、参数准备方式和入口模板判断。

控制台入口参数

1
int main(int argc, char* argv[]);

分析时关注:

  • argc 是整数计数。
  • argv 是字符指针数组。
  • argv[i] 再次解引用后得到字符串地址。

Windows 图形程序的 WinMain 参数结构不同,还需要关注实例句柄、命令行和显示方式。

优化带来的变化

Release 构建可能出现:

  • 用户入口被部分内联。
  • 启动流程和异常处理被合并。
  • 未使用的参数被删除。
  • 函数边界不再呈现经典序言和尾声。

因此不要把某一版 Visual Studio 的固定字节序列当成永恒签名。

编译器版本、静态或动态 CRT、控制台或 GUI 子系统、异常和安全选项都会改变启动代码。

实验设计

分别构建:

1
2
3
4
int main()
{
return 7;
}

以及带参数版本,然后记录:

  1. PE 入口 RVA。
  2. 入口到用户函数经过的主要调用。
  3. main 返回值最终流向。
  4. Debug 与 Release 的差异。
  5. x86 与 x64 参数传递差异。

逆向记录模板

1
2
3
4
5
6
7
PE 入口:模块 + RVA
CRT 启动函数:
全局初始化函数:
候选用户入口:
入口参数:
退出路径:
动态验证:

学习检查清单

检查项 状态
能计算入口虚拟地址 待复盘
能说明 PE 入口与 main 的区别 待复盘
能列出 CRT 的主要初始化职责 待复盘
能从入口或业务锚点定位用户代码 待复盘
能解释优化为何会改变入口识别特征 待复盘