kernel UAF
UAF 即 Use After Free,通常指的是对于释放后未重置的垂悬指针的利用,此前在用户态下的 heap 阶段对于 ptmalloc 的利用很多都是基于UAF漏洞进行进一步的利用。
在 CTF 当中,内核的“堆内存”主要指的是直接映射区(direct mapping area),常用的分配函数 kmalloc 从此处分配内存,常用的分配器为 slub,若是在 kernel 中存在着垂悬指针,我们同样可以以此完成对 slab/slub 内存分配器的利用,通过 Kernel UAF 完成提权。
内核堆利用与绑核
slub allocator 会优先从当前核心的 kmem_cache_cpu 中进行内存分配,在多核架构下存在多个 kmem_cache_cpu ,由于进程调度算法会保持核心间的负载均衡,因此我们的 exp 进程可能会被在不同的核心上运行,这也就导致了利用过程中 kernel object 的分配有可能会来自不同的 kmem_cache_cpu ,这使得利用模型变得复杂,也降低了漏洞利用的成功率。
比如说你在 core 0 上整了个 double free,准备下一步利用时 exp 跑到 core 1去了,那就很容易让人摸不着头脑 :(
因此为了保证漏洞利用的稳定,我们需要将我们的进程绑定到特定的某个 CPU 核心上,这样 slub allocator 的模型对我们而言便简化成了 kmem_cache_node + kmem_cache_cpu ,我们也能更加方便地进行漏洞利用。
现笔者给出如下将 exp 进程绑定至指定核心的模板:
#include <sched.h>
/* to run the exp on the specific core only */
void bind_cpu(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}通用 kmalloc flag
GFP_KERNEL 与 GFP_KERNEL_ACCOUNT 是内核中最为常见与通用的分配 flag,常规情况下他们的分配都来自同一个 kmem_cache ——即通用的 kmalloc-xx 。
这两种 flag 的区别主要在于 GFP_KERNEL_ACCOUNT 比 GFP_KERNEL 多了一个属性——表示该对象与来自用户空间的数据相关联,因此我们可以看到诸如 msg_msg 、pipe_buffer、sk_buff的数据包 的分配使用的都是 GFP_KERNEL_ACCOUNT ,而 ldt_struct 、packet_socket 等与用户空间数据没有直接关联的结构体则使用 GFP_KERNEL。
在5.9 版本之前GFP_KERNEL 与 GFP_KERNEL_ACCOUNT 存在隔离机制,在 这个 commit 中取消了隔离机制,自内核版本 5.14 起,在 这个 commit 当中又重新引入:
对于开启了
CONFIG_MEMCG_KMEM编译选项的 kernel 而言(通常都是默认开启),其会为使用GFP_KERNEL_ACCOUNT进行分配的通用对象创建一组独立的kmem_cache——名为kmalloc-cg-*,从而导致使用这两种 flag 的 object 之间的隔离。
slub 合并 & 隔离
slab alias 机制是一种对同等/相近大小 object 的 kmem_cache 进行复用的一种机制:
当一个
kmem_cache在创建时,若已经存在能分配相等/近似大小的 object 的kmem_cache,则不会创建新的 kmem_cache,而是为原有的 kmem_cache 起一个 alias,作为“新的” kmem_cache 返回。
举个🌰,cred_jar 是专门用以分配 cred 结构体的 kmem_cache,在 Linux 4.4 之前的版本中,其为 kmalloc-192 的 alias,即 cred 结构体与其他的 192 大小的 object 都会从同一个 kmem_cache——kmalloc-192 中分配。
对于初始化时设置了 SLAB_ACCOUNT 这一 flag 的 kmem_cache 而言,则会新建一个新的 kmem_cache 而非为原有的建立 alias,🌰如在新版的内核当中 cred_jar 与 kmalloc-192 便是两个独立的 kmem_cache,彼此之间互不干扰。
例题:CISCN2017 - babydriver
分析
先解压 rootfs.cpio 看一下有什么文件
根据 init 的内容,12 行加载了 babydriver.ko 这个驱动,根据 pwn 的一般套路,这个就是有漏洞的 LKM 了。init 的其他命令都是 linux 常用的命令,就不再解释了。
把这个驱动文件拿出来。
没有开 PIE,无 canary 保护,没有去除符号表,很 nice。
用 IDA 打开分析,既然没有去除符号表,shift + F9 先看一下有什么结构体,可以发现如下的结构体:
再看一下主要函数
babyioctl: 定义了 0x10001 的命令,可以释放全局变量 babydev_struct 中的 device_buf,再根据用户传递的 size 重新申请一块内存,并设置 device_buf_len。
babyopen: 申请一块空间,大小为 0x40 字节,地址存储在全局变量 babydev_struct.device_buf 上,并更新 babydev_struct.device_buf_len
babyread: 先检查长度是否小于 babydev_struct.device_buf_len,然后把 babydev_struct.device_buf 中的数据拷贝到 buffer 中,buffer 和长度都是用户传递的参数
babywrite: 类似 babyread,不同的是从 buffer 拷贝到全局变量中
babyrelease: 释放空间,没什么好说的
还有 babydriver_init() 和 babydriver_exit() 两个函数分别完成了 /dev/babydev 设备的初始化和清理,查一下函数的用法即可,不再分析。
思路
没有用户态传统的溢出等漏洞,但存在一个伪条件竞争引发的 UAF 漏洞:
如果我们同时打开两个设备,第二次会覆盖第一次分配的空间,因为 babydev_struct 是全局的。同样,如果释放第一个,那么第二个其实是被是释放过的,这样就造成了一个 UAF。
接下来考虑如何通过 UAF 劫持程序执行流,这里我们选择 tty_struct 结构体作为 victim object。
在 /dev 下有一个伪终端设备 ptmx ,在我们打开这个设备时内核中会创建一个 tty_struct 结构体,与其他类型设备相同,tty驱动设备中同样存在着一个存放着函数指针的结构体 tty_operations。
那么我们不难想到的是我们可以通过 UAF 劫持 /dev/ptmx 这个设备的 tty_struct 结构体与其内部的 tty_operations 函数表,那么在我们对这个设备进行相应操作(如write、ioctl)时便会执行我们布置好的恶意函数指针。
由于没有开启SMAP保护,故我们可以在用户态进程的栈上布置ROP链与 fake tty_operations 结构体。
结构体
tty_struct位于include/linux/tty.h中,tty_operations位于include/linux/tty_driver.h中。
内核中没有类似 one_gadget 一类的东西,因此为了完成ROP我们还需要进行一次栈迁移
使用gdb进行调试,观察内核在调用我们的恶意函数指针时各寄存器的值,我们在这里选择劫持 tty_operaionts 结构体到用户态的栈上,并选择任意一条内核gadget作为fake tty函数指针以方便下断点:

我们不难观察到,在我们调用tty_operations->write时,其rax寄存器中存放的便是tty_operations结构体的地址,因此若是我们能够在内核中找到形如mov rsp, rax的gadget,便能够成功地将栈迁移到tty_operations结构体的开头。
使用ROPgadget查找相关gadget,发现有两条符合我们要求的gadget:

gdb调试,发现第一条gadget其实等价于mov rsp, rax ; dec ebx ; ret:

那么利用这条gadget我们便可以很好地完成栈迁移的过程,执行我们所构造的ROP链。
而tty_operations结构体开头到其write指针间的空间较小,因此我们还需要进行二次栈迁移,这里随便选一条改rax的gadget即可:

Exploit
Old Solution
这道题在当年的解法是通过 UAF 修改该进程的 cred 结构体的 uid、gid 为0,十分简单十分白给。
但是此种方法在较新版本 kernel 中已不可行,我们已无法直接分配到 cred_jar 中的 object,这是因为 cred_jar 在创建时设置了 SLAB_ACCOUNT 标记,在 CONFIG_MEMCG_KMEM=y 时(默认开启)cred_jar 不会再与相同大小的 kmalloc-192 进行合并
来自内核源码 4.5
kernel/cred.c本题(4.4.72):
因此这里考虑介绍更加通用的解法,对旧的解法感兴趣的可以参考如下exp:
Reference
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x04-Kernel-Heap-Use-After-Free
https://bbs.pediy.com/thread-247054.htm
https://whereisk0shl.top/NCSTISC%20Linux%20Kernel%20pwn450%20writeup.html
http://muhe.live/2017/07/13/babydriver-writeup/
https://www.anquanke.com/post/id/86490
Last updated