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

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain