linux内存布局和地址空间布局随机化(ASLR)下的可分配地址空间
https://zsummer.github.io/2019/11/04/2019-11-04-aslr/
地址空间布局随机化(ASLR)
《mmap的随机化》
《Meltdown(熔断漏洞)- Reading Kernel Memory from User Space/KASLR | 原文+中文翻译》
导语
64位下的linux地址空间虽然看起来虽然庞大2^64 但是实际上进行内核与用户空间的划分后, 包括ASLR以及PIE等机制的启用, 实际留给mmap和brk的可分配区域远远小于这个值, 大约是42T的可用地址空间. 本文根据内核代码的默认宏定义进行了X86-64下的布局分析, 给基于共享内存的用户空间选址给予一定的参考.
目录
- 导语
- 目录
- LINUX内存布局
- ..1.1. 基本布局
- ..1.2. 用户空间布局
- ASLR地址空间随机化
- ASLR的设置与关闭
- 设置randomize_va_space的等级
- 进程个性化设置: 进程描述符的成员personality设置 ADDR_NO_RANDOMIZE
- GDB调试中打开或者关闭ASLR(默认会禁用)
- ASLR的设置与关闭
- 内核代码中相关的宏定义和随机值计算
- TASK_SIZE 定义 用户地址空间的大小
- 栈地址
- MMAP映射区起始地址
- 代码段开始地址
- HEAP区开始地址
- 结论部分
- 结论部分补充验证数据
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 2 3 4 5 6 7 8 9 10 11 12 |
/proc/sys/kernel/randomize_va_space 0 = Disabled 1 = Conservative Randomization 2 = Full Randomization ``` 总共分了三档, 不同版本其内容随着支持程度和等级划分略有不同, 默认启用的等级也不同, 以更具体的生产环境为准 大致如下: 0 = 关闭 1 = 保守随机化: 共享库 栈 mmap vdso随机化 2 = 完全随机化: 包括brk分配的内存 2.1 = 代码段和数据段的随机化需要PIE位置无关可执行程序的支持 编译链接时添加 -fpie -pie * 修改/关闭系统配置方式如下 |
echo 0 > /proc/sys/kernel/randomize_va_space
1 2 |
* 或者通过sysctl修改 |
sysctl -w kernel.randomize_va_space=0
1 2 3 4 5 6 7 8 9 |
##### 进程个性化设置: 进程描述符的成员personality设置 ADDR_NO_RANDOMIZE * setarch $(uname -m) -R [--addr-no-randomize] [target exe] * 例如 ldd ./benchmark_fast 在aslr环境下会看到每次so的内存位置都在变化 * setarch $(uname -m) -R ldd ./benchmark_fast 这样去查看则是固定不变的 ##### GDB调试中打开或者关闭ASLR(默认会禁用) |
关闭ASLR:
set disable-randomization on
开启ASLR:
set disable-randomization off
查看:
show disable-randomization
1 2 3 4 5 6 7 |
### 内核代码中相关的宏定义和随机值计算#### TASK_SIZE 定义 用户地址空间的大小 在X86的内核代码中, 默认的TASK_SIZE为 (1UL << 47) - 4096) 即 |
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 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
00007ffffffde000 + 132K = 0x7FFFFFFFF000 和内核代码中的宏定义一致```C++ #ifdef CONFIG_X86_32 /** User space process size: 3GB (default).*/ #define IA32_PAGE_OFFSET PAGE_OFFSET #define TASK_SIZE PAGE_OFFSET #define TASK_SIZE_LOW TASK_SIZE #define TASK_SIZE_MAX TASK_SIZE #define DEFAULT_MAP_WINDOW TASK_SIZE #define STACK_TOP TASK_SIZE #define STACK_TOP_MAX STACK_TOP#else#ifdef CONFIG_X86_5LEVEL #define __VIRTUAL_MASK_SHIFT (pgtable_l5_enabled() ? 56 : 47) #else #define __VIRTUAL_MASK_SHIFT 47 #endif#define TASK_SIZE_MAX ((1UL << __VIRTUAL_MASK_SHIFT) - PAGE_SIZE)#define DEFAULT_MAP_WINDOW ((1UL << 47) - PAGE_SIZE)/* This decides where the kernel will search for a free chunk of vm* space during mmap's.*/ #define IA32_PAGE_OFFSET ((current->personality & ADDR_LIMIT_3GB) ? \0xc0000000 : 0xFFFFe000)#define TASK_SIZE_LOW (test_thread_flag(TIF_ADDR32) ? \IA32_PAGE_OFFSET : DEFAULT_MAP_WINDOW) #define TASK_SIZE (test_thread_flag(TIF_ADDR32) ? \IA32_PAGE_OFFSET : TASK_SIZE_MAX) #define TASK_SIZE_OF(child) ((test_tsk_thread_flag(child, TIF_ADDR32)) ? \IA32_PAGE_OFFSET : TASK_SIZE_MAX)#define STACK_TOP TASK_SIZE_LOW #define STACK_TOP_MAX TASK_SIZE_MAX#define INIT_THREAD { \.addr_limit = KERNEL_DS, \ }extern unsigned long KSTK_ESP(struct task_struct *task);#endif /* CONFIG_X86_64 */ |
栈地址
随机值大小为17G = 0x3fffff000
代码位置如下:
load_elf_binary -> setup_arg_page
1 2 |
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),executable_stack); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#define __STACK_RND_MASK(is32bit) ((is32bit) ? 0x7ff : 0x3fffff) #define STACK_RND_MASK __STACK_RND_MASK(mmap_is_ia32())unsigned long randomize_stack_top(unsigned long stack_top) {unsigned long random_variable = 0;if (current->flags & PF_RANDOMIZE) {random_variable = get_random_long();random_variable &= STACK_RND_MASK;random_variable <<= PAGE_SHIFT;}return PAGE_ALIGN(stack_top) - random_variable; } |
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 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
#define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE) //0x7FFFFFFFF000 #define TASK_SIZE TASK_SIZE_MAX#define STACK_TOP TASK_SIZE #define STACK_TOP_MAX TASK_SIZE_MAX/* 1GB for 64bit, 8MB for 32bit */ #define __STACK_RND_MASK(is32bit) ((is32bit) ? 0x7ff : 0x3fffff) #define STACK_RND_MASK __STACK_RND_MASK(mmap_is_ia32())static unsigned long stack_maxrandom_size(unsigned long task_size) {unsigned long max = 0;if (current->flags & PF_RANDOMIZE) {max = (-1UL) & __STACK_RND_MASK(task_size == task_size_32bit());max <<= PAGE_SHIFT;}return max; }//task size为0x7FFFFFFFF000 //随机值为 random()& ((1UL << 28) -1) 个页面, 即0x00FFFFFFF000 void arch_pick_mmap_layout(struct mm_struct *mm, struct rlimit *rlim_stack) {mm->get_unmapped_area = arch_get_unmapped_area_topdown;arch_pick_mmap_base(&mm->mmap_base, &mm->mmap_legacy_base,arch_rnd(mmap64_rnd_bits), task_size_64bit(0),rlim_stack); //proc/sys/vm/mmap_rnd_bits default=28}// static unsigned long arch_rnd(unsigned int rndbits) {if (!(current->flags & PF_RANDOMIZE))return 0;return (get_random_long() & ((1UL << rndbits) - 1)) << PAGE_SHIFT; }/** 新布局直接调用*base = mmap_base(random_factor, task_size, rlim_stack); */ static void arch_pick_mmap_base(unsigned long *base, unsigned long *legacy_base,unsigned long random_factor, unsigned long task_size,struct rlimit *rlim_stack) {*legacy_base = mmap_legacy_base(random_factor, task_size);if (mmap_is_legacy())*base = *legacy_base;else*base = mmap_base(random_factor, task_size, rlim_stack); }//这是是随机值的上下限保护 栈至少要有128M //task size减去随机值, 再减去栈的大小, 栈的最小值为128M static unsigned long mmap_base(unsigned long rnd, unsigned long task_size,struct rlimit *rlim_stack) {unsigned long gap = rlim_stack->rlim_cur;unsigned long pad = stack_maxrandom_size(task_size) + stack_guard_gap;unsigned long gap_min, gap_max;/* Values close to RLIM_INFINITY can overflow. */if (gap + pad > gap)gap += pad;/** Top of mmap area (just below the process stack).* Leave an at least ~128 MB hole with possible stack randomization.*/gap_min = SIZE_128M;gap_max = (task_size / 6) * 5;if (gap < gap_min)gap = gap_min;else if (gap > gap_max)gap = gap_max;return PAGE_ALIGN(task_size - gap - rnd); }//max = (0x3fffff <<= 12) = 0x3fffff000 static unsigned long stack_maxrandom_size(unsigned long task_size) {unsigned long max = 0;if (current->flags & PF_RANDOMIZE) {max = (-1UL) & __STACK_RND_MASK(task_size == task_size_32bit());max <<= PAGE_SHIFT;}return max; } |
代码段开始地址
ELF文件如果是普通的EXEC类型则会使用指定的入口地址下面讨论PIE编译出的DYN可执行文件
其加载地址为 DEFAULT_MAP_WINDOW /32上增加一个arch_mmap_rnd随机值
DEFAULT_MAP_WINDOW /32 = 0x555555554AAA
同mmap一样为 0x00FFFFFFF000 约1个T大小
代码段起始位置约为84T 随机值 1T
代码段数据段等整体随机
1 2 3 4 5 6 7 |
if (interpreter) {load_bias = ELF_ET_DYN_BASE;if (current->flags & PF_RANDOMIZE)load_bias += arch_mmap_rnd();elf_flags |= MAP_FIXED;} elseload_bias = 0; |
1 2 3 4 5 6 7 |
loc->elf_ex.e_entry += load_bias; elf_bss += load_bias; elf_brk += load_bias; start_code += load_bias; end_code += load_bias; start_data += load_bias; end_data += load_bias; |
HEAP区开始地址
brk从BSS结束地址开始, 会有一个额外的随机arch_randomize_brk
为固定的大小范围0x02000000, 大约为33M
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {/** For architectures with ELF randomization, when executing* a loader directly (i.e. no interpreter listed in ELF* headers), move the brk area out of the mmap region* (since it grows up, and may collide early with the stack* growing down), and into the unused ELF_ET_DYN_BASE region.*/if (IS_ENABLED(CONFIG_ARCH_HAS_ELF_RANDOMIZE) &&loc->elf_ex.e_type == ET_DYN && !interpreter)current->mm->brk = current->mm->start_brk =ELF_ET_DYN_BASE;current->mm->brk = current->mm->start_brk =arch_randomize_brk(current->mm); #ifdef compat_brk_randomizedcurrent->brk_randomized = 1; #endif} |
1 2 3 4 |
unsigned long arch_randomize_brk(struct mm_struct *mm) {return randomize_page(mm->brk, 0x02000000); } |
结论部分
假定编译出来的是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 31
0000000000400000 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 32
00005640b5092000 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 ]
linux内存布局和地址空间布局随机化(ASLR)下的可分配地址空间相关推荐
- 内存安全 - 地址空间布局随机化(ASLR)
说明 学过编译原理可知,C语言程序中所有变量的内存地址编译后都是确定了的,但是在linux平台上实际使用时可以发现变量的内存地址并不是固定的,如下: * 示例代码 #include <stdio ...
- 关闭aslr oracle,地址空间布局随机化 (Address Space Layout Randomization, ASLR)
地址空间布局随机化 (Address Space Layout Randomization, ASLR) ASLR 是 Oracle Solaris 系统的一种功能,利用此功能可以随机生成进程地址空间 ...
- 关于Windows上地址空间布局随机化防御机制的分析(下)
将32位程序重新编译为64位程序,以使地址空间布局随机化更有效 尽管Windows的64位版本已经成为主流十多年了,但32位版本的使用者仍然很多.有些程序确实需要保持与第三方插件的兼容性,比如web浏 ...
- mini2440 linux 内存布局
mini2440 linux 内存布局 在学习linux内存寻址的过程中,注意到在x86架构上,分段与分页机制共存.而在RSIC体系结构下一般只支持分页.<深入理解linux内核>是在x8 ...
- linux内存布局及页面映射
在Linux系统中,以32bit x86系统来说,进程的4GB内存空间(虚拟地址空间)被划分成为两个部分 ------用户空间和内核空间,大小分别为0-3G,3-4G. 用户进程通常情况下,只能访问用 ...
- linux内存映射起始地址,内存初始化代码分析(三):创建系统内存地址映射
内存初始化代码分析(三):创建系统内存地址映射 作者:linuxer 发布于:2016-11-24 12:08 分类:内存管理 一.前言 经过内存初始化代码分析(一)和内存初始化代码分析(二)的过渡, ...
- linux代码布局,Linux内存布局(示例代码)
在上一篇博文里,我们已经看到Linux如何有效地利用80x86的分段和分页硬件单元把逻辑地址转换为线性地址,在由线性地址转换到物理地址.那么我们的应用程序如何使用这些逻辑地址,整个内存的地址布局又是怎 ...
- linux内存布局的内核实现--用户空间的映射方式
引用牛人的一个表格(本人绘制能力实在有限,引自:http://blog.csdn.net/absurd/archive/2006/06/19/814268.aspx) Linux的内存模型,一般为: ...
- linux内存布局 zone,【原创】(八)Linux内存管理 - zoned page frame allocator - 3
背景 read the fucking source code! --by 鲁迅 a picture is worth a thousand words. --by 高尔基 说明: kernel版本: ...
最新文章
- [LeetCode] Remove Duplicates from Sorted Array II
- 功能测试——医疗管理系统
- ios PNG Crush error (PNG图片错误)
- macOS 使用手册
- 自动将存储过程转成C#代码的过程[转]
- evolution 的回收站不能清除的终极解决办法
- ZooKeeper 相关概念以及使用小结
- 自学通过CISSP备考心得
- 迅雷如何添加html文件夹,迅雷7上我的收藏怎么找
- 用QEMU搭建arm开发环境之一:QEMU能干啥
- 硬盘柱面损坏怎么办_硬盘有坏道怎么修复?使用DiskGenius修复硬盘逻辑坏道的方法...
- 计算机桌面图标有双影,win7图标重影怎么办_win7电脑桌面图标有重影如何解决
- Android参考之代号、标签和版本号
- asp.net中@Register指令
- 请不要做浮躁的人!(转载自勉)
- Y Combinator Is Boot Camp for Startups
- OA项目(MVC项目)
- USACO 奶牛食品(最大流)
- c语言private用法,深入理解C++中public、protected及private用法
- mysql判断字段是否存在不存在添加字段_mysql 新增字段时判断字段是否存在