ELF 重定位实战:5 个 LinkLab 实验阶段解析符号表与节偏移计算

ELF 重定位实战:5 个 LinkLab 实验阶段解析符号表与节偏移计算

ELF 重定位深度实战:从符号表解析到二进制修补的完整指南

在计算机系统底层开发中,理解 ELF 文件的重定位机制是掌握程序链接过程的关键。本文将带您深入探索 LinkLab 实验的五个阶段,通过实际操作演示如何解析符号表、计算节偏移以及修改重定位条目,最终形成一套完整的 ELF 文件分析与修改方法论。

1. ELF 重定位基础与实验环境搭建

ELF(Executable and Linkable Format)是现代 Linux 系统中最常用的二进制文件格式,它包含了程序运行所需的代码、数据以及元信息。理解 ELF 重定位,首先要掌握几个核心概念:

  • 可重定位目标文件(.o 文件):包含未链接的机器代码,其中的符号引用尚未解析
  • 符号表(.symtab):记录所有定义的符号及其属性
  • 重定位条目(.rel.text/.rel.data):指示链接器如何修改符号引用

实验环境准备:

# 基础工具安装 sudo apt-get update sudo apt-get install -y binutils hexedit gcc # 验证工具版本 readelf --version | head -1 objdump --version | head -1

关键工具说明:

工具用途常用参数示例
readelf查看ELF文件结构信息-a(显示全部信息)
objdump反汇编和查看目标文件信息-d(反汇编代码段)
hexedit二进制文件编辑直接编辑文件
gcc编译和链接-o(指定输出文件名)

2. 阶段一:全局变量与数据节修改

第一阶段的核心任务是修改全局变量在.data节中的内容。以下是详细操作步骤:

  1. 使用readelf分析符号表
readelf -a phase1.o | grep -A5 "Symbol table"

典型输出示例:

Symbol table '.symtab' contains 18 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS phase1.c ... 8: 0000000000000000 16 OBJECT GLOBAL DEFAULT 3 g_data
  1. 定位.data节信息
readelf -S phase1.o | grep -A3 ".data"

输出示例:

[Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 3] .data PROGBITS 0000000000000000 00000060 0000000000000010 0000000000000000 WA 0 0 4
  1. 计算修改位置
.data节偏移 = 0x60 g_data在.data节中的偏移 = 0x11 实际修改位置 = 0x60 + 0x11 = 0x71
  1. 使用hexedit修改二进制
hexedit phase1.o

在0x71位置输入学号的ASCII码(如"23215150438"对应32 33 32 31 35 31 35 30 34 33 38)

  1. 验证修改结果
gcc -o linkbomb main.o phase1.o -no-pie ./linkbomb

关键提示:在修改.data节时,务必注意数据的字节序和对齐要求。x86架构采用小端字节序,但ELF文件中的偏移量总是以大端格式表示。

3. 阶段二:强弱符号解析与覆盖

第二阶段重点在于理解强弱符号的交互规则:

  • 强符号:已初始化的全局变量/函数
  • 弱符号:未初始化的全局变量(COMMON符号)

操作流程:

  1. 分析弱符号特征
readelf -a phase2.o | grep -A3 "g_myCharArray"

输出示例:

10: 0000000000000000 256 OBJECT WEAK DEFAULT COM g_myCharArray
  1. 创建强符号覆盖: 创建phase2_patch.c文件:
char g_myCharArray[256] = { 0x00, 0x00, /* 前0x11个字节填充0 */ /* 学号ASCII码 */ 0x32, 0x33, 0x32, 0x31, 0x35, 0x31, 0x35, 0x30, 0x34, 0x33, 0x38 };
  1. 编译与链接
gcc -c phase2_patch.c gcc -o linkbomb2 main.o phase2.o phase2_patch.o -no-pie
  1. 处理偏移问题: 当出现字符偏移时,可通过辅助程序计算实际偏移量:
// hack.c #include <stdio.h> int main() { char buf[256]; fgets(buf, sizeof(buf), stdin); printf("Actual output: %s\n", buf); return 0; }

技术细节:弱符号的Size字段表示其最小分配空间,而强符号的实际大小必须至少达到这个值,否则可能导致内存越界。

4. 阶段三:代码节注入与函数调用重定位

第三阶段涉及代码节的直接修改和函数调用重定位,这是最复杂的环节之一。以下是关键步骤:

  1. 分析函数调用关系
objdump -d linkbomb3

输出示例:

0000000000001149 <do_phase>: 1149: e8 d2 ff ff ff call 1120 <myFunc2> 114e: 48 89 c7 mov %rax,%rdi 1151: e8 ba ff ff ff call 1110 <myFunc1>
  1. 计算注入位置
readelf -S phase3.o | grep -A1 ".text"

输出示例:

[ 1] .text PROGBITS 0000000000000000 00000040 0000000000000031 0000000000000000 AX 0 0 1

注入起始地址 = 0x40(.text节偏移) + 0x31(do_phase函数偏移) = 0x71

  1. 构造机器指令
  • call myFunc2:相对调用,计算偏移量
    目标地址 = myFunc2地址 = 0x1b 当前指令结束地址 = 0x31 + 5 = 0x36 相对偏移 = 0x1b - 0x36 = -0x1b → 补码表示:0xff ff ff e5 完整指令:e8 e5 ff ff ff
  • mov %rax,%rdi:机器码为48 89 c7
  • call myFunc1:类似方法计算
  1. 二进制修补
hexedit phase3.o

在0x71位置依次写入构造的指令:

e8 e5 ff ff ff 48 89 c7 e8 c2 ff ff ff
  1. 修改数据节内容
readelf -S phase3.o | grep -A1 ".data"

.data节偏移 = 0x1a0 学号位置 = 0x1a0 + 0x11 = 0x1b1

深度解析:call指令使用PC相对寻址,其机器码格式为E8后跟4字节的相对偏移(目标地址 - 下条指令地址)。理解这一点对正确构造调用指令至关重要。

5. 阶段四与五:重定位表修复与符号交换

最后两个阶段涉及更复杂的重定位表操作:

阶段四关键步骤

  1. 识别异常重定位条目
readelf -r phase4.o

输出示例:

Relocation section '.rela.text' at offset 0x250 contains 3 entries: Offset Info Type Sym. Value Sym. Name + Addend 0000000000000000 000300000001 R_X86_64_64 0000000000000000 .data + 0 0000000000000000 000300000001 R_X86_64_64 0000000000000000 .data + 0 0000000000000000 000300000001 R_X86_64_64 0000000000000000 .data + 0
  1. 修正重定位偏移量: 根据objdump输出确定正确偏移量(0x6, 0x11, 0x19),在hexedit中修改0x250位置的数据。

阶段五关键步骤

  1. 调试分析符号引用
gdb ./linkbomb5 (gdb) b do_phase (gdb) run (gdb) x/s 0x601040
  1. 交换重定位条目
readelf -r phase5.o

找到两个相关条目的偏移量(通常在0x340附近),交换它们的符号引用。

6. 重定位类型速查与实战总结

常见重定位类型对比:

类型计算方式用途字节数
R_X86_64_32S + A32位绝对地址4
R_X86_64_PC32S + A - P32位PC相对地址4
R_X86_64_64S + A64位绝对地址8
R_X86_64_PLT32L + A - P过程链接表相对地址4

实战经验总结:

  1. 系统化分析方法

    • 始终从readelf -a开始全面分析ELF结构
    • 使用objdump -d交叉验证代码逻辑
    • 通过gdb动态调试确认运行时行为
  2. 常见问题解决

    • 段错误:通常由重定位地址计算错误导致
    • 输出乱码:检查数据节的偏移和编码
    • 链接失败:确认符号可见性和强弱关系
  3. 进阶技巧

    # 使用Python辅助计算重定位值 def calc_reloc(S, A, P, type): if type == 'R_X86_64_32': return S + A elif type == 'R_X86_64_PC32': return S + A - P # 其他类型...

通过这五个阶段的系统实践,我们不仅掌握了ELF重定位的核心机制,还建立了一套完整的二进制分析与修改方法论。这种底层技能在逆向工程、安全分析和系统编程等领域都具有极高的实用价值。