reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
# ELF 布局:ehdr / phdr / shdr 字段表与典型布局
|
||||
|
||||
ELF 文件 = ELF 头(ehdr)+ 程序头表(phdr,供加载器)+ 节区(sections)+ 节头表(shdr,供工具/链接器)。三张表的偏移与条数都在 ehdr 里给出(e_phoff/e_phnum、e_shoff/e_shnum),文件里可以按索引线性遍历。32 位与 64 位字段相同、大小不同(本文件以 64 位为主,32 位差异单独标注)。
|
||||
|
||||
## ELF 头(ehdr)
|
||||
|
||||
### e_ident 前 16 字节(0x00-0x0F)
|
||||
|
||||
| 偏移 | 名称 | 长度 | 典型值 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | EI_MAG | 4 | `7F 45 4C 46`(`\x7fELF`),前 4 字节不符即非 ELF |
|
||||
| 0x04 | EI_CLASS | 1 | 1=ELFCLASS32,2=ELFCLASS64 |
|
||||
| 0x05 | EI_DATA | 1 | 1=小端,2=大端 |
|
||||
| 0x06 | EI_VERSION | 1 | 恒 1 |
|
||||
| 0x07 | EI_OSABI | 1 | 0=System V,3=Linux,9=FreeBSD;固件常见 0(裸机) |
|
||||
| 0x08 | EI_ABIVERSION | 1 | 0 |
|
||||
| 0x09-0x0F | 填充 | 7 | 0 |
|
||||
|
||||
### 字段表(64 位,总 64 字节;32 位总 52 字节)
|
||||
|
||||
| 偏移(64) | 名称 | 类型 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x10 | e_type | u16 | 1=REL(可重定位 .o) 2=EXEC(固定基址) 3=DYN(PIE/共享库) 4=CORE |
|
||||
| 0x12 | e_machine | u16 | 62=x86-64 3=i386 40=ARM 183=AArch64 8=MIPS 20=PPC 243=RISC-V |
|
||||
| 0x14 | e_version | u32 | 1 |
|
||||
| 0x18 | e_entry | u64 | 入口点(DYN 下为相对基址的偏移) |
|
||||
| 0x20 | e_phoff | u64 | 程序头表文件偏移 |
|
||||
| 0x28 | e_shoff | u64 | 节头表文件偏移 |
|
||||
| 0x30 | e_flags | u32 | 架构相关(如 MIPS 的 ABI/ISA/PIC/NaN 等位,见 EF_MIPS_*;**不含字节序**——字节序始终看 e_ident[EI_DATA]) |
|
||||
| 0x34 | e_ehsize | u16 | 64(32 位为 52) |
|
||||
| 0x36 | e_phentsize | u16 | 56(32 位为 32) |
|
||||
| 0x38 | e_phnum | u16 | 程序头条数(=PN_XNUM 0xffff 时真实值在 shdr[0].sh_info) |
|
||||
| 0x3A | e_shentsize | u16 | 64(32 位为 40) |
|
||||
| 0x3C | e_shnum | u16 | 节头条数(=0 时真实值在 shdr[0].sh_size——与 e_phnum 不是同一套规则) |
|
||||
| 0x3E | e_shstrndx | u16 | 节名表(.shstrtab)在节头表里的索引(=SHN_XINDEX 0xffff 时真实索引在 shdr[0].sh_link) |
|
||||
|
||||
32 位偏移变化:e_entry=0x18(4B)、e_phoff=0x1C、e_shoff=0x20、e_flags=0x24、e_ehsize=0x28、e_phentsize=0x2A、e_phnum=0x2C、e_shentsize=0x2E、e_shnum=0x30、e_shstrndx=0x32。
|
||||
|
||||
## 程序头(phdr)——加载视图
|
||||
|
||||
64 位每条 56 字节;32 位 32 字节。
|
||||
|
||||
| 偏移(64) | 名称 | 类型 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | p_type | u32 | 1=PT_LOAD 2=PT_DYNAMIC 3=PT_INTERP 4=PT_NOTE 6=PT_PHDR 7=PT_TLS;GNU 私有:0x6474e550=GNU_EH_FRAME 0x6474e551=GNU_STACK 0x6474e552=GNU_RELRO |
|
||||
| 0x04 | p_flags | u32 | 1=PF_X 2=PF_W 4=PF_R |
|
||||
| 0x08 | p_offset | u64 | 段在文件中的偏移 |
|
||||
| 0x10 | p_vaddr | u64 | 段装载虚拟地址(DYN 为相对基址偏移) |
|
||||
| 0x18 | p_paddr | u64 | 物理地址(多数平台忽略) |
|
||||
| 0x20 | p_filesz | u64 | 文件内大小(readelf 要求 ≤ memsz) |
|
||||
| 0x28 | p_memsz | u64 | 内存大小(> filesz 部分为零填充,如 .bss) |
|
||||
| 0x30 | p_align | u64 | 对齐(页 0x1000;p_vaddr≡p_offset mod align) |
|
||||
|
||||
32 位顺序不同:p_type、p_offset、p_vaddr、p_paddr、p_filesz、p_memsz、p_flags、p_align(p_flags 在第 7 个而不是第 2 个)。
|
||||
|
||||
- PT_LOAD:定义需要装载进进程内存映像的 loadable segment(按 p_filesz/p_memsz 建立常规文件→内存映射);可执行文件至少 1 个,通常 2-4 个(R、R X、R W)。**不要表述成"唯一被映射的段类型"**——PT_PHDR 在存在时同时描述程序头表在文件与内存映像中的位置,PT_TLS 等也具运行时内存语义
|
||||
- PT_DYNAMIC:指向 .dynamic 段(DT_* 标签数组),动态链接入口
|
||||
- PT_INTERP:解释器路径字符串(`/lib64/ld-linux-x86-64.so.2`)
|
||||
- PT_GNU_STACK:**存在时**由 PF_X 决定是否请求可执行栈(内核按该 header 的 p_flags 控制用户栈权限)。**缺失时不能一律推断**,且分两个层面:内核装载主程序时按目标 ABI/实现决定(x86-64 上缺失按不可执行处理,ia32 上缺少 PT_GNU_STACK 会走 READ_IMPLIES_EXEC 使映射实际可执行);glibc 的 `dlopen` 判据不同——缺 header 时按该 ABI 的默认栈权限处理(x86/x86-64/arm32 等视为要求可执行栈、aarch64/riscv 等不要求),"要求可执行栈而进程未启用"即报 `cannot enable executable stack`(见 SKILL.md 坑)
|
||||
- PT_GNU_RELRO:该范围映射为只读(配合 BIND_NOW 为全 RELRO)
|
||||
|
||||
## 节头(shdr)——静态视图
|
||||
|
||||
64 位每条 64 字节;32 位 40 字节。
|
||||
|
||||
| 偏移(64) | 名称 | 类型 | 含义 |
|
||||
|---|---|---|---|
|
||||
| 0x00 | sh_name | u32 | 节名在 .shstrtab 内的偏移(0=无名) |
|
||||
| 0x04 | sh_type | u32 | 1=PROGBITS 2=SYMTAB 3=STRTAB 4=RELA 5=HASH 6=DYNAMIC 8=NOBITS(.bss) 9=REL 11=DYNSYM 14=INIT_ARRAY 15=FINI_ARRAY;0x6ffffff6=GNU_HASH |
|
||||
| 0x08 | sh_flags | u64 | 1=SHF_WRITE 2=SHF_ALLOC 4=SHF_EXECINSTR(A 标志 = 需装载进内存) |
|
||||
| 0x10 | sh_addr | u64 | 装载后的虚拟地址(非 ALLOC 节为 0) |
|
||||
| 0x18 | sh_offset | u64 | 文件偏移(NOBITS 节无内容) |
|
||||
| 0x20 | sh_size | u64 | 节大小 |
|
||||
| 0x28 | sh_link | u32 | 关联节索引(符号节→字符串节;RELA→符号表) |
|
||||
| 0x2C | sh_info | u32 | 附加信息(RELA:重定位的节索引) |
|
||||
| 0x30 | sh_addralign | u64 | 对齐 |
|
||||
| 0x38 | sh_entsize | u64 | 定长条目大小(符号 24、重定位 24、DT_ 16),0=不定长 |
|
||||
|
||||
## 常见 section 布局
|
||||
|
||||
典型非 PIE x86-64 可执行文件(地址从左到右递增):
|
||||
|
||||
```
|
||||
文件偏移 节 内存(vaddr) 属性
|
||||
0x000000 ELF header (64B)
|
||||
0x000040 Program headers (e_phnum×56B)
|
||||
0x0001a8 .interp 0x4001a8 R (解释器路径)
|
||||
.note.gnu.property .note.ABI-tag
|
||||
.gnu.hash 符号哈希(新式查找)
|
||||
.dynsym 动态符号表(24 字节/条) A
|
||||
.dynstr 动态符号字符串表 A
|
||||
.rela.dyn 重定位(DATA/RELATIVE) A
|
||||
.rela.plt 重定位(JUMP_SLOT) A
|
||||
.text 代码 AX
|
||||
.rodata 只读数据 A
|
||||
.eh_frame_hdr/.eh_frame
|
||||
.init_array/.fini_array 构造/析构指针数组 AW
|
||||
.dynamic DT_* 标签数组 AW
|
||||
.got/.got.plt GOT AW
|
||||
.data 已初始化数据 AW
|
||||
.bss 零初始化(NOBITS) AW
|
||||
文件尾 .symtab/.strtab(调试符号,可被 strip)
|
||||
.shstrtab 节名表
|
||||
Section headers(e_shoff 指向)
|
||||
```
|
||||
|
||||
布局要点:
|
||||
|
||||
- 程序头(加载视图)与节头(静态视图)是两套并行的视图:节可以不在任何段里(.comment、.shstrtab),段也可以不覆盖节头表(多数二进制如此)
|
||||
- 动态区(.dynsym/.dynstr/.gnu.hash/.rela.*/.dynamic/.got)全部带 SHF_ALLOC,因为运行时需要;.symtab/.strtab 不带——被 strip 后只剩动态区
|
||||
- 惰性绑定二进制有 .plt/.plt.got + .rela.plt;`-z now`(全 RELRO)二进制 GOT 装载时填好,只读
|
||||
- 现代发行版默认 `-z now` + PIE(e_type=DYN),部分构建无 .plt(GOT 直调)
|
||||
|
||||
## 动态链接对象链(符号解析路径)
|
||||
|
||||
```
|
||||
.dynamic (DT_* 标签)
|
||||
├─ DT_SYMTAB → .dynsym(每 24 字节一个 Elf64_Sym: st_name→dynstr 偏移, st_value, st_size, st_info(bind+type), st_shndx)
|
||||
├─ DT_STRTAB/DT_STRSZ → .dynstr(符号名字符串池)
|
||||
├─ DT_GNU_HASH / DT_HASH → 符号查找表(GNU_HASH 新格式:bloom 过滤器 + 桶 + 链;DT_HASH 旧格式仅桶链)
|
||||
├─ DT_JMPREL + DT_PLTRELSZ → .rela.plt(R_X86_64_JUMP_SLOT: GOT 槽 → 实际函数地址)
|
||||
├─ DT_RELA + DT_RELASZ → .rela.dyn(GLOB_DAT/RELATIVE/64)
|
||||
└─ DT_INIT_ARRAY/DT_INIT_ARRAYSZ → 构造函数指针数组(main 前执行)
|
||||
```
|
||||
|
||||
- R_X86_64_GLOB_DAT(6):GOT 槽 = 符号地址;R_X86_64_JUMP_SLOT(7):PLT/GOT 跳转槽;R_X86_64_RELATIVE(8):槽 = 基址 + addend
|
||||
- DT_BIND_NOW(或 DT_FLAGS 含 BIND_NOW):装载时完成全部重定位,GOT 只读(全 RELRO 前提)
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 一切偏移以 e_ident[4](class)分派:32/64 位结构大小不同,解析器先读 class 再选格式
|
||||
- **扩展编号(extended numbering)是三套独立规则**,手写解析器必须分别实现,不能共用一套逻辑:`e_phnum==PN_XNUM(0xffff)` → 真实程序头数在 `shdr[0].sh_info`;`e_shnum==0` → 真实节头数在 `shdr[0].sh_size`;`e_shstrndx==SHN_XINDEX(0xffff)` → 真实索引在 `shdr[0].sh_link`。三者都以"节 0 存在"为前提——建议配 parser fixture 验证这三条路径(工具一般已处理)
|
||||
- 大端目标(MIPS 固件常见)解析前按 EI_DATA 换字节序,别默认小端
|
||||
- readelf 能正常解析 ≠ 结构"正确":工具解析器有容错,关键结论(GOT 可写性、加载范围)对照 phdr 手工核一遍
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 静态分析无需沙箱;跨架构目标(ARM/MIPS/RISC-V)用 `readelf -h -l` 不依赖本机架构,反汇编才需要交叉工具
|
||||
- 与 [[re-imports]](导入导出/劫持面)、[[re-triage]](初勘)配合使用
|
||||
Reference in New Issue
Block a user