1. 背景
KASLR是一个什么技术点其实不重要,但重要的是有了KASLR这个功能后,造成内核中某个符号(函数 or 变量)在System.map中的地址和实际不一样了(实际: cat /proc/kallsyms),进一步带来了分析类似crash问题中的打印地址不准确问题。所以必须得知道这是什么?本文主要从实战层面来谈几个问题:
- KASLR是什么?
- KASLR如何开启?如何查看开启了?
- 破除KASLR的几个方法(有些奇技淫巧)
- 破除KASLR后再实战中有何作用?
2. 要点
-
KASLR:kernel address space layout randomization 内核地址空间布局随机化。是一个安全功能,本质是将链接的地址 mapping到 运行的地址多做了一个随机偏移。断句是:KAS LR,KAS的LR
-
作者:Kees Cook,linux内核的安全工程师(Linux kernel security engineer),曾是Ubuntu Security Team的技术老大。更多参考链接: Cook的Linkedin 、 Cook的github仓库、Cook更多介绍
-
ASLR:address space layout randomization地址空间布局随机化,是一种众所周知的技术,它通过将各种对象随机放置在而不是固定的地址上来增加攻击的难度。参考Wiki
-
ASLR首次提出:Linux PaX 项目首先创造了“ASLR”这个术语,并于 2001 年 7 月作为 Linux 内核的补丁发布了 ASLR 的第一个设计和实现。参考Wiki
-
第一个默认支持 ASLR 的主流操作系统是 2003 年的 OpenBSD 3.4 版, 其次是 2005 年的 Linux。参考Wiki
-
ASLR是一种“统计防御”,因为一般可以使用暴力方法来克服它
-
在一个完全不受限制的系统中,尤其是在本地不受信任的用户的系统中,KASLR 不会很有用。但是,在使用容器或具有大量包含进程的系统上,KASLR 可以提供帮助。参考链接
-
Linux 长期以来一直为用户空间程序提供 ASLR。参考链接
-
在虚拟机内使用较多,提高虚拟机安全性
-
让内核vmlinux在运行态的地址相比于编译的地址有一个偏移,具体偏移值通过DTS配置
-
另外如果可以参数化,在bootloader中配置,可以动态设置每次每个设备都是不同的映射地址
-
Red Hat Enterprise Linux 7.5 and later开始引入,参考redhat 官网
-
如何避免内核消息卸载:通过将 dmesg_restrict sysctl 设置为 1,防止非特权用户访问内核消息。此设置将 dmesg 访问限制为具有 CAP_SYSLOG 权限的用户。proc通过kptr_restrict sysctl 设置为 1。
3. 实操
3.1 查看是否打开:CONFIG_RANDOMIZE_BASE
内核编译是否配置 CONFIG_RANDOMIZE_BASE=y
cat /boot/config-$(uname -r) |grep CONFIG_RANDOMIZE_BASE
实操:
- 在bootloader配置中关闭的方式,添加nokaslr的启动项
GRUB_CMDLINE_LINUX="rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet nokaslr"
nokaslr介绍
3.2 计算内核符号表和堆栈空间地址的偏移方法
可以随机找一个内核符号表假设是 kmalloc_info。
通过 /proc/kallsyms获取符号实际的加载地址
A
d
d
r
k
a
l
l
s
y
m
s
Addr_{kallsyms}
Addrkallsyms
通过 /boot/System.map-$(uname -r) 获取链接后的地址
A
d
d
r
s
y
s
t
e
m
m
a
p
Addr_{systemmap}
Addrsystemmap
那么偏移的地址就是他们的delta
A
d
d
r
δ
=
A
d
d
r
k
a
l
l
s
y
m
s
−
A
d
d
r
s
y
s
t
e
m
m
a
p
Addr_\delta = Addr_{kallsyms} - Addr_{systemmap}
Addrδ=Addrkallsyms−Addrsystemmap
命令:
cat /boot/System.map-$(uname -r) |grep kmalloc_info
cat /proc/kallsyms |grep kmalloc_info
python3 -c 'print(hex(int("0xffffffff87fa04c0", 16) - int("0xffffffff831a04c0", 16)))'
实操:
获取链接地址和运行加载地址:
计算delta:
所以算出来是:0x4e00000
那么计算出这个delta之后有什么作用呢?对实战有什么帮助呢?
4. 实战作用
在实战中,主要是需要用gdb调试linux内核的时候,比如有qemu场景调试guest主机内核,或者host主机内核都可以。下面主要以gdb调试vmlinux为例子介绍:
gdb使用add-symbol-file 默认基地址就是二进制vmlinux中的各个段地址。如果开启了KASLR,那么这个地址寻找到某个变量的实际地址就不对了。需要加上刚才的偏移。才能正确的使用。
4.1 获取默认链接地址
比如gdb中加载符号地址默认是根据vmlinux二进制文件中的各个段地址进行加载基础地址的。比如选取某个vmlinux查看他的text段的地址:
4.2 获取偏移后的加载地址:
- data段的基础地址就是:
ffffffff82600000 + 0x4e00000 = 0xffffffff87400000
- text段的基础地址就是:
ffffffff81000000 + 0x4e00000 = 0xffffffff85e00000
计算这两个段的地址以后在gdb加载的时候指定对应的地址就能正确的映射上去
验证正确性:使用gdb和crash分别查看一个内核全局变量vm_numa_stat中的值
使用最新的加载地址的gdb获取地址:
使用crash作为正确性对比来源对比:
结论是:都是28872。说明通过随机地址偏移后计算新的地址正确。
注意这里为什么vm_numa_stat不能直接使用kallsyms中直接使用他的地址?
实际上是可以的,但是会带来两个问题:
- 每次都需要将gdb中符号地址转化
- 转化后需要使用地址实际使用
使用加载时候指定每个段的基础地址这样gdb在使用具体符号(函数或者全局变量)的时候,就能从动态连接的符号表中获取到正确的地址偏移,最终获取到对应的正确数据。
另外值得注意的是gdb在添加symbol的时候需要先清空表(symbol-file 命令),以及有些数据需要每次都add-symbol-file才会更新。
# 清空表
symbol-file
#
add-symbol-file /usr/lib/debug/lib/modules/4.18.0-372.9.1.an8.x86_64/vmlinux -s .data 0xffffffff87400000 -s .text 0xffffffff85e00000
综述
本文从KASLA的功能出发,介绍了基础概念,以及实战场景。
实战场景中以在systemmap中的链接地址和kallsyms中的实际地址进行计算,并且以vmlinux中的各个段基础地址加上偏移计算通用
本方法依赖掌握代码编译链接和动态加载的基础原理,以及gdb的基础流程和原理,详细细节可以多参考相关文档,并多琢磨多实践。
其他参考链接:
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/virtualization_security_guide/sect-virtualization_security_guide-guest_security-kaslr
LWN介绍KASLR