003、认识启动函数,找到用户入口
PE 入口不等于 main
PE 可选头中的 AddressOfEntryPoint 指向映像的入口 RVA:
1 | 入口虚拟地址 = ImageBase + AddressOfEntryPoint |
Windows 把控制权交给入口后,通常先执行 C/C++ 运行库启动代码,而不是立刻进入用户编写的 main。
逆向时要区分加载器入口、CRT 启动函数和用户入口三个层次。
CRT 启动代码负责什么
典型职责包括:
- 初始化安全 Cookie 和运行库状态。
- 初始化全局及静态 C++ 对象。
- 准备命令行参数和环境变量。
- 选择并调用
main、wmain、WinMain或wWinMain。 - 处理返回值、退出函数和清理逻辑。
1 | PE 入口 |
寻找 main 的方法
从入口向下跟踪
在启动代码中寻找一次具有明显用户代码特征的调用,调用后返回值通常会进入退出流程。
从字符串和功能向上回溯
若 main 很难直接识别,可以从用户界面字符串、文件路径或关键 API 的交叉引用找到业务函数,再向上回溯调用者。
使用符号和运行库特征
带符号版本会直接显示名称;无符号版本可以结合编译器运行库函数、参数准备方式和入口模板判断。
控制台入口参数
1 | int main(int argc, char* argv[]); |
分析时关注:
argc是整数计数。argv是字符指针数组。argv[i]再次解引用后得到字符串地址。
Windows 图形程序的 WinMain 参数结构不同,还需要关注实例句柄、命令行和显示方式。
优化带来的变化
Release 构建可能出现:
- 用户入口被部分内联。
- 启动流程和异常处理被合并。
- 未使用的参数被删除。
- 函数边界不再呈现经典序言和尾声。
因此不要把某一版 Visual Studio 的固定字节序列当成永恒签名。
编译器版本、静态或动态 CRT、控制台或 GUI 子系统、异常和安全选项都会改变启动代码。
实验设计
分别构建:
1 | int main() |
以及带参数版本,然后记录:
- PE 入口 RVA。
- 入口到用户函数经过的主要调用。
main返回值最终流向。- Debug 与 Release 的差异。
- x86 与 x64 参数传递差异。
逆向记录模板
1 | PE 入口:模块 + RVA |
学习检查清单
| 检查项 | 状态 |
|---|---|
| 能计算入口虚拟地址 | 待复盘 |
| 能说明 PE 入口与 main 的区别 | 待复盘 |
| 能列出 CRT 的主要初始化职责 | 待复盘 |
| 能从入口或业务锚点定位用户代码 | 待复盘 |
| 能解释优化为何会改变入口识别特征 | 待复盘 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ruiqy~!






















