我正在为这个简单的C ++程序进行二进制编程,以了解ELF的程序头:
int main(){ }
编译:
❯ make
g++ -O0 -fverbose-asm -no-pie -o main main.cpp
我使用readelf -l main
获得以下信息:
Elf file type is EXEC (Executable file)
Entry point 0x401020
There are 11 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x0000000000000268 0x0000000000000268 R 0x8
INTERP 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000004c0 0x00000000000004c0 R 0x1000
...
我在本文档中看到:PHDR的http://man7.org/linux/man-pages/man5/elf.5.html:
数组元素,如果存在,则指定局部程序头表本身的大小和大小,pro的文件和存储映像中公克。此细分类型可能不会出现超过一次写入文件。此外,只有在程序头表是内存映像的一部分该程序。如果存在,则必须在任何可加载的细分条目。
if present
的出现让我想知道如果我跳过PHDR标头会发生什么。我使用vim的十六进制编辑器使用main
来更改:%!xxd
的二进制布局(是请务必在保存之前运行:%!xxd -r
,否则它将不再是二进制文件):
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0200 3e00 0100 0000 2010 4000 0000 0000 ..>..... .@.....
00000020: 4000 0000 0000 0000 1839 0000 0000 0000 @........9......
至:
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0200 3e00 0100 0000 2010 4000 0000 0000 ..>..... .@.....
00000020: 7800 0000 0000 0000 1839 0000 0000 0000 @........9......
(仅更改第20个字节),以跳过PHDR标头的长度。我再次运行readelf
以确认它仍然是有效的ELF文件:
❯ readelf -l main
Elf file type is EXEC (Executable file)
Entry point 0x401020
There are 11 program headers, starting at offset 120
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
INTERP 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
...
令人惊讶的是,程序仍然可以完美执行。为什么我们甚至需要PHDR标头?对链接和/或其他情况有用吗?似乎在运行时根本没有使用过,所以为什么要放这个呢?
如果主程序的类型为ET_EXEC
(非PIE),则可以在没有PT_PHDR
的情况下运行。 PT_PHDR
的主要用途是将标头中的(未重定位)地址与程序标头的实际运行时地址(由动态链接器通过aux向量中的AT_PHDR
获得)进行比较,以确定偏移量PIE可执行文件已加载。
我不确定glibc对具有PT_PHDR
的动态链接器的要求是什么,但是在musl libc中,我们仅需要它来计算此负载偏移量,否则根本就不会使用它。
我再次运行readelf以确认它仍然是有效的ELF文件:
请注意,尽管它是有效的ELF,但它现在已经破坏了程序头表中的第11个条目(因为您没有减少程序头的数量)。
而且令人惊讶的是,程序仍然可以完美执行。
此程序不使用动态链接器的any功能,因此,您破坏了其结构的事实并不明显。
现在尝试从libc.so.6
调用某些例程,或调用dlopen
和dlsym
,看看是否仍然有效。
[查看GLIBC加载程序源(rtld.c),它确实非常关心PT_PHDR
,所以如果没有它,事情仍然可以工作,我会感到惊讶。