PE与ELF文件格式解析
文件格式这东西,说白了就是操作系统和可执行文件之间的「握手协议」。你在Windows上双击一个exe,系统怎么知道代码在哪、数据在哪、从哪里开始执行?这些信息都藏在文件头里。我刚开始学逆向时,总觉得看这些结构体很枯燥,直到有一次调试一个加壳样本,发现DOS头被篡改导致加载失败……嗯,从那以后我再也不敢跳过文件格式了。
PE文件结构
PE(Portable Executable)是Windows平台的可执行文件格式。它的设计思路其实挺清晰的——从DOS兼容性一路演进到32位/64位。整个结构分三层:
- DOS头:64字节,以
MZ签名开头。关键字段是e_lfanew,它指向真正的PE头偏移。为什么要有这个?为了兼容DOS系统,老程序看到MZ就直接运行,新系统则顺着偏移去找NT头。 - NT头:包含
Signature(PE\0\0)、FileHeader(机器类型、节数量、时间戳等)、OptionalHeader(入口点、镜像基址、节对齐等)。注意,OptionalHeader在PE32+里是必选的,名字有误导性。 - 节表:每个节(Section)描述一块内存区域。常见的节有
.text(代码)、.data(已初始化数据)、.rdata(只读数据)、.idata(导入表)。
关键偏移速查:PE文件解析时,你只需要记住三个偏移:0x00(DOS头起始)、0x3C(e_lfanew字段)、e_lfanew指向的值(NT头起始)。其他字段都可以通过结构体偏移计算。
ELF文件结构
ELF(Executable and Linkable Format)是Linux/Unix世界的标准。它比PE更简洁,但概念上类似。我个人习惯把ELF看作三张表:
| 组件 | 作用 | 我踩过的坑 |
|---|---|---|
| ELF头 | 文件标识、架构、入口点、程序头/节头偏移 | 有一次忘了检查 e_ident[EI_CLASS],把32位ELF当64位解析,段错误查了一下午 |
| 程序头 | 描述段(Segment),用于加载器映射到内存 | PT_LOAD段的对齐属性没处理好,mmap会失败 |
| 节头 | 描述节(Section),用于链接器和调试器 | .symtab和.strtab的关联关系容易搞混 |
你想想看,为什么ELF需要两套视图?程序头是给运行时加载器看的,节头是给链接器和调试器看的。一个面向执行,一个面向分析,分工明确。
动态链接与静态链接
这是CTF里绕不开的话题。静态链接把所有库代码打包进可执行文件,体积大但独立性强。动态链接依赖外部共享库(.so或.dll),体积小但需要运行时解析符号。
我建议你重点理解动态链接的延迟绑定(Lazy Binding)机制。为什么?因为CTF的ret2libc攻击就是利用GOT(全局偏移表)和PLT(过程链接表)的解析流程。具体来说:
- GOT:存放外部函数的实际地址。第一次调用时,GOT条目指向PLT中的解析器;解析完成后,GOT更新为真实地址。
- PLT:一段桩代码,负责调用动态链接器解析符号。每个外部函数对应一个PLT条目。
- 延迟绑定:只有函数第一次被调用时才解析,节省启动时间。
实战技巧:在GDB中,用 got.plt 查看GOT条目,用 disas <func>@plt 查看PLT代码。我曾经靠这个技巧在比赛中快速定位了一个libc基址泄露漏洞。
PE vs ELF:核心差异对比
| 维度 | PE | ELF |
|---|---|---|
| 签名 | MZ + PE\0\0 | \x7fELF |
| 头结构 | DOS头 + NT头 + 节表 | ELF头 + 程序头 + 节头 |
| 导入机制 | 导入表(IDT) | .dynsym + .dynstr + .plt.got |
| 重定位 | .reloc节 | .rel.dyn / .rel.plt |
| 入口点 | AddressOfEntryPoint(相对基址) | e_entry(虚拟地址) |
为什么会这样?因为两个系统的设计哲学不同。Windows强调向后兼容(DOS头),Linux追求简洁统一(ELF头只有52字节)。
SVG:PE与ELF结构对比图
避坑指南
我曾经犯过的错:
- 解析PE时直接读
0x00当作NT头——忘了先读e_lfanew,结果解析出一堆乱码。 - 处理ELF动态链接时,以为
.got.plt和.got是同一个节——其实前者是PLT专用的GOT,后者是全局GOT,偏移不同。 - 静态链接的ELF没有
.plt节,我写脚本时没做判断,直接段错误。
我的调试习惯:用 readelf -a 看ELF全貌,用 objdump -x 看PE节表。写解析脚本时,先打印头结构的前几个字节验证偏移,再逐步展开。这样能快速定位解析错误。
文件格式是逆向工程的地基。你花一小时把PE和ELF的头结构理清楚,后面调试、漏洞利用、加壳脱壳都会顺畅很多。别急,慢慢来。