内核提权二
字数 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-32或GFP_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文件时:
- 内核调用
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 结构定义
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 漏洞利用
- 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地址
- 利用UAF构造与
-
函数指针覆盖:
- 覆盖为
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等保护机制
通过深入理解这些内核结构体和利用技术,安全研究人员可以更好地评估系统安全性并开发更有效的防御措施。