TL:DR:并非所有绝对地址的使用都会在非 PIE 可执行文件(ELF 类型 EXEC,而不是 DYN)中具有重定位信息。因此,内核的程序加载器无法找到所有这些来应用修复。
因此,无法为构建为非 PIE 的可执行文件追溯启用 ASLR。传统的可执行文件无法将自己标记为每次使用绝对地址时都具有重定位元数据,并且添加这样的功能也没有意义,因为如果您想要文本 ASLR,您只需构建一个 PIE。
因为 ELF 类型的 EXEC Linux 可执行文件保证在链接时加载/映射到链接器选择的固定基地址,所以在可执行文件中为内部符号创建符号表条目会浪费空间。所以工具链并没有做到这一点,也没有理由开始。这就是传统 ELF 可执行文件的设计方式; Linux 在 90 年代中期就从 a.out 切换到了 ELF,当时堆栈 ASLR 还没有出现,所以它并没有引起人们的关注。
例如的绝对地址static char buf[100]
可能嵌入在使用它的机器代码中的某个位置(如果我们谈论的是 32 位代码,或将地址放入寄存器的 64 位代码),但无法知道在哪里或多少次。
此外,特别是对于 x86-64,非 PIE 可执行文件的默认代码模型保证静态地址(文本/数据/bss)都将位于虚拟地址空间的低 2GiB 中,因此 32 位绝对有符号或无符号地址可以工作,以及rel32
位移可以从任何物体到达任何物体。这就是为什么非 PIE 编译器输出使用mov $symbol, %edi
(5 个字节)将地址放入寄存器中,而不是lea symbol(%rip), %rdi
(7 字节)。https://godbolt.org/z/89PeK1
因此,即使您确实知道每个绝对地址在哪里,您也只能在低 2GiB 中对其进行 ASLR,从而限制了您可以引入的熵位数。 (我认为 Windows 有一个模式:LargeAddressAware = no。但 Linux 没有。x86-64 Linux 中不再允许使用 32 位绝对地址?同样,PIE 是允许文本 ASLR 的更好方法,因此人们(发行版)如果想要它的好处,就应该为此进行编译。)
与 Windows 不同,Linux 不会花费大量精力来处理可以通过从源代码重新编译二进制文件来更好、更高效地处理的事情。
话虽这么说,GNU/Linuxdoes支持修复重定位64-bit即使在 PIC / PIE ELF 共享对象中也是绝对地址。这就是为什么像 NASM 这样的初学者代码mov rdi, BUFFER
甚至可以在共享库中工作:使用objdump -drwC -Mintel
查看有关在中使用该符号的重定位信息mov reg, imm64
操作说明。一个lea rdi, [rel BUFFER]
不需要任何重定位条目,如果BUFFER
不是一个全球性的符号。 (相当于Cstatic
.)
您可能想知道为什么元数据至关重要:
没有可靠的方法来搜索文本/数据以查找可能的绝对地址;误报是可能的. e.g. /usr/bin/ld
可能包含0x401000
作为 x86-64 可执行文件的默认起始地址。你不想要 ASLRld
的代码+数据也可以更改其默认值。或者该整数值可以在许多程序中以多种方式出现,例如作为位图。当然,x86-64 机器代码是可变长度的,因此在最常见的情况下甚至没有可靠的方法来区分操作码和立即操作数。
还有潜在的漏报。 x86 程序不太可能用多条指令在寄存器中构造绝对地址,但这当然是可能的。但在非 x86 代码中,这种情况很常见。
具有固定长度指令的RISC机器无法将32位地址放入32位指令中;就没有空间放其他东西了。因此,要从静态存储加载,绝对地址必须分成多个指令,例如 MIPSlui $t0, %hi(0x612300)
/ lw $t1, %lo(0x612300)($t0)
从绝对地址 0x612300 处的静态变量加载。 (asm 源中通常会有一个符号名称,但它不会出现在最终链接的二进制文件中,除非它是.globl
,所以我用数字作为提醒。)这样的指令不必成对出现;地址的相同高半部分可以由后续指令中对同一数组或结构的其他访问重用。