4、ELF文件格式解析:ELF头部、节区表、程序头表、动态链接与重定位、常见段(.text、.data、.bss、.got、.plt)

在CTF的Pwn与逆向工程中,绝大多数题目都基于Linux下的ELF(Executable and Linkable Format)文件。无论是分析栈溢出、格式化字符串漏洞,还是手工构造ROP链、劫持GOT表,都离不开对ELF文件结构的深刻理解。ELF不仅是一种文件格式,更是操作系统加载、链接器解析、调试器定位符号的“契约”。本章将从ELF头部出发,逐层拆解节区表(Section Header Table)与程序头表(Program Header Table),并深入动态链接与重定位机制,最后结合CTF实战场景剖析.text、.data、.bss、.got、.plt等关键段的作用与利用价值。

ELF文件解析核心结构图 ELF Header e_ident / e_type / e_machine e_entry / e_phoff / e_shoff Program Header Table PT_LOAD / PT_DYNAMIC PT_INTERP / PT_GNU_STACK 用于加载与运行时映射 Section Header Table .text / .data / .bss .got / .plt / .dynsym 用于链接与调试 动态链接与重定位 动态链接 .dynamic / .dynsym GOT / PLT 协作 重定位 R_X86_64_JUMP_SLOT R_X86_64_GLOB_DAT 常见段(节) .text 代码段,只读可执行 .data 已初始化全局变量,可读写 .bss 未初始化全局变量,不占文件空间 .got 全局偏移表 .plt 过程链接表 CTF 关键视角 • 程序头表决定进程内存布局,PT_LOAD 段映射关系是 ROP 与栈迁移的基础 • .got.plt 存储外部函数真实地址,GOT 覆写是 Pwn 题最常用的攻击手段之一 • .bss 段无文件内容但占用内存,常用于存放伪造结构体或 stage2 payload • 重定位表(.rela.plt / .rela.dyn)揭示了动态符号解析过程,是 ret2dl-resolve 的核心依据

上图展示了ELF文件在静态与动态两个维度上的核心结构。左侧的ELF头部是文件的“总索引”,通过e_phoff和e_shoff分别指向程序头表和节区表;右侧的常见段则是链接器与加载器实际操作的“内容单元”。理解这张图,就掌握了ELF分析的全局脉络。

4.1 ELF头部:一切解析的起点

核心原理:ELF头部(Elf64_Ehdr)固定位于文件起始位置,大小为64字节(64位系统)。它定义了文件的类型(可重定位文件、可执行文件、共享对象)、目标架构、程序入口地址、程序头表和节区表的偏移及条目数量。在CTF中,我们常用readelf -h或xxd快速查看头部信息,以确认文件是32位还是64位、是否开启PIE(地址随机化)、入口点在哪里。

判断方法:使用readelf -h ./pwn命令,重点关注以下字段:

  • Type:EXEC(固定地址)或DYN(PIE/共享对象),决定能否直接使用固定地址构造ROP。
  • Machine:Advanced Micro Devices X86-64,确认指令集。
  • Entry point address:程序入口,通常是_start,但PIE开启时该地址是相对偏移。
  • Start of section headers:节区表偏移,用于解析节区。
经验提醒:在Pwn题中,若ELF Type为DYN,则所有地址都需要加上PIE基址。通常先通过泄露一个已知地址(如puts@got)来计算基址,再构造ROP链。

4.2 程序头表:进程内存布局的蓝图

核心原理:程序头表(Program Header Table)是内核加载ELF时唯一参考的结构。每个条目(Elf64_Phdr)描述一个段(Segment),常见类型包括PT_LOAD(可加载段)、PT_DYNAMIC(动态链接信息)、PT_INTERP(解释器路径)、PT_GNU_STACK(栈可执行性)。PT_LOAD段指定了文件中的某段内容应映射到进程虚拟地址空间的哪个位置,以及映射后的权限(R/E/W)。

操作流程:使用readelf -l ./pwn查看程序头表,重点关注:

  1. Offset 与 VirtAddr:文件偏移与虚拟地址的对应关系,用于计算ASLR偏移。
  2. Flags:R(读)、E(执行)、W(写)。通常.text所在段为R+E,.data和.bss所在段为R+W。
  3. PT_GNU_STACK:若该段Flags包含E,则栈可执行,可考虑直接注入shellcode;否则需要ROP。

案例拆解:假设一个64位Pwn题,readelf -l显示有两个PT_LOAD段。第一段包含ELF头部和.text,权限为R+E;第二段包含.data和.bss,权限为R+W。当程序开启PIE时,内核会将整个文件按页映射到随机基址,但两段之间的相对偏移不变。因此,如果我们泄露了puts@got(位于第二段)的地址,就可以通过固定偏移计算出_start(位于第一段)的真实地址,从而绕过PIE。

风险说明:不要混淆节区表与程序头表。节区表用于链接器和调试器(静态视角),程序头表用于加载器(动态视角)。在运行时,只有程序头表决定内存布局,节区表可能被丢弃。

4.3 节区表与常见段:.text、.data、.bss

核心原理:节区表(Section Header Table)是ELF文件的“目录”,每个条目描述一个节(Section)的名称、类型、地址、偏移、大小等信息。对于CTF逆向,最常关注的节如下:

节名 内容 权限 CTF关注点
.text 程序机器码 只读+执行 反汇编分析漏洞函数,查找gadget
.data 已初始化的全局变量 读写 存放flag或关键指针,可被覆写
.bss 未初始化的全局变量 读写 不占文件空间,适合存放伪造结构或二次payload
.got 全局偏移表(外部函数地址) 读写(延迟绑定后) GOT覆写攻击,泄露libc地址
.plt 过程链接表(跳板) 只读+执行 调用外部函数,ret2plt

判断方法:使用readelf -S ./pwn查看节区表,注意每个节的Addr(虚拟地址)、Off(文件偏移)、Size。在IDA或Ghidra中,这些节会以不同颜色区分,便于快速定位。

实战场景:在格式化字符串漏洞中,我们常利用%n向任意地址写入数据。如果目标地址在.bss段(无文件内容),我们需要先通过readelf -S确认.bss的虚拟地址范围。例如,某题.bss基址为0x404060,我们可将payload写入.bss+0x100处,避免破坏全局变量。

案例闭环:某Pwn题存在栈溢出,但开启了Full RELRO(GOT只读),无法直接覆写GOT。通过分析节区表,发现.bss段有一个全局结构体指针,且程序在退出前会调用该指针指向的函数。我们利用栈溢出将返回地址改为read@plt,将伪造的“结构体”写入.bss,其中包含system("/bin/sh")的地址。最终在程序退出时,该指针被调用,成功getshell。此案例说明,理解.bss的用途和地址布局,是构造复杂利用链的基础。

4.4 动态链接与重定位:GOT与PLT的协作机制

核心原理:动态链接允许可执行文件在运行时加载共享库(如libc.so)。为了实现这一点,ELF引入了两个关键结构:GOT(Global Offset Table)和PLT(Procedure Linkage Table)。PLT中的每个条目对应一个外部函数(如puts),它是一段短小的跳转代码。首次调用时,PLT跳转到GOT中记录的地址,该地址初始指向PLT的下一条指令(即解析代码);解析器(通常是_dl_runtime_resolve)找到真实函数地址后,将其写入GOT。后续调用直接通过GOT跳转,无需再次解析。重定位表(.rela.plt / .rela.dyn)记录了哪些GOT条目需要被修正,以及修正类型(如R_X86_64_JUMP_SLOT)。

操作流程:在CTF中,我们通常使用objdump -R ./pwn查看重定位表,或使用readelf -r。关键信息包括:

  • Offset:GOT条目的虚拟地址,即写入真实地址的位置。
  • Type:R_X86_64_JUMP_SLOT(函数地址)、R_X86_64_GLOB_DAT(全局变量地址)。
  • Symbol Name:对应的函数或变量名。

案例拆解:假设我们想要泄露libc地址。首先通过objdump -R找到puts@GOT的地址(例如0x404018)。然后利用格式化字符串漏洞,用%7$s读取该地址处存储的内容(即puts的真实地址)。由于puts在libc中的偏移是固定的,我们通过libc-database或在线工具匹配偏移,计算出libc基址,进而找到system和/bin/sh的地址。

常见误区:不要混淆GOT与GOT.plt。在部分ELF中,GOT分为.got(全局变量)和.got.plt(函数地址)。.got.plt的前三项是保留项(分别指向动态段、链接映射和解析器),从第四项开始才是函数地址。覆写GOT时,务必确认目标地址属于.got.plt且对应正确的函数。

总结复盘:ELF格式是Pwn与逆向的基石。掌握ELF头部、程序头表、节区表、动态链接与重定位,意味着你能够:

  1. 通过readelf快速定位关键段和地址,判断防护机制(PIE、RELRO、NX)。
  2. 理解程序加载后的内存布局,为ROP、栈迁移、堆利用提供地址基础。
  3. 利用GOT/PLT机制进行地址泄露、函数劫持,甚至构造ret2dl-resolve攻击。
  4. 在逆向分析时,通过节区表快速定位用户代码、全局变量和外部依赖。
进阶提示:在实战中,建议使用checksec脚本(pwntools自带)一键查看所有安全属性,再结合readelf -a输出完整信息。对于复杂题目,可以先用readelf -S和-l画出内存布局图,再开始漏洞分析。