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(默认会禁用)
  • 内核代码中相关的宏定义和随机值计算
    • 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 /3
2 = 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)下的可分配地址空间相关推荐

  1. 内存安全 - 地址空间布局随机化(ASLR)

    说明 学过编译原理可知,C语言程序中所有变量的内存地址编译后都是确定了的,但是在linux平台上实际使用时可以发现变量的内存地址并不是固定的,如下: * 示例代码 #include <stdio ...

  2. 关闭aslr oracle,地址空间布局随机化 (Address Space Layout Randomization, ASLR)

    地址空间布局随机化 (Address Space Layout Randomization, ASLR) ASLR 是 Oracle Solaris 系统的一种功能,利用此功能可以随机生成进程地址空间 ...

  3. 关于Windows上地址空间布局随机化防御机制的分析(下)

    将32位程序重新编译为64位程序,以使地址空间布局随机化更有效 尽管Windows的64位版本已经成为主流十多年了,但32位版本的使用者仍然很多.有些程序确实需要保持与第三方插件的兼容性,比如web浏 ...

  4. mini2440 linux 内存布局

    mini2440 linux 内存布局 在学习linux内存寻址的过程中,注意到在x86架构上,分段与分页机制共存.而在RSIC体系结构下一般只支持分页.<深入理解linux内核>是在x8 ...

  5. linux内存布局及页面映射

    在Linux系统中,以32bit x86系统来说,进程的4GB内存空间(虚拟地址空间)被划分成为两个部分 ------用户空间和内核空间,大小分别为0-3G,3-4G. 用户进程通常情况下,只能访问用 ...

  6. linux内存映射起始地址,内存初始化代码分析(三):创建系统内存地址映射

    内存初始化代码分析(三):创建系统内存地址映射 作者:linuxer 发布于:2016-11-24 12:08 分类:内存管理 一.前言 经过内存初始化代码分析(一)和内存初始化代码分析(二)的过渡, ...

  7. linux代码布局,Linux内存布局(示例代码)

    在上一篇博文里,我们已经看到Linux如何有效地利用80x86的分段和分页硬件单元把逻辑地址转换为线性地址,在由线性地址转换到物理地址.那么我们的应用程序如何使用这些逻辑地址,整个内存的地址布局又是怎 ...

  8. linux内存布局的内核实现--用户空间的映射方式

    引用牛人的一个表格(本人绘制能力实在有限,引自:http://blog.csdn.net/absurd/archive/2006/06/19/814268.aspx) Linux的内存模型,一般为: ...

  9. linux内存布局 zone,【原创】(八)Linux内存管理 - zoned page frame allocator - 3

    背景 read the fucking source code! --by 鲁迅 a picture is worth a thousand words. --by 高尔基 说明: kernel版本: ...

最新文章

  1. [LeetCode] Remove Duplicates from Sorted Array II
  2. 功能测试——医疗管理系统
  3. ios PNG Crush error (PNG图片错误)
  4. macOS 使用手册
  5. 自动将存储过程转成C#代码的过程[转]
  6. evolution 的回收站不能清除的终极解决办法
  7. ZooKeeper 相关概念以及使用小结
  8. 自学通过CISSP备考心得
  9. 迅雷如何添加html文件夹,迅雷7上我的收藏怎么找
  10. 用QEMU搭建arm开发环境之一:QEMU能干啥
  11. 硬盘柱面损坏怎么办_硬盘有坏道怎么修复?使用DiskGenius修复硬盘逻辑坏道的方法...
  12. 计算机桌面图标有双影,win7图标重影怎么办_win7电脑桌面图标有重影如何解决
  13. Android参考之代号、标签和版本号
  14. asp.net中@Register指令
  15. 请不要做浮躁的人!(转载自勉)
  16. Y Combinator Is Boot Camp for Startups
  17. OA项目(MVC项目)
  18. USACO 奶牛食品(最大流)
  19. c语言private用法,深入理解C++中public、protected及private用法
  20. mysql判断字段是否存在不存在添加字段_mysql 新增字段时判断字段是否存在

热门文章

  1. NET- SplitContainer控件说明
  2. java高性能编程是什么,Java高性能编程
  3. python写一个crm系统_用Python打造一个CRM系统(四)
  4. 【コンテンツ配信高速化 】
  5. Redis+MongoDB 极佳实践:做到读写分离
  6. iOS 13-Sign In with Apple
  7. 基于 YOLOV3 和 OpenCV的目标检测
  8. CNN反向传播算法过程
  9. 不支持模块化规范的插件可以使用import 导入的原因
  10. Kanzi编程基础3 - 图片读取与显示