4

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

int global_var = 42;

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

我用 编译它gcc -fpie -o global global.cobjdump -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's 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 是如何工作的?

4

2 回答 2

6

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

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

于 2016-04-20T20:05:42.033 回答
4

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

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

在 System V ABI 中有解释:

程序头中的虚拟地址可能不代表程序内存映像的实际虚拟地址。可执行文件通常包含绝对代码。[...] 另一方面,共享对象段通常包含与位置无关的代码。这允许段的虚拟地址从一个进程更改到另一个进程,而不会使执行行为无效。尽管系统为各个进程选择虚拟地址,但它维护了段的相对位置因为 位置无关代码使用 段之间的相对寻址,内存中虚拟地址之间的差异必须与文件中虚拟地址之间的差异相匹配. 因此,内存中任何段的虚拟地址与文件中相应的虚拟地址之间的差异是给定进程中任何一个可执行文件或共享对象的单个常量值。这个区别就是基地址。

对于 DWARF,这在 DWARF 4 的第7.3中进行了解释:

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

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

于 2016-04-20T20:00:07.383 回答