内核提权二
字数 2366 2025-08-29 22:41:01

内核提权技术详解:seq_operations与pt_regs结构体利用

一、序列文件接口(Sequence File Interface)基础

1.1 seq_file背景与作用

序列文件接口是针对procfs默认操作函数每次只能读取一页数据的限制而设计的,为处理较大的proc文件提供了更友好的内核编程接口。

1.2 seq_file结构体

定义于/include/linux/seq_file.h中,关键特性:

  • 为file结构体提供private data成员
  • 其中的函数表成员op在打开文件时通过kmalloc动态分配

1.3 single_open简化接口

定义于fs/seq_file.c中的简化初始化函数:

  • 使用kmalloc分配seq_operations所需空间
  • 分配空间来自kmalloc-32GFP_KERNEL_ACCOUNT
  • 难以直接操纵seq_file结构体本身,因为其空间从单独的seq_file_cache中分配

二、seq_operations结构体分析

2.1 结构定义

定义于/include/linux/seq_file.h,包含四个函数指针:

struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

2.2 分配与释放机制

分配链:

stat_open() -> single_open() -> kmalloc(seq_operations)
  • stat_open()对应/proc/id/stat文件
  • 打开/proc/self/stat即可分配新的seq_operations结构体

释放:

  • single_release()会释放对应的seq_operations结构体
  • 关闭文件即可触发释放

2.3 利用方式

2.3.1 内核.text地址泄漏

  • seq_operations结构体包含四个内核指针
  • 读取这些指针可泄露内核.text段基址

2.3.2 劫持内核执行流

当read一个stat文件时:

  1. 内核调用proc_opsproc_read_iter指针
  2. 默认值为seq_read_iter()函数
  3. 通过劫持seq_operations->start等函数指针可控制执行流

三、pt_regs结构体分析

3.1 结构体形成

用户态进入内核态时:

  1. 通过门结构进入entry_SYSCALL_64函数
  2. PUSH_AND_CLEAR_REGS指令将所有寄存器压入内核栈
  3. 形成pt_regs结构体,位于内核栈底

3.2 结构定义

struct pt_regs {
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long bp;
    unsigned long bx;
    unsigned long r11;
    unsigned long r10;
    unsigned long r9;
    unsigned long r8;
    unsigned long ax;
    unsigned long cx;
    unsigned long dx;
    unsigned long si;
    unsigned long di;
    unsigned long orig_ax;
    unsigned long ip;
    unsigned long cs;
    unsigned long flags;
    unsigned long sp;
    unsigned long ss;
};

3.3 通用ROP利用

  • 内核栈仅有一个页面大小
  • pt_regs固定位于内核栈底
  • 劫持函数指针时,rsp与栈底的相对偏移通常不变
  • 系统调用中未使用的寄存器(r8-r15)可用于布置ROP链
  • 关键:寻找"add rsp, val; ret" gadget抬高栈

3.4 新版本内核防护

从内核v5.13开始:

  • 添加系统调用栈偏移值(CONFIG_RANDOMIZE_KSTACK_OFFSET=y)
  • 导致pt_regs与触发劫持时的栈偏移不再固定
  • 仍可通过slide gadget利用,但稳定性大幅下降

四、swapgs_restore_regs_and_return_to_usermode分析

4.1 关键指令

  • 包含寄存器rsi~r15的出栈操作
  • 使用rdi作为old_rsp,rsp指向新栈位置存放iretq返回结构体
  • 若开启KPTI保护,自动切换cr3后再调用iretq返回

4.2 内核代码修补机制

  • 内核启动时通过text_poke_early修补代码
  • apply_alternatives函数在运行时优化或替换指令
  • 修补位置不限于一处
  • 关闭PTI保护(nopit启动参数)可避免代码修补

五、实战案例:西湖论剑2021-easykernel

5.1 环境分析

  • 启动脚本检查:开启SMEP、KASLR、KPTI
  • test.ko模块分析:kerpwn_ioctl函数

5.2 漏洞利用

  1. UAF漏洞:cmd=0x30释放内核堆后未清空指针
  2. 堆操作
    • cmd=0x40:读取内核堆到用户态
    • cmd=0x20:申请内核堆(大小≤0x20)
    • cmd=0x50:向内核堆写入

5.3 利用步骤

  1. 构造UAF

    • 利用UAF构造与seq_operations相同大小的结构体
    • 通过open("/proc/self/stat", O_RDONLY)申请回来
    • 泄漏seq_operations中的.text地址
  2. 函数指针覆盖

    • 覆盖为add rsp,val... gadget地址
    • 利用pt_regs结构体提权
  3. pt_regs伪造

    • 提前获取init_cred地址
    • 构造ROP链:pop_rdi; init_cred_addr; commit_creds; swapgs_restore_regs + offset
    • 触发read操作调用覆盖的gadget

5.4 返回用户态

  • 使用swapgs_restore_regs_and_return_to_usermode + offset
  • offset需根据构造的pt_regs选择(示例中offset=9)
  • 也可使用commit_creds(prepare_kernel_cred(0))方式

六、虚拟机逃逸非预期解

  • 启动脚本未关闭monitor(-monitor /dev/null)
  • 直接ctrl+A C可逃逸
  • 解压rootfs.img读取flag

七、防御措施总结

  1. 代码完整性保护
  2. 随机化内核栈偏移
  3. 及时清理释放的指针
  4. 限制/proc文件访问
  5. 启用SMEP/KASLR/KPTI等保护机制

通过深入理解这些内核结构体和利用技术,安全研究人员可以更好地评估系统安全性并开发更有效的防御措施。

内核提权技术详解:seq_ operations与pt_ regs结构体利用 一、序列文件接口(Sequence File Interface)基础 1.1 seq_ file背景与作用 序列文件接口是针对procfs默认操作函数每次只能读取一页数据的限制而设计的,为处理较大的proc文件提供了更友好的内核编程接口。 1.2 seq_ file结构体 定义于 /include/linux/seq_file.h 中,关键特性: 为file结构体提供private data成员 其中的函数表成员 op 在打开文件时通过 kmalloc 动态分配 1.3 single_ open简化接口 定义于 fs/seq_file.c 中的简化初始化函数: 使用 kmalloc 分配 seq_operations 所需空间 分配空间来自 kmalloc-32 或 GFP_KERNEL_ACCOUNT 难以直接操纵 seq_file 结构体本身,因为其空间从单独的 seq_file_cache 中分配 二、seq_ operations结构体分析 2.1 结构定义 定义于 /include/linux/seq_file.h ,包含四个函数指针: 2.2 分配与释放机制 分配链: stat_open() 对应 /proc/id/stat 文件 打开 /proc/self/stat 即可分配新的 seq_operations 结构体 释放: single_release() 会释放对应的 seq_operations 结构体 关闭文件即可触发释放 2.3 利用方式 2.3.1 内核.text地址泄漏 seq_operations 结构体包含四个内核指针 读取这些指针可泄露内核.text段基址 2.3.2 劫持内核执行流 当read一个stat文件时: 内核调用 proc_ops 的 proc_read_iter 指针 默认值为 seq_read_iter() 函数 通过劫持 seq_operations->start 等函数指针可控制执行流 三、pt_ regs结构体分析 3.1 结构体形成 用户态进入内核态时: 通过门结构进入 entry_SYSCALL_64 函数 PUSH_AND_CLEAR_REGS 指令将所有寄存器压入内核栈 形成 pt_regs 结构体,位于内核栈底 3.2 结构定义 3.3 通用ROP利用 内核栈仅有一个页面大小 pt_regs 固定位于内核栈底 劫持函数指针时,rsp与栈底的相对偏移通常不变 系统调用中未使用的寄存器(r8-r15)可用于布置ROP链 关键:寻找"add rsp, val; ret" gadget抬高栈 3.4 新版本内核防护 从内核v5.13开始: 添加系统调用栈偏移值( CONFIG_RANDOMIZE_KSTACK_OFFSET=y ) 导致 pt_regs 与触发劫持时的栈偏移不再固定 仍可通过slide gadget利用,但稳定性大幅下降 四、swapgs_ restore_ regs_ and_ return_ to_ usermode分析 4.1 关键指令 包含寄存器rsi~r15的出栈操作 使用rdi作为old_ rsp,rsp指向新栈位置存放iretq返回结构体 若开启KPTI保护,自动切换cr3后再调用iretq返回 4.2 内核代码修补机制 内核启动时通过 text_poke_early 修补代码 apply_alternatives 函数在运行时优化或替换指令 修补位置不限于一处 关闭PTI保护( nopit 启动参数)可避免代码修补 五、实战案例:西湖论剑2021-easykernel 5.1 环境分析 启动脚本检查:开启SMEP、KASLR、KPTI test.ko模块分析: kerpwn_ioctl 函数 5.2 漏洞利用 UAF漏洞 :cmd=0x30释放内核堆后未清空指针 堆操作 : cmd=0x40:读取内核堆到用户态 cmd=0x20:申请内核堆(大小≤0x20) cmd=0x50:向内核堆写入 5.3 利用步骤 构造UAF : 利用UAF构造与 seq_operations 相同大小的结构体 通过 open("/proc/self/stat", O_RDONLY) 申请回来 泄漏 seq_operations 中的.text地址 函数指针覆盖 : 覆盖为 add rsp,val... gadget地址 利用 pt_regs 结构体提权 pt_ regs伪造 : 提前获取 init_cred 地址 构造ROP链: pop_rdi; init_cred_addr; commit_creds; swapgs_restore_regs + offset 触发read操作调用覆盖的gadget 5.4 返回用户态 使用 swapgs_restore_regs_and_return_to_usermode + offset offset需根据构造的 pt_regs 选择(示例中offset=9) 也可使用 commit_creds(prepare_kernel_cred(0)) 方式 六、虚拟机逃逸非预期解 启动脚本未关闭monitor( -monitor /dev/null ) 直接 ctrl+A C 可逃逸 解压 rootfs.img 读取flag 七、防御措施总结 代码完整性保护 随机化内核栈偏移 及时清理释放的指针 限制/proc文件访问 启用SMEP/KASLR/KPTI等保护机制 通过深入理解这些内核结构体和利用技术,安全研究人员可以更好地评估系统安全性并开发更有效的防御措施。