我已经在一些地方读过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如何在这些情况下工作?
答案 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.3的DWARF 4中解释了这一点:
调试信息中重定位的地址 对于可执行对象是虚拟的 地址和重新定位的地址 调试信息对于共享对象是偏移量 相对于加载的内存的最低区域的开始 来自那个共享对象。
由于这些文件可以映射到任何基地址,因此该基地址可以随机化。