不同选项下的虚拟内存分布
Linux
系统下,
ELF
格式的可执行文件的各个段都会被分配到不同的虚拟内存空间中。在操作系统实现地址随机化机制(
Address Space Layout Randomization
)之前,程序在任意一次执行下,所使用的虚拟空间的地址往往是相同的。这就给恶意攻击者的攻击行为提供了很大的便利(见
Stack Smashing for Fun and Profit
)。以下述程序为例。
#include <stdio.h>
int local_global_var=0x10;
int local_global_func(void)
{
return 0x40;
}
int main(void)
{
int x = local_global_func();
local_global_var= 0x20;
while(getchar()!= 'q')
continue;
return0;
}
图
1
示例程序
exam1.c
程序包括一个全局函数,一个全局变量和一个局部变量。程序中的
while
语句等待用户的输入,当输入字符为
‘q’
时程序终止,这是为了方便在程序运行时观察不同的段所对应的不同内存。通过使用不同的编译选项,来观察它们对程序的虚拟内存的影响。选项分为
3
种情况:
1)
不使用任何特殊选项;
2)
使用
-fpic
选项。该选项通常被用来生成与位置无关动态库文件;
3)-fpie
选项,用于生成位置无关的代码段。
不使用任何特殊选项
不使用任何选项的情况下,在程序运行过程中,通过
Linux
命令
cat /proc/
pidnum
/maps
来输出程序的虚拟内存分布状况。其中
pidnum
是程序的进程号,通过
ps
命令获得。程序的两次不同执行下,其虚拟内存的分布如图
2
所示。
图中显示,程序
exam1
的代码段和数据段分别对应的虚拟地址为
00400000-00401000
和
00600000-00601000
,而且在两次不同的执行中,地址不变。堆栈段(
stack
)在两次执行中则有了变化,第一次为
7fff783b9000-7fff783da000
,第二次为
7fff7f27a000-7fff7f29b000
。这是因为操作系统的地址随机化机制,把堆栈段的地址随机化了,每次不同的执行都对应不同的堆栈段。
图
2
不使用任何选项所生成程序的两次不同执行
使用选项
-fpic
使用
-fpic
所编译生成的程序不是一个可执行程序,因此不能观察其执行时的虚拟内存分布状况,将在下一节讨论该选项的作用。
使用选项
-fpie
使用选项
-fpie(P
osition I
ndependent Executable)
生成的文件是可执行文件,其两次执行效果如图
3
所示。从图中我们可以看到,第一次执行时,程序的代码段和数据段所对应的虚拟地址分别为
7fb75116a000-7fb75116b000
和
7fb75136a000-7fb75136b000
。第二次执行时,对应的虚拟地址分别为
7f02313ac000-7f02313ad000
和
7f02315ac000-7f02315ad000
。与不加
-fpie
选项的情况不同,这两次执行,
elf
文件中的代码段被映射到了不同的虚拟内存地址上,这是因为编译器在该选项下可以生成位置无关的代码,从而在执行时,可以被装载到不同的地址空间。当代码、数据、
stack
和
heap
在每次执行时,都被随机化分配到不同的空间的时候,攻击者的难度无疑大大增加了。
图
3
使用
-fpie
选项所生成程序的两次不同执行
不同选项的重定位信息
使用选项
gcc -c exam1.c
,生成
exam1.o
,然后使用命令
objdump -d -r exam1.o
输出其反汇编的代码,并附带有重定位信息。输出代码如下。
000000000000000b <main>:
b: 55 push %rbp
c: 48 89 e5 mov %rsp,%rbp
f: 48 83 ec 10 sub $0x10,%rsp
13: e8 00 00 00 00 callq 18 <main+0xd>
14: R_X86_64_PC32 local_global_func-0x4
18: 89 45 fc mov %eax,-0x4(%rbp)
1b: c7 05 00 00 00 00 20 movl $0x20,0x0(%rip) # 25 <main+0x1a>
22: 00 00 00
1d: R_X86_64_PC32 local_global_var-0x8
使用选项gcc -c -fpic exam1.c,然后对生成的二进制文件反汇编,输出代码如下。
000000000000000b <main>:
b: 55 push %rbp
c: 48 89 e5 mov %rsp,%rbp
f: 48 83 ec 10 sub $0x10,%rsp
13: e8 00 00 00 00 callq 18 <main+0xd>
14: R_X86_64_PLT32 local_global_func-0x4
18: 89 45 fc mov %eax,-0x4(%rbp)
1b: 48 8b 05 00 00 00 00 mov 0x0(%rip),%rax # 22 <main+0x17>
1e: R_X86_64_GOTPCREL local_global_var-0x4
22: c7 00 20 00 00 00 movl $0x20,(%rax)
使用选项gcc -c -fpie exam1.c,然后对生成的二进制文件反汇编,输出代码如下。
000000000000000b <main>:
b: 55 push %rbp
c: 48 89 e5 mov %rsp,%rbp
f: 48 83 ec 10 sub $0x10,%rsp
13: e8 00 00 00 00 callq 18 <main+0xd>
14: R_X86_64_PC32 local_global_func-0x4
18: 89 45 fc mov %eax,-0x4(%rbp)
1b: c7 05 00 00 00 00 20 movl $0x20,0x0(%rip) # 25 <main+0x1a>
22: 00 00 00
1d: R_X86_64_PC32 local_global_var-0x8
比较3个变量的寻址方式和指令实现,结果如下表。
图中红色的
0x0
表示需要在链接时填入的偏移值。从图中可知,全局函数
local_global_func
在无特殊选项和
-fpie
情况下都是
R_X86_64_PC32
重定位类型的,在
-fpic
选项下,是
R_X86_64_PLT32
重定位类型的。而全局变量
local_global_var
与函数类似,也是在无特殊选项和
-fpic
选项下相同,都为
R_X86_64_PC32
重定位类型,只使用一条指令存储其数值(
0x20
)。在
-fpie
选项下为
R_X86_64_GOTPCREL
重定位类型,而且使用了两条指令,一条计算地址,另一条保存数值。
不同选项下生成可执行文件的比较
无特殊选项与
-fpie
选项对比
使用
objdump-x
输出文件头,分别如下所示。观察可知,主要差别在于两个地方:
1.
文件标志(
Fileflags
)
分别为
EXEC_P,HAS_SYMS, D_PAGED
和
HAS_SYMS,DYNAMIC, D_PAGED
2.
无特殊选项时,虚拟装载地址(
LOAD ADDR
)为400000;而在-fpie选项下,其虚拟装载地址为0x0。
-fpic与-fpie
这两个选项所产生的主要异同点如下。
1. 两者的文件标志都是DYNAMIC。
2. 装载地址都是0x0。
3. -fpic所产生的共享库文件没有PHDR和INTERP段。
无特殊选项
exam1_no: file format elf64-x86-64
exam1_no
architecture: i386:x86-64, flags 0x00000112:
EXEC_P
, HAS_SYMS, D_PAGED
start address 0x00000000004003c0
Program Header:
PHDR off 0x0000000000000040 vaddr 0x0000000000400040 paddr 0x0000000000400040 align 2**3
filesz 0x00000000000001c0 memsz 0x00000000000001c0 flags r-x
INTERP off 0x0000000000000200 vaddr 0x0000000000400200 paddr 0x0000000000400200 align 2**0
filesz 0x000000000000001c memsz 0x000000000000001c flags r–
LOAD off 0x0000000000000000 vaddr 0x0000000000400000
paddr 0x0000000000400000 align 2**21
filesz 0x00000000000006fc memsz 0x00000000000006fc flags r-x
LOAD off 0x0000000000000700 vaddr 0x0000000000600700 paddr 0x0000000000600700 align 2**21
filesz 0x000000000000022c memsz 0x0000000000000230 flags rw-
DYNAMIC off 0x0000000000000718 vaddr 0x0000000000600718 paddr 0x0000000000600718 align 2**3
filesz 0x00000000000001d0 memsz 0x00000000000001d0 flags rw-
NOTE off 0x000000000000021c vaddr 0x000000000040021c paddr 0x000000000040021c align 2**2
filesz 0x0000000000000020 memsz 0x0000000000000020 flags r–
EH_FRAME off 0x0000000000000604 vaddr 0x0000000000400604 paddr 0x0000000000400604 align 2**2
filesz 0x0000000000000034 memsz 0x0000000000000034 flags r–
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**3
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
-fpie
exam1_pie: file format elf64-x86-64
exam1_pie
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS,
DYNAMIC
, D_PAGED
start address 0x00000000000006b0
Program Header:
PHDR off 0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
filesz 0x00000000000001c0 memsz 0x00000000000001c0 flags r-x
INTERP off 0x0000000000000200 vaddr 0x0000000000000200 paddr 0x0000000000000200 align 2**0
filesz 0x000000000000001c memsz 0x000000000000001c flags r–
LOAD off 0x0000000000000000 vaddr 0x0000000000000000
paddr 0x0000000000000000 align 2**21
filesz 0x0000000000000a2c memsz 0x0000000000000a2c flags r-x
LOAD off 0x0000000000000a30 vaddr 0x0000000000200a30 paddr 0x0000000000200a30 align 2**21
filesz 0x000000000000026c memsz 0x0000000000000270 flags rw-
DYNAMIC off 0x0000000000000a48 vaddr 0x0000000000200a48 paddr 0x0000000000200a48 align 2**3
filesz 0x00000000000001d0 memsz 0x00000000000001d0 flags rw-
NOTE off 0x000000000000021c vaddr 0x000000000000021c paddr 0x000000000000021c align 2**2
filesz 0x0000000000000020 memsz 0x0000000000000020 flags r–
EH_FRAME off 0x0000000000000934 vaddr 0x0000000000000934 paddr 0x0000000000000934 align 2**2
filesz 0x0000000000000034 memsz 0x0000000000000034 flags r–
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**3
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
-fpic
exam1_pic: file format elf64-x86-64
exam1_pic
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS,
DYNAMIC
, D_PAGED
start address 0x00000000000005c0
Program Header:
LOAD off 0x0000000000000000 vaddr 0x0000000000000000
paddr 0x0000000000000000 align 2**21
filesz 0x0000000000000824 memsz 0x0000000000000824 flags r-x
LOAD off 0x0000000000000828 vaddr 0x0000000000200828 paddr 0x0000000000200828 align 2**21
filesz 0x0000000000000244 memsz 0x0000000000000248 flags rw-
DYNAMIC off 0x0000000000000840 vaddr 0x0000000000200840 paddr 0x0000000000200840 align 2**3
filesz 0x00000000000001c0 memsz 0x00000000000001c0 flags rw-
EH_FRAME off 0x0000000000000778 vaddr 0x0000000000000778 paddr 0x0000000000000778 align 2**2
filesz 0x0000000000000024 memsz 0x0000000000000024 flags r–
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**3
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-