是否有等同于 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 处选择)。
我一直在查看一些 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 处选择)。