我在几个地方读到过,.data每次程序运行时,ASLR 应该以随机地址加载该部分,这意味着全局变量的地址应该不同。但是,如果我有以下代码:
int global_var = 42;
int main()
{
global_var = 10;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我用 编译它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>
Run Code Online (Sandbox Code Playgroud)
它似乎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)
Run Code Online (Sandbox Code Playgroud)
在这些情况下,ASLR 是如何工作的?
当您编译 PIE 时,该文件实际上在技术上是一个共享对象(ET_DYN,您可以使用 来检查readelf -h filename)。这种类型的 ELF 文件(PIE 和.so文件)被设计为可在任何基地址(通常以页面大小为模)加载。
对于这些文件,虚拟地址(在节头表、程序头表、符号表、DWARF DIE 等中给出)是相对于该基地址的偏移量。
\n\nSystem V ABI对此进行了解释:
\n\n\n\n\n程序头中的虚拟地址可能不代表程序内存映像的实际虚拟地址。\n 可执行文件通常包含绝对代码。[...]\n 另一方面,共享对象段通常包含\n 与位置无关的代码。\n 这使得段\xe2\x80\x99 的虚拟地址从\n 一个进程更改为\n 另一个进程,而不会导致执行无效行为。\n 尽管系统为各个\n 进程选择虚拟地址,但它会维护段\xe2\x80\x99 相对位置\n 因为与 位置无关的代码在段之间使用\n 相对寻址,所以虚拟地址之间的差异\n内存中的虚拟地址必须与文件中的虚拟地址之间的差异相匹配。因此,内存中任何段的虚拟地址与文件中相应虚拟地址之间的差异对于给定进程中的任何一个可执行文件或共享对象来说都是一个常量值。这个差异是基址。地址。
\n
对于 DWARF, DWARF 4的7.3 节对此进行了解释:
\n\n\n\n\n可执行对象的调试信息中的重定位地址是虚拟地址,共享对象的调试信息中的重定位地址是相对于加载的内存最低区域的起始位置的偏移量那个共享对象。
\n
由于这些文件可以映射到任何基地址,因此该基地址可以是随机的。
\n反汇编的指令输出为您601038提供了相对于任意基数 (0x400000) 的便利,但请阅读实际指令;它正在写信给DWORD PTR [rip+0x200b3d]. rip是指令指针。代码和数据相对于彼此处于固定偏移量;随机化基地址不会改变这一点。通过使用指令指针加载,它使用的地址已经包含了 ASLR 重定位。
描述中的方便映射601038是因为rip分散在整个代码中的固定偏移量都取决于指令所在的位置,因此如果不对指令位置进行调整,它们就没有可比性;反汇编器虽然知道指令偏移量,因此它可以减去该指令偏移量,以便您获得通用 0x400000 基址的全局可比较地址。