linux内存布局和ASLR下的可分配地址空间【转】
导语
64位下的linux地址空间虽然看起来虽然庞大2^64 但是实际上进行内核与用户空间的划分后, 包括ASLR以及PIE等机制的启用, 实际留给mmap和brk的可分配区域远远小于这个值, 大约是42T的可用地址空间. 本文根据内核代码的默认宏定义进行了X86-64下的布局分析, 给基于共享内存的用户空间选址给予一定的参考.
目录
LINUX内存布局
针对X86-64内核代码的分析
..1.1. 基本布局
- | - |
---|---|
内核地址空间范围 | [0XFFFF 0000 0000 0000, 0XFFFF FFFF FFFF FFFF] |
用户地址空间 | [0X0000 7FFF FFFF F000, 0X0000 0000 0000 0000] |
不规范地址空间 | 不属于内核或者用户的地址空间属于不规范地址空间 |
用户空间的大小由宏定义TASK_SIZE决定, 在X86-64下这个大小默认为2^47-4096(128T) 对应十六进制数为: 0X0000 7FFF FFFF F000 .
..1.2. 用户空间布局
- 用户空间布局 - |
---|
0x0 |
保留区 |
代码段(PLT代码表部分) |
代码段 |
数据段(GOT) 只读 |
数据段(.got.plt) 惰性加载机制 |
数据段(Data) |
数据段(BSS) |
堆空间(Heap) |
↓ |
未分配区域 |
↑ |
内存映射区域(mmap) |
栈空间(进程栈) |
TASK_SIZE |
ASLR地址空间随机化
地址空间配置随机加载(英语:Address space layout randomization,缩写ASLR,又称地址空间配置随机化、地址空间布局随机化)是一种防范内存损坏漏洞被利用的计算机安全技术。
ASLR通过随机放置进程关键数据区域的地址空间来防止攻击者能可靠地跳转到内存的特定位置来利用函数。现代操作系统一般都加设这一机制,以防范恶意程序对已知地址进行Return-to-libc攻击。
这些数据区域一般包括代码段 数据段 堆区 栈区 mmap 动态库等, 其中涉及代码段的随机一般需要代码位置无关化的支持(PIC PIE机制)
不同版本的操作系统和内核版本, 在ASLR的实现上会有细节的不同, 这里主要是根据目前的生产环境做的分析, 用于确认ASLR的在地址空间中对内存布局带来的扰动范围.
ASLR的设置与关闭
LINUX下常见的设置或关闭有方式:
设置randomize_va_space的等级
随机化虚拟地址空间的配置说明如下:
1
|
/proc/sys/kernel/randomize_va_space
|
echo 0 > /proc/sys/kernel/randomize_va_space
1
|
|
sysctl -w kernel.randomize_va_space=0
1
|
|
关闭ASLR:
set disable-randomization on
开启ASLR:
set disable-randomization off
查看:
show disable-randomization
1
|
|
2^47-4096 => 0x7fff ffff f000 约为128T
1
|
gdb调试程序默认会关闭aslr, 我们通过gdb运行一个程序, 然后对齐pmap可以得到如下内存分布:
|
00007ffff7ffc000 4K r—- ld-2.27.so
00007ffff7ffd000 4K rw— ld-2.27.so
00007ffff7ffe000 4K rw— [ anon ]
00007ffffffde000 132K rw— [ stack ]
ffffffffff600000 4K r-x– [ anon ]
1
|
|
栈地址
随机值大小为17G = 0x3fffff000
代码位置如下:
load_elf_binary -> setup_arg_page
1
|
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
|
1
|
|
MMAP映射区起始地址
1
|
load_elf_binary -> setup_new_exec -> arch_pick_mmap_layout ->arch_pick_mmap_base
|
mmap的起始计算为:
STACK_TOP - 栈最大长度 - 间隙 - 随机值
栈最小长度为128M
随机位数配置在/proc/sys/vm/mmap_rnd_bits default=28
默认随机最大值为 0xFFFFFFF000 大约为1T
用户空间起始地址0x7FFFFFFFF000
1
|
|
代码段开始地址
ELF文件如果是普通的EXEC类型则会使用指定的入口地址下面讨论PIE编译出的DYN可执行文件
其加载地址为 DEFAULT_MAP_WINDOW /32上增加一个arch_mmap_rnd随机值
DEFAULT_MAP_WINDOW /32 = 0x555555554AAA
同mmap一样为 0x00FFFFFFF000 约1个T大小
代码段起始位置约为84T 随机值 1T
代码段数据段等整体随机
1
|
if (interpreter) {
|
1
|
loc->elf_ex.e_entry += load_bias;
|
HEAP区开始地址
brk从BSS结束地址开始, 会有一个额外的随机arch_randomize_brk
为固定的大小范围0x02000000, 大约为33M
1
|
if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
|
1
|
unsigned long arch_randomize_brk(struct mm_struct *mm)
|
结论部分
假定编译出来的是PIE类型的ELF并且全开ASLR设置
那么在mmap和heap之间的地址空间大小大约是
128T - 17G - 1T - 84T - 1T -33M = 42T
mmap最小起始地址略小于0x0000 7F00 0000 0000
brk起始地址最大略大于 0x0000 5655 5555 5555
这个是以T为单位的粗略计算, 考虑到计算时忽略了一些小的单位(GB级别或者更小), 包括间隙 小的随机值, 以及动态库 数据段代码段本身占用的空间, 这里可以做一个进一步的保守计算来使用这个空间.
计算这个空间的意义在于, 例如我们对共享内存使用一个固定的地址时, 需要避免和系统本身的动态分配的地址空间相冲突, 而计算出来的地址空间的确定可以保证这一点, 例如可以用这两个地址做一个中值计算, 把这个中值作为安全的绝对地址使用.
结论部分补充验证数据
- 没有开启PIE和ASLR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
310000000000400000 4K r-x-- a.out (deleted)
0000000000600000 4K r---- a.out (deleted)
0000000000601000 40964K rw--- a.out (deleted)
0000000002e02000 132K rw--- [ anon ]
00007ffff70f2000 1732K r-x-- libc-2.27.so
00007ffff72a3000 2044K ----- libc-2.27.so
00007ffff74a2000 16K r---- libc-2.27.so
00007ffff74a6000 8K rw--- libc-2.27.so
00007ffff74a8000 16K rw--- [ anon ]
00007ffff74ac000 92K r-x-- libgcc_s.so.1
00007ffff74c3000 2044K ----- libgcc_s.so.1
00007ffff76c2000 4K r---- libgcc_s.so.1
00007ffff76c3000 4K rw--- libgcc_s.so.1
00007ffff76c4000 1608K r-x-- libm-2.27.so
00007ffff7856000 2044K ----- libm-2.27.so
00007ffff7a55000 4K r---- libm-2.27.so
00007ffff7a56000 4K rw--- libm-2.27.so
00007ffff7a57000 1480K r-x-- libstdc++.so.6.0.25
00007ffff7bc9000 2048K ----- libstdc++.so.6.0.25
00007ffff7dc9000 40K r---- libstdc++.so.6.0.25
00007ffff7dd3000 8K rw--- libstdc++.so.6.0.25
00007ffff7dd5000 12K rw--- [ anon ]
00007ffff7dd8000 148K r-x-- ld-2.27.so
00007ffff7fd7000 24K rw--- [ anon ]
00007ffff7ff8000 8K r---- [ anon ]
00007ffff7ffa000 8K r-x-- [ anon ]
00007ffff7ffc000 4K r---- ld-2.27.so
00007ffff7ffd000 4K rw--- ld-2.27.so
00007ffff7ffe000 4K rw--- [ anon ]
00007ffffffde000 132K rw--- [ stack ]
ffffffffff600000 4K r-x-- [ anon ]
- 开启PIE和ASLR后多次测试得到的一个接近最小可分配空间的内存分布结果如下:
代码段从0x0000 5640开始
mmap的则从0x 0000 7eff开始而不是概率更大的0x 0000 7f** 这样的地址1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
3200005640b5092000 4K r-x-- a.out
00005640b5292000 4K r---- a.out
00005640b5293000 40964K rw--- a.out
00005640b99f2000 132K rw--- [ anon ]
00007effd7734000 20484K rw--- [ anon ]
00007effd8b35000 1732K r-x-- libc-2.27.so
00007effd8ce6000 2044K ----- libc-2.27.so
00007effd8ee5000 16K r---- libc-2.27.so
00007effd8ee9000 8K rw--- libc-2.27.so
00007effd8eeb000 16K rw--- [ anon ]
00007effd8eef000 92K r-x-- libgcc_s.so.1
00007effd8f06000 2044K ----- libgcc_s.so.1
00007effd9105000 4K r---- libgcc_s.so.1
00007effd9106000 4K rw--- libgcc_s.so.1
00007effd9107000 1608K r-x-- libm-2.27.so
00007effd9299000 2044K ----- libm-2.27.so
00007effd9498000 4K r---- libm-2.27.so
00007effd9499000 4K rw--- libm-2.27.so
00007effd949a000 1480K r-x-- libstdc++.so.6.0.25
00007effd960c000 2048K ----- libstdc++.so.6.0.25
00007effd980c000 40K r---- libstdc++.so.6.0.25
00007effd9816000 8K rw--- libstdc++.so.6.0.25
00007effd9818000 12K rw--- [ anon ]
00007effd981b000 148K r-x-- ld-2.27.so
00007effd9a1e000 24K rw--- [ anon ]
00007effd9a3f000 4K r---- ld-2.27.so
00007effd9a40000 4K rw--- ld-2.27.so
00007effd9a41000 4K rw--- [ anon ]
00007ffd922c7000 132K rw--- [ stack ]
00007ffd92385000 8K r---- [ anon ]
00007ffd92387000 8K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]