是否有等同于 PE 基础重定位的 ELF?

Is there an ELF equivalent of PE base relocations?

我一直在查看一些 ELF 二进制文件的反汇编,我注意到了这一点:

   0000000000401020 <_start>:
      401020:   31 ed                   xor    ebp,ebp
      401022:   49 89 d1                mov    r9,rdx
      401025:   5e                      pop    rsi
      401026:   48 89 e2                mov    rdx,rsp
      401029:   48 83 e4 f0             and    rsp,0xfffffffffffffff0
      40102d:   50                      push   rax
      40102e:   54                      push   rsp
      40102f:   49 c7 c0 30 13 40 00    mov    r8,0x401330
      401036:   48 c7 c1 d0 12 40 00    mov    rcx,0x4012d0
      40103d:   48 c7 c7 72 12 40 00    mov    rdi,0x401272
      401044:   ff 15 a6 2f 00 00       call   QWORD PTR [rip+0x2fa6]        # 403ff0 <__libc_start_main@GLIBC_2.2.5>
      40104a:   f4                      hlt    
      40104b:   0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]

当调用 __libc_start_main 时,我们将这三个立即值作为参数通过寄存器传递。这些显然是在 __libc_start_main(包括 main)中调用的函数指针。但这些是虚拟地址,我的理解是二进制文件加载到内存时的实际映射地址和 运行 不一定相同。因此,这些函数指针可能无法反映它们在内存中的实际位置。

由于对 PE 文件更加熟悉,IMAGE_DIRECTORY_BASERELOC 部分为我们提供了 IMAGE_BASE_RELOCATION 结构,可帮助我们调整这些常量值以反映新的图像库。但是我没有看到 ELF 文件的任何等效项。我在这里错过了什么吗?加载 ELF 文件时如何固定这些地址?

and my understanding is that the actual mapped address of the binary when it's loaded into memory and running will not necessarily be the same.

不,从这些地址我们可以看出这是链接在ld默认基地址的non-PIE ELF可执行文件。这是一个 position-dependent 可执行文件。

可执行文件本身将始终加载到固定的虚拟地址,因此可以使用 32 位立即数而不是 RIP-relative LEA 将静态地址放入寄存器。可执行文件本身的 ASLR 是不允许/不可能的。

libc 是一个可以被 ALSRed 的 ELF "shared object",因此通过 GOT 中的指针调用 __libc_start_main。在这个 CRT 启动代码的 gcc 源代码中,这可能看起来像 call *__libc_start_main@GOTPCREL(%rip)(AT&T 语法)。

顺便说一句,我们可以看出这是 hand-written asm,因为错过了使用 7 字节 mov rdi, sign_extended_imm32(与 RIP-relative LEA 大小相同)而不是 5 字节的优化mov edi, imm32。 x86-64 系统 V ABI 中的默认 non-PIE code-model 将所有静态 code/data 放在虚拟地址 space 的低 2GiB 中,因此静态地址可以与零一起使用或 sign-extension 到 64 位。


ELF "executables" 可以加载到一个随机的基地址被称为PIE (Position Independent Executable)。在 ELF 细节方面,它们使用相同的 ELF "type" 作为共享库,因此它们实际上是具有 "entry point" 并被标记为可执行文件的 ELF 共享对象。

现代 Linux 发行版让 gcc 默认构建 PIE。请参阅 32-bit absolute addresses no longer allowed in x86-64 Linux?(可重定位的 ELF 共享对象可以重定位地址 space 中的任何位置,不限于低 2GiB,因此没有 relocation-type 用于 32 位绝对地址的运行时修正。)

有一个 64 位绝对地址的重定位类型,所以(function/code 指针的)跳转表仍然是可能的,10 字节的 mov rdi, imm64 也是如此,但效率低于RIP-relative LEA,即使它不是用于 ELF 程序加载器或动态链接器,也必须为这些重定位修改程序文本。

例如readelf -a /bin/ls

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x5ae0
...

注意类型字段:DYN,与 readelf -a /lib/libc.so.6 等实际库中的相同。入口点是一个相对地址,相对于它映射到的基地址。

一个 non-PIE 可执行文件(例如静态链接,或使用 -fno-pie -no-pie 构建)看起来像这样:

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x401000

注意 Type: EXEC 和绝对入口点(由 ld 在 link-time 处选择)。