如果全局变量的地址在 ELF 中被硬编码,那么它们如何随机化?

How can global variables' addresses be randomized if they're hardcoded inside the ELF?

我在几个地方读到 ASLR 应该在每次程序 运行 时在随机地址加载 .data 部分,这意味着全局变量的地址应该不同.但是,如果我有以下代码:

int global_var = 42;

int main()
{
    global_var = 10;
    return 0;
}

我用gcc -fpie -o global global.c编译它,objdump -d -M intel显示如下:

  4004ed:   55                      push   rbp
  4004ee:   48 89 e5                mov    rbp,rsp
  4004f1:   c7 05 3d 0b 20 00 0a    mov    DWORD PTR [rip+0x200b3d],0xa        # 601038 <global_var>

看来 global_var 将始终放置在 601038。实际上,如果我使用调试符号进行编译,global_var 的 DIE 会硬编码该地址:

$ gcc -ggdb3 -fpie -o global global.c
$ objdump --dwarf=info global
...
<1><55>: Abbrev Number: 4 (DW_TAG_variable)
   <56>   DW_AT_name        : (indirect string, offset: 0x30c): global_var  
   <5a>   DW_AT_decl_file   : 1 
   <5b>   DW_AT_decl_line   : 1 
   <5c>   DW_AT_type        : <0x4e>    
   <60>   DW_AT_external    : 1 
   <60>   DW_AT_location    : 9 byte block: 3 38 10 60 0 0 0 0 0    (DW_OP_addr: 601038)

ASLR 在这些情况下如何工作?

当您编译 PIE 时,该文件在技术上实际上是一个共享对象(ET_DYN,您可以使用 readelf -h filename 进行检查)。这种类型的 ELF 文件(PIE 和 .so 文件)被设计为可在任何基地址 加载 (好吧,通常以页面大小为模)。

对于那些文件,虚拟地址(在节头 table、程序头 table、符号 table、DWARF DIE 等中给出)是从这个基址。

这个解释in the System V ABI:

the virtual addresses in the program headers might not represent the actual virtual addresses of the program’s memory image. Executable files typically contain absolute code. [...] On the other hand, shared object segments typically contain position-independent code. This lets a segment’s virtual address change from one process to another, without invalidating execution behavior. Though the system chooses virtual addresses for individual processes, it maintains the segments’ relative positions Because position-independent code uses relative addressing between segments, the difference between virtual addresses in memory must match the difference between virtual addresses in the file. The difference between the virtual address of any segment in memory and the corresponding virtual address in the file is thus a single constant value for any one executable or shared object in a given process. This difference is the base address.

对于 DWARF,这在 section 7.3 of DWARF 4:

中有解释

The relocated addresses in the debugging information for an executable object are virtual addresses and the relocated addresses in the debugging information for a shared object are offsets relative to the start of the lowest region of memory loaded from that shared object.

由于这些文件可以映射到任何基地址,因此可以随机化该基地址。

反汇编的指令输出为您提供 601038 作为相对于任意基数 (0x400000) 的便利,但请阅读实际指令;它正在写入 DWORD PTR [rip+0x200b3d]rip 是指令指针。代码和数据相对于彼此处于固定偏移量;随机化基地址不会改变这一点。通过使用指令指针加载,它正在使用一个已经包含 ASLR 重定位的地址。

描述中方便映射到601038是因为分散在整个代码中的rip的固定偏移量都依赖于指令所在的位置,所以如果不进行比较就无法比较指令位置的调整;反汇编器虽然知道指令偏移量,因此它可以减去该指令偏移量以获得公共 0x400000 基址的全局可比较地址。