修复objcopy输出二进制文件的ELF段地址问题
问题
尝试将编译后的代码注入ELF可执行文件,策略是在代码段末尾写入字节(选择该段因需执行载荷),当段间padding不足时用objcopy --update-section <name>=<file>扩展段。以.fini段为宿主,其后是.rodata段。执行objcopy后,代码段的文件大小和内存镜像大小正确,段和节偏移也已按需修补,代码存在于预期偏移(经objdump确认)。但所有段的虚拟地址未被修补,导致代码段与后续节/段重叠,在_dl_start期间触发段错误(代码被视为位于只读段)。
objcopy前的readelf输出片段
Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [16] .fini PROGBITS 00000000000a3e84 000a3e84 0000000000000009 0000000000000000 AX 0 0 4 [17] .rodata PROGBITS 00000000000a4000 000a4000 000000000003b488 0000000000000000 A 0 0 32 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000063000 0x0000000000063000 0x0000000000063000 0x0000000000040e8d 0x0000000000040e8d R E 0x1000 LOAD 0x00000000000a4000 0x00000000000a4000 0x00000000000a4000 0x0000000000043ccc 0x0000000000043ccc R 0x1000
objcopy后的readelf输出片段
Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [16] .fini PROGBITS 00000000000a3e84 000a3e84 00000000000002bd 0000000000000000 AX 0 0 4 [17] .rodata PROGBITS 00000000000a4000 000a5000 000000000003b488 0000000000000000 A 0 0 32 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000063000 0x0000000000063000 0x0000000000063000 0x0000000000041141 0x0000000000041141 R E 0x1000 LOAD 0x00000000000a5000 0x00000000000a4000 0x00000000000a4000 0x0000000000043ccc 0x0000000000043ccc R 0x1000
系统环境:Linux 6.1.0-13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.55-1 (2023-09-29) x86_64 GNU/Linux
需要解决:如何进一步修补该二进制文件,使其能正常运行且保持有效的ELF格式?
解决方案
1. 修正后续LOAD段的虚拟地址
从输出可见,第二个LOAD段(只读段)的文件偏移已被调整为0x000a5000,但虚拟地址仍为原0x000a4000,导致与第一个LOAD段(可执行段)的内存范围重叠(第一个LOAD段内存结束地址为0x00063000 + 0x00041141 = 0x000a4141,与第二个LOAD段起始地址0x000a4000重叠)。
操作步骤:
- 用
readelf -l确认所有LOAD段的详细信息 - 使用二进制编辑工具(如
hexedit、bvi)或ELF处理脚本(基于pyelftools),将第二个LOAD段的VirtAddr和PhysAddr修改为0x000a5000,使其与文件偏移一致,避免内存范围重叠。
2. 同步修正后续节的虚拟地址
.rodata节的虚拟地址仍为0x000a4000,需同步修改为0x000a5000,与所属LOAD段的虚拟地址、文件偏移匹配。同时检查.rodata之后的所有节,将它们的Address字段增加对应的偏移量(此处为0x1000),确保所有节的内存地址落在所属LOAD段范围内。
3. 修复重定位条目(若存在)
如果是动态链接的可执行文件,需检查重定位表中指向.rodata或后续节的条目:
- 用
readelf -r查看重定位信息 - 将相关条目的地址同步增加对应的偏移量,避免程序运行时访问错误地址。
4. 替代策略:新增可执行节
若不想手动修补,可换用更可控的方式注入代码:
- 用
objcopy --add-section .new_code=payload.bin --set-section-flags .new_code=alloc,exec,load添加新的可执行节 - 确保新节被包含在第一个LOAD段内,再调整后续LOAD段和节的地址,或使用自定义链接脚本通过
ld重新链接。
5. 验证修补结果
完成后用以下命令验证有效性:
readelf -S:检查所有节的Address和Offset是否匹配所属LOAD段范围readelf -l:确认LOAD段无内存重叠,虚拟地址与文件偏移对应关系正确objdump -d:确认注入代码位于正确的虚拟地址- 直接运行可执行文件,验证是否能正常启动并执行注入代码。
内容的提问来源于stack exchange,提问作者matteobu02

