4

我正在对这个简单的 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
...

我在本文档中看到:http ://man7.org/linux/man-pages/man5/elf.5.html for PHDR:

数组元素(如果存在)指定程序头表本身在文件和程序内存映像中的位置和大小。这种段类型在一个文件中不能出现多次。此外,只有当程序头表是程序内存映像的一部分时,才会出现这种情况。如果它存在,它必须在任何可加载的段条目之前。

引用中的出现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 标头?它对链接和/或其他情况有用吗?似乎它在运行时根本没有使用,那么为什么我们会有这个?

4

2 回答 2

4

如果主程序是类型ET_EXEC(非 PIE),它可能在没有PT_PHDR. 主要用途PT_PHDR是能够将标头中的(未重定位的)地址与程序标头的实际运行时地址(由动态链接器通过AT_PHDR辅助向量中获得)进行比较,以确定加载 PIE 可执行文件的偏移量。

我不确定 glibc 的动态链接器的要求PT_PHDR是什么,但在 musl libc 中,我们只需要它来计算这个负载偏移量,否则根本不使用它。

于 2020-05-03T02:06:59.917 回答
3

我再次运行 readelf 以验证它仍然是有效的 ELF 文件:

请注意,虽然 ELF 有效,但它现在已损坏程序头表中的第 11 个条目(因为您没有减少程序头的计数)。

令人惊讶的是,该程序仍然执行得非常好。

这个程序不使用动态链接器的任何特性,所以你破坏了它的结构这一事实并没有表现出来。

现在尝试调用一些例程 from libc.so.6,或调用dlopenand dlsym,看看是否仍然有效。

查看 GLIBC 加载程序源 ( rtld.c ),它确实非常关心PT_PHDR.

于 2020-05-03T02:06:19.157 回答