Linux内核地址空间随机化ASLR的几种实现方法

2024-04-26 03:18

本文主要是介绍Linux内核地址空间随机化ASLR的几种实现方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ASLR(Address Space Layout Randomization)在2005年被引入到Linux内核kernel 2.6.12中。地址空间随机化在内核中有多种实现和表现方式,下面分别介绍。

堆栈随机化

堆栈随机化是一项安全增强,它允许对系统调用发生时,内核使用的堆栈添加一个随机偏移。这给基于stack的攻击增加了难度,因为stack攻击通常要求stack有个固定的layout。现在每次系统调用,stack的layout都变化的话,攻击就无规律可寻了。

cbdfdc3067bdaafb6ec7bef1549da510.png

首先以系统调用为主线,看一下Linux内核ARM64架构堆栈随机化的实现方式:

在ARM64上,主要在系统调用的链条中增加了invoke_syscall()这个函数来实现堆栈随机化:

linux-5.4.175未增加堆栈随机化的实现:

linux-5.16.4增加堆栈随机化的实现:

我们模仿上面的逻辑,写一个demo来分析其原理:

#include <stdio.h>
#include <stdlib.h>#define DBG(fmt, ...)   do { printf("%s line %d, "fmt"\n", __func__, __LINE__, ##__VA_ARGS__); } while (0)int __attribute__((noinline)) bar(void)
{unsigned int abc = 0;DBG("stack var addr %p.", &abc);return 0;
}int __attribute__((noinline)) foo(void)
{int offset = rand()%256;   void *ptr = __builtin_alloca(offset);bar();return 0;
}int main(void)
{int i = 0;for(i = 0; i < 10; i ++){DBG("loop %d.", i);foo();}return 0;
}

去掉随机化之后,和上面对比,你会发现,堆栈地址在每次调用是都保持了不变。

具体的说,在内核实现中,这个机制允许在每个系统调用时,随机选择内核堆栈的偏移量,比如在启动时,通过输入randomize_kstack_offset=on/off控制是否启用这个机制。

得益于在每次系统调用时都分配了随机的内核堆栈偏移量,该功能可在启用后让基于堆栈的攻击变得更加困难。

堆栈随机化的另一种实现

nvidia GPU驱动中实现了另一种堆栈随机化价值。NVIDIA GPU驱动分为开源部分和闭源部分,为了安全,从开源代码调用闭源代码时,会调用NV_ENTER_RM_RUNTIME重新设置新的堆栈空间,退出时调用NV_EXIT_RM_RUNTIME恢复开源上下文的堆栈。

调用:

这个功能是需要一个支持__use_altstack__的customlize工具链才可以使用。

经过实际测试,altstack功能貌似没有起作用。

​#include <stdio.h>unsigned long stack[0x1000];
#define NV_ENTER_RM_RUNTIME(sp,fp)                                          \
{                                                                           \__asm__ __volatile__ ("movq %%rbp,%0" : "=r" (fp)); /* save %rbp */     \__asm__ __volatile__ ("movq %0,%%rbp" :: "r" (sp));                     \
}#define NV_EXIT_RM_RUNTIME(sp,fp)                                           \
{                                                                           \register void *__rbp __asm__ ("rbp");                                   \if (__rbp != (void*)(sp))                                                      \{                                                                       \printf("%s line %d, fata error.\n", __func__, __LINE__);            \}                                                                       \__asm__ __volatile__ ("movq %0,%%rbp" :: "r" (fp)); /* restore %rbp */  \
}unsigned long spp;
void __attribute__((altstack(0))) call_printf(unsigned long newsp)
{void* fp;NV_ENTER_RM_RUNTIME(newsp,fp);printf("%s line %d.\n", __func__, __LINE__);NV_EXIT_RM_RUNTIME(newsp,fp);return;
}int main(void)
{spp = (unsigned long)stack + 0x1000*8;call_printf(spp);	return 0;
}

 根据GITHUB上NVIDIA 的说法:

Does the altstack attribute require compiler support to be used? · NVIDIA/open-gpu-kernel-modules · Discussion #299 · GitHub

具体可以查看src/nvidia/arch/nvalloc/unix/include/osapi.h中NV_EXIT_RM_RUNTIME的定义,在工具链没有定义__use_altstack__时定义方式是空的。

加入altstack的作用

NVIDIA考虑到了安全,调用到闭源库后,会切栈,这样在闭源库内通过工具dump_stack时,会由于开源和闭源的堆栈不连续,导致无法导出完整的调用堆栈,一般来讲,开源的部分能看出业务逻辑来的,而闭源部门是无法根据函数名称得到业务逻辑信息的,所以,这样便会使攻击方无法将闭源部分的调用堆栈和业务逻辑,场景联系在一起,这样就失去了一个很重要的信息源。

用户态应用ASLR随机化

1.查看当前操作系统的ASLR配置情况,两种命令任君选择:

root@zlcao-RedmiBook-14:~# cat /proc/sys/kernel/random
random/             randomize_va_space  
root@zlcao-RedmiBook-14:~# cat /proc/sys/kernel/randomize_va_space 
0
root@zlcao-RedmiBook-14:~# sysctl -a --pattern randomize
kernel.randomize_va_space = 0
root@zlcao-RedmiBook-14:~# 

2.配置选项:

0-:关闭

1-:半随机化,共享库,栈,mmap以及vdso将被随机化。

2-: 全随机化,除了1中所有,还有HEAP。

地址随机化的表现:

ALSR关闭时,每次动态库的加载地址相同:

 关闭ALSR,有多种方法可以关闭ALSR:

方法1: echo 0 > /proc/sys/kernel/randomize_va_space

方法2:sysctl -w kernel.randomize_va_space=0

root@zlcao-RedmiBook-14:~# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
root@zlcao-RedmiBook-14:~# cat /proc/sys/kernel/randomize_va_space 
0
root@zlcao-RedmiBook-14:~# 

方法3:启动命令行加入norandmaps,内核启动阶段自动将randomize_va_space设置为0。

randomize_va_space变量是如何发挥作用的? 

关键代码在下面这幅图中,当启动ALSR后,加载器在加载可执行程序的时候,会调用arch_randomize_brk函数得到一个随机的地址空间范围。

trace的调用堆栈如下

root@zlcao-Vostro-3268:~# cd /sys/kernel/debug/tracing/
root@zlcao-Vostro-3268:/sys/kernel/debug/tracing# echo arch_randomize_brk > set_ftrace_filter 
root@zlcao-Vostro-3268:/sys/kernel/debug/tracing# echo function > current_tracer 
root@zlcao-Vostro-3268:/sys/kernel/debug/tracing# echo 1 > options/func_stack_trace 
root@zlcao-Vostro-3268:/sys/kernel/debug/tracing# echo 1 > tracing_on 
root@zlcao-Vostro-3268:/sys/kernel/debug/tracing# cat trac
cat: trac: 没有那个文件或目录
root@zlcao-Vostro-3268:/sys/kernel/debug/tracing# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 4/4   #P:4
#
#                                _-----=> irqs-off
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| /     delay
#           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION
#              | |         |   ||||      |         |cat-5261    [001] .... 57035.744709: arch_randomize_brk <-load_elf_binarycat-5261    [001] .... 57035.744719: <stack trace>=> arch_randomize_brk=> load_elf_binary=> search_binary_handler=> __do_execve_file.isra.39=> __x64_sys_execve=> do_syscall_64=> entry_SYSCALL_64_after_hwframecat-5264    [002] .... 57036.998736: arch_randomize_brk <-load_elf_binarycat-5264    [002] .... 57036.998746: <stack trace>=> arch_randomize_brk=> load_elf_binary=> search_binary_handler=> __do_execve_file.isra.39=> __x64_sys_execve=> do_syscall_64=> entry_SYSCALL_64_after_hwframe

Kernel 内核空间映射地址的随机化

内核线性地址空间(direct mapping)。这块区域把所有物理内存线性映射到PAGE_OFFSET虚拟地址的值可能是固定的0xffff888000000000,或者KASLR使能后的随机地址page_offset_base

KASLR是Kernel ASLR的缩写,开启内核地址随机化需要打开两个配置项,如果配置了这两个选项,page_offset_base、vmalloc_base、vmemmap_base的基址会在函数
kernel_randomize_memory()中被随机化赋予一个和是范围的值。

CONFIG_DYNAMIC_MEMORY_LAYOUT=y
CONFIG_RANDOMIZE_MEMORY=y

开启以后会随机化线性映射区域基址(page_offset_base)、vmalloc区域基址(vmalloc_base)、vmemmap区域基址(vmemmap_base)。参考文件arch/x86/mm/kaslr.c的实现:

start_kernel() → setup_arch() → kernel_randomize_memory():

对三个地址赋值的代码在下图中的139行,vaddr的产生算法中有随机变量发挥作用。

Kernel物理加载地址的随机化

物理加载地址的随机化是上面介绍的所有随机化机制里面最根本的随即化实现,前面介绍的种种,基本上都是围绕虚拟地址的随机化,而内核物理加载地址的随机化则是将整个Linux内核的物理加载地址进行了随机化处理,每次启动,内核加载到物理内存的具体位置都是不同的,让攻击者无法定位到内核的具体位置。

这个功能需要打开CONFIG_RANDOMIZE_BASE获取支持:

可以看到,X86架构有两个kaslr.c文件,分别处理内核和内核加载程序的随机化操作,前者上面已经了做了介绍,这里介绍的是后者。

测试关闭CONFIG_RANDOMIZE_BASE后的内核加载地址是否不再随机化:

可以看到,内核运行时实际的符号地址等于链接脚本中指定的地址,也等于反编译文件的开始地址,所以,内核加载地址没有被随机化。

另外,由于CONFIG_RANDOMIZE_BASE是CONFIG_DYNAMIC_MEMORY_LAYOUT和CONFIG_RANDOMIZE_MEMORY的先决条件,所以,当关闭内核的物理加载地址随机化后,内核虚拟地址空间的随机化也被关闭了,此时,PAGE_OFFSET是宏定义的值:

内核加载地址随机化是如何实现的呢?这涉及到内核bzImage文件的格式问题,内核顶层目录编译得到的ELF文件vmlinux是真正的内核,这个内核是保护模式下的,无法直接运行,所以LINUX构建系统在将这个真正的内核压缩后,套上一个解压程序作为外壳后,再次装配出一个新的ELF文件,也叫vmlinux,bzImage文件就是由最终的这个vmlinux提取二进制指令段后压缩生成的:

如果将内核映像比作航天器,则壳程序就相当于火箭的一级推进子系统,这部分负责将真正的内核解压后加载到内存,为内核创建保护模式的环境,之后跳转到真正的内核继续运行,而后者就相当于二级推进了。

既然如此,内核拷贝随机化的操作一定发生在一级推进程序里面,我们看内核启动的流程:

可以看到,一级助推程序中会调用choose_random_location函数获取一个随机的加载地址,之后将内核解压到这个地址,之后再跳入到这个地址继续执行真正的内核,所以,加载地址随机化的逻辑搞清楚了。

如前所述,这一切流程都需要打开CONFIG_RANDOMIZE_BASE选项,支持kaslr才能实现:

关闭KASLR有一个优雅的办法,choose_random_location入口处会检测命令行中是否有nokaslr字符,如果有,则直接退出生成随机地址的逻辑,见下图:

所以,关闭地址随机化最好的办法,是在启动命令行中加入nokaslr项:

关于加载地址随机化后,如何调整内核启动阶段的页表映射具体细节可以结合这篇文章:

linux内核——内存管理之早期页表建立过程 - 知乎


结束! 

这篇关于Linux内核地址空间随机化ASLR的几种实现方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/936570

相关文章

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

Linux之软件包管理器yum详解

《Linux之软件包管理器yum详解》文章介绍了现代类Unix操作系统中软件包管理和包存储库的工作原理,以及如何使用包管理器如yum来安装、更新和卸载软件,文章还介绍了如何配置yum源,更新系统软件包... 目录软件包yumyum语法yum常用命令yum源配置文件介绍更新yum源查看已经安装软件的方法总结软

linux报错INFO:task xxxxxx:634 blocked for more than 120 seconds.三种解决方式

《linux报错INFO:taskxxxxxx:634blockedformorethan120seconds.三种解决方式》文章描述了一个Linux最小系统运行时出现的“hung_ta... 目录1.问题描述2.解决办法2.1 缩小文件系统缓存大小2.2 修改系统IO调度策略2.3 取消120秒时间限制3

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

Linux:alias如何设置永久生效

《Linux:alias如何设置永久生效》在Linux中设置别名永久生效的步骤包括:在/root/.bashrc文件中配置别名,保存并退出,然后使用source命令(或点命令)使配置立即生效,这样,别... 目录linux:alias设置永久生效步骤保存退出后功能总结Linux:alias设置永久生效步骤

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

Git中恢复已删除分支的几种方法

《Git中恢复已删除分支的几种方法》:本文主要介绍在Git中恢复已删除分支的几种方法,包括查找提交记录、恢复分支、推送恢复的分支等步骤,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录1. 恢复本地删除的分支场景方法2. 恢复远程删除的分支场景方法3. 恢复未推送的本地删除分支场景方法4. 恢复

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf