修改静态链接ELF程序头拼接虚拟内存页失败,如何实现?
问题描述
我正在修补静态链接的ELF可执行文件,希望从二进制文件的多个不同部分创建虚拟地址空间中的内存页。以下是我想要实现的简单示例可视化(|标记内存页边界):
file: |aabb|ccdd| mem: |aadd|
该内存页的前半部分来自文件第一个页大小范围的前半段,后半部分来自文件第二个页大小范围的后半段,确保各部分在自身范围内对齐正确。
我是否可以实现这一需求?
我修改了二进制文件的程序头表(通过readelf -l打印):
Elf file type is EXEC (Executable file) Entry point 0x401520 There are 12 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000560000 0x0000000000400000 0x0000000000400000 0x0000000000000040 0x0000000000000040 R 0x1000 LOAD 0x0000000000001270 0x0000000000400270 0x0000000000400270 0x00000000000002c8 0x00000000000002c8 R 0x1000 LOAD 0x0000000000002000 0x0000000000401000 0x0000000000401000 0x0000000000075ac1 0x0000000000075ac1 R E 0x1000 LOAD 0x0000000000078000 0x0000000000477000 0x0000000000477000 0x0000000000027053 0x0000000000027053 R 0x1000 LOAD 0x000000000009f7d8 0x000000000049f7d8 0x000000000049f7d8 0x0000000000005ab8 0x000000000000b288 RW 0x1000 LOAD 0x0000000000000000 0x00000000004ab000 0x00000000004ab000 0x00000000000002e0 0x00000000000002e0 R 0x1000 NOTE 0x0000000000001270 0x0000000000400270 0x0000000000400270 0x0000000000000040 0x0000000000000040 R 0x8 NOTE 0x00000000000012b0 0x00000000004002b0 0x00000000004002b0 0x0000000000000044 0x0000000000000044 R 0x4 TLS 0x000000000009f7d8 0x000000000049f7d8 0x000000000049f7d8 0x0000000000000018 0x0000000000000060 R 0x8 GNU_PROPERTY 0x0000000000001270 0x0000000000400270 0x0000000000400270 0x0000000000000040 0x0000000000000040 R 0x8 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x000000000009f7d8 0x000000000049f7d8 0x000000000049f7d8 0x0000000000003828 0x0000000000003828 R 0x1
如你所见,我尝试从文件的两个独立“页”拼接虚拟地址0x400000处的内存页:前0x40字节来自文件偏移0x560000的“页”,剩余部分来自文件偏移0x1000的“页”。
当我在GDB中运行该可执行文件时,info proc mappings的输出如下:
Start Addr End Addr Size Offset Perms objfile 0x400000 0x401000 0x1000 0x1000 r--p <the executable> 0x401000 0x477000 0x76000 0x2000 r-xp <the executable> 0x477000 0x49f000 0x28000 0x78000 r--p <the executable> 0x49f000 0x4a6000 0x7000 0x9f000 rw-p <the executable> 0x4a6000 0x4ab000 0x5000 0x0 rw-p 0x4ab000 0x4ac000 0x1000 0x0 r--p <the executable> 0x7ffff7ff9000 0x7ffff7ffd000 0x4000 0x0 r--p [vvar] 0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x0 r-xp [vdso] 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 rw-p [stack] 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 --xp [vsyscall]
看起来我的第一个程序头(phdr)条目没有生效。请问如何实现我的需求?
解决方案
你的第一个LOAD程序头未生效的核心原因是:ELF加载器会合并或覆盖重叠的虚拟地址范围,且第二个LOAD段(VirtAddr=0x400270)的地址范围完全落在0x400000-0x401000内,直接覆盖了第一个段的内容。此外,部分加载器对跨页拼接的LOAD段组合有严格限制。
要实现目标需求,有两种可行路径:
1. 预拼接文件内容,用单个LOAD段映射(推荐)
这是兼容性最好的方法,完全符合ELF加载器的预期:
- 在ELF文件中创建一个新的页对齐文件偏移(比如
0xXX0000),将文件偏移0x560000的前0x40字节,与文件偏移0x1000的后0xFC0字节复制到这个新页中。 - 添加一个LOAD段:
Offset设为新页起始地址,VirtAddr=0x400000,FileSiz=MemSiz=0x1000,权限R,Align=0x1000。 - 调整或删除原第二个LOAD段(
VirtAddr=0x400270),确保其虚拟地址不与0x400000-0x401000重叠。
2. 拆分虚拟页为多个对齐的LOAD段(兼容性有限)
部分Linux内核的ELF加载器支持将单个虚拟页拆分为多个LOAD段,前提是满足VirtAddr - Offset为Align的倍数:
- 第一个LOAD段:
Offset=0x560000,VirtAddr=0x400000,FileSiz=0x40,MemSiz=0x40,权限R,Align=0x1000(验证:0x400000 - 0x560000 = -0x160000,是0x1000的倍数)。 - 第二个LOAD段:
Offset=0x1040(文件偏移0x1000的0x40偏移处),VirtAddr=0x400040,FileSiz=0xFC0,MemSiz=0xFC0,权限R,Align=0x1000(验证:0x400040 - 0x1040 = 0x3FF000,是0x1000的倍数)。 - 必须删除或调整原第二个LOAD段(
VirtAddr=0x400270),避免地址重叠。
注意:这种方法依赖加载器实现,部分系统可能不支持,因此优先选择第一种方案。
内容的提问来源于stack exchange,提问作者FoxCrafter
相关产品推荐
相关产品推荐

