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

时间:2016-04-20 19:02:25

标签: c elf dwarf aslr

我已经在一些地方读过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如何在这些情况下工作?

2 个答案:

答案 0 :(得分:4)

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

说明中对601038的便利映射是因为散布在整个代码中的rip的固定偏移量都取决于指令所在的位置,因此它们在没有制作的情况下无法比较调整指令位置;反汇编程序知道指令偏移量,因此它可以减去该指令偏移量,以获得公共0x400000基数的全局可比地址。

答案 1 :(得分:1)

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

对于这些文件,虚拟地址(在标题表,程序标题表,符号表,DWARF DIE等中给出)是与此基址的偏移量。

这解释为in the System V ABI

  

程序头中的虚拟地址可能不代表   程序内存映像的实际虚拟地址。   可执行文件通常包含绝对代码。 [...]   另一方面,共享对象段通常包含   与位置无关的代码。   这可以让段的虚拟地址发生变化   一个进程到另一个进程,而不会使执行行为无效。   虽然系统为个人选择虚拟地址   过程中,它维持了细分市场的相对位置   因为与位置无关的代码使用相对寻址   段之间,虚拟地址之间的差异   在内存中必须匹配虚拟地址之间的差异   在文件中。任何虚拟地址的区别   内存中的段和相应的虚拟地址   因此,文件中的任何一个都是一个常量值   给定进程中的可执行文件或共享对象。   这个差异是基地址。

对于DWARF,section 7.3DWARF 4中解释了这一点:

  

调试信息中重定位的地址   对于可执行对象是虚拟的   地址和重新定位的地址   调试信息对于共享对象是偏移量   相对于加载的内存的最低区域的开始   来自那个共享对象。

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