2

我知道 PIC 代码使 ASLR 随机化更加高效和容易,因为代码可以放置在内存中的任何位置而无需更改代码。但是,如果我根据维基百科重新定位的理解正确,动态链接器可以在运行时进行“修复”,因此可以定位符号,尽管代码不是与位置无关的。但是根据我在这里看到的许多答案,非 pic 代码不能ASLR部分除了堆栈(所以不能随机化程序入口点)。如果这是正确的,那么运行时修复的用途是什么,为什么我们不能在程序开始之前在运行时修复代码中的所有位置以使程序入口点随机化。

4

2 回答 2

2

在了解 Linux 之前,让我们先了解一下 Windows:

Windows 的.EXE文件(程序)通常有一个所谓的“基本重定位表”,它们有一个“映像库”。

“镜像库”是程序“想要的”起始地址;如果 Windows 将程序加载到该地址,则无需进行重定位。

“基本重定位表”包含程序中表示地址的所有值的列表。如果程序加载到与“图像库”不同的地址,Windows 必须将差异添加到该表中列出的所有值。

如果.EXE文件不包含“基本重定位表”(据我所知,某些 32 位 GCC 版本会生成此类文件),则无法将文件加载到另一个地址。

someVariable这是因为如果变量位于地址 12340000,以下 C 代码语句将产生完全相同的机器代码(二进制代码) ,并且无法区分它们:

long myVariable = 12340000;

和:

int * myVariable = &someVariable;

在第一种情况下,值 12340000 在任何情况下都不得更改;在第二种情况下,如果将程序加载到另一个地址,则必须将地址(即 12340000)更改为实际地址。

如果缺少“基本重定位表”,则没有信息 12340000 是整数值(不得更改)还是地址(必须更改)。

所以程序必须加载到某个固定地址。

我不确定最新的 32 位 Linux 版本,但至少在较旧的 32 位 Linux 版本中,没有像“基本重定位表”这样的东西,并且程序不使用 PIC。这意味着必须将这些程序加载到它们“最喜欢的”地址。

我不了解 64 位 Linux 程序,但如果一个程序的编译方式与(较旧的)32 位程序相同,它们也必须加载到某个地址,并且 ASLR 是不可能的。

于 2020-10-07T20:37:47.303 回答
2

TL:DR:并非所有绝对地址的使用都会在非 PIE 可执行文件(ELF 类型 EXEC,而不是 DYN)中具有重定位信息。 因此内核的程序加载器无法找到它们来应用修复。

因此,无法为构建为非 PIE 的可执行文件追溯启用 ASLR。传统的可执行文件无法在每次使用绝对地址时将自己标记为具有重定位元数据,并且添加这样的功能也没有意义,因为如果您想要文本 ASLR,您只需构建一个 PIE。

因为 ELF 类型的 EXEC Linux 可执行文件保证在链接时加载/映射到链接器选择的固定基地址,所以在可执行文件中为内部符号创建符号表条目将是浪费空间。所以工具链没有这样做,也没有理由开始。这就是传统 ELF 可执行文件的设计方式;早在 90 年代中期,Linux 就从 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/Linux确实支持64 位绝对地址的修复重定位,即使在 PIC / PIE ELF 共享对象中也是如此。这就是为什么像 NASM 这样的初学者代码mov rdi, BUFFER甚至可以在共享库中工作的原因:用于objdump -drwC -Mintel查看有关在mov reg, imm64指令中使用符号的重定位信息。如果不是全局符号,lea rdi, [rel BUFFER]则不需要任何重定位条目。BUFFER(相当于 C static。)


您可能想知道为什么元数据是必不可少的:

没有可靠的方法来搜索可能的绝对地址的文本/数据;误报是可能的。例如,/usr/bin/ld可能包含0x401000x86-64 可执行文件的默认起始地址。您不希望ld's code+data 的 ASLR 也更改其默认值。或者该整数值可能在许多程序中以多种方式出现,例如作为位图。当然,x86-64 机器码是可变长度的,因此在最一般的情况下,甚至没有可靠的方法来区分操作码和立即操作数。

还有潜在的假阴性。x86 程序不太可能在具有多条指令的寄存器中构造绝对地址,但这当然是可能的。但是在非 x86 代码中,这很常见。

具有固定长度指令的 RISC 机器无法将 32 位地址放入 32 位指令中;就没有其他空间了。因此,要从静态存储中加载,必须将绝对地址拆分为多个指令,例如 MIPS lui $t0, %hi(0x612300)/lw $t1, %lo(0x612300)($t0)从绝对地址 0x612300 处的静态变量加载。(asm 源代码中通常会有一个符号名称,但它不会出现在最终链接的二进制文件中,除非它是.globl,所以我使用数字作为提醒。)这样的指令不必成对出现;在以后的指令中,相同的地址的高半部分可以被其他访问相同的数组或结构重用。

于 2020-10-07T22:36:00.350 回答