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-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

常用的jdk下载地址

jdk下载地址 安装方式可以看之前的博客: mac安装jdk oracle 版本:https://www.oracle.com/java/technologies/downloads/ Eclipse Temurin版本:https://adoptium.net/zh-CN/temurin/releases/ 阿里版本: github:https://github.com/

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo