内核提权案例分析一
字数 3200 2025-08-30 06:50:35
内核提权技术深度解析
1. 修改cred结构体提权技术
1.1 基本原理
内核通过进程的task_struct结构体中的cred指针来索引cred结构体,根据cred内容判断进程权限。当cred结构体成员中的uid-fsgid都为0时,进程具有root权限。
1.2 两种实现方式
- 直接修改cred结构体内容:找到cred结构体并修改其权限字段
- 修改task_struct中的cred指针:使其指向一个伪造的具有root权限的cred结构体
1.3 定位技术
- 使用
task_struct中的comm字段定位:comm标记可执行文件名,位于cred正下方- 使用
prctl设置进程comm为特殊字符串提高定位准确性
- 具体步骤:
- 定位当前进程
task_struct结构体地址 - 根据
cred指针相对于task_struct的偏移计算cred指针地址 - 获取或修改
cred地址
- 定位当前进程
1.4 修改方法
-
修改为init_cred地址:
- 将
cred指针指向内核镜像中已有的init_cred地址 - 适用于能直接修改
cred指针且知道init_cred地址的情况
- 将
-
伪造cred结构体:
- 创建伪造的cred结构体并修改指针指向它
- 实现复杂,一般不常用
1.5 UAF利用方法(已过时)
-
典型利用流程:
- 申请与cred结构体大小相同的堆块
- 释放该堆块
- fork新进程,恰好使用刚释放的堆块
- 修改cred结构体特定内存实现提权
-
限制:
- 较新内核(v4.5+)中
cred_jar设置了SLAB_ACCOUNT标记 - 默认开启
CONFIG_MEMCG_KMEM时不再与kmalloc-192合并
- 较新内核(v4.5+)中
2. commit_creds函数提权技术
2.1 commit_creds(&init_cred)
-
原理:
commit_creds()函数将新cred设为当前进程task_struct的real_cred与cred字段init_cred是init进程的静态定义cred结构体,具有root权限
-
使用方法:
commit_creds(&init_cred);
2.2 commit_creds(prepare_kernel_cred(NULL))
-
原理:
prepare_kernel_cred()函数拷贝指定进程的cred结构体- 参数为NULL时拷贝
init_cred并返回root权限的cred
-
使用方法:
commit_creds(prepare_kernel_cred(NULL)); -
限制:
- 内核v6.2+后
prepare_kernel_cred(NULL)将返回NULL - 不再适用于v6.2+内核
- 内核v6.2+后
3. 内核ROP技术
3.1 基本原理
- 与用户态ROP类似,但使用内核中的gadget
- 目标ropchain:
commit_creds(&init_cred)- 或
commit_creds(prepare_kernel_cred(NULL))
3.2 状态保存
- 需要手动模拟用户态进入内核态的准备工作
- 保存各寄存器值到内核栈上,便于后续返回用户态
- 常用保存函数(使用内联汇编):
unsigned long user_cs, user_ss, user_rflags, user_sp; void save_status() { __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;"); }
3.3 返回用户态
-
必要步骤:
swapgs指令恢复用户态GS寄存器sysretq或iretq返回到用户空间
-
区别:
sysretq和iretq操作不相同- 通常使用
iretq更可靠
4. 案例分析:CISCN2017_babydriver
4.1 漏洞分析
-
驱动初始化:
- 注册字符设备
/dev/babydev - 定义操作函数:
babyread、babywrite、babyioctl、babyopen、babyrelease
- 注册字符设备
-
漏洞点:
babyrelease释放全局变量指向空间但未清零- 打开两个
/dev/babydev设备并关闭一个会造成UAF
4.2 利用思路
-
利用UAF劫持控制流:
- 执行内核gadget修改SMEP属性
- 使用ret2usr提权
-
SMEP关闭方法:
- 向CR4寄存器写入
0x6f0 - 将第21位覆盖为0
- 向CR4寄存器写入
4.3 利用技术细节
-
关键结构体:
tty_struct和tty_operations- 包含多个函数指针,利于构造ROP
-
结构体创建:
open("/dev/ptmx", O_RDWR)创建tty_struct结构体- 大小为
0x2e0
-
控制流劫持步骤:
- 伪造
tty_operations结构体 - 修改
tty_struct中的ops指针指向伪造结构体 - 操作设备时调用伪造的函数指针
- 伪造
4.4 调试分析
-
UAF实现:
- 打开两个
/dev/babydev设备 - 关闭一个造成UAF
- 打开
/dev/ptmx使tty_struct使用释放的内存
- 打开两个
-
控制流劫持:
- 使用
babyread/babywrite操作tty_struct - 覆盖
ops指针指向用户态伪造结构体 - 调用
write触发控制流转移
- 使用
-
栈迁移:
- 利用
mov rsp, rax等gadget将栈迁移到用户空间 - 构造ROP链关闭SMEP并提权
- 利用
4.5 提权执行流程
-
提权函数:
void privilege_escalate() { commit_creds(prepare_kernel_cred(NULL)); } -
返回用户态:
swapgs切换GS寄存器iretq恢复用户态寄存器- 执行
system("/bin/sh")获取root shell
5. 案例分析:qwb_2018_core
5.1 环境配置
-
内核保护机制:
kptr_restrict控制内核符号地址可见性dmesg_restrict控制内核日志访问权限
-
符号地址获取:
- 从
/tmp/kallsyms读取commit_creds和prepare_kernel_cred地址 - 计算内核加载偏移量
- 从
5.2 漏洞分析
-
内核模块功能:
- 创建
/proc/core条目 - 定义操作:
core_write、core_ioctl、core_release
- 创建
-
漏洞点:
ioctl的0x6677889C和0x6677889B操作可泄漏canarycore_write配合0x6677889A操作造成内核栈溢出
5.3 利用技术
-
Canary泄漏:
- 设置大偏移量读取内核栈布局
- 通过
core_read将canary读到用户空间
-
栈溢出利用:
- 使用
core_write构造ROP链写入name - 通过
core_ioctl的0x6677889A操作触发溢出 - 使用负数绕过
core_copy_func中的条件检查
- 使用
-
ROP链构造:
- 覆盖返回地址执行提权代码
- 返回用户态启动shell
6. 关键防护机制与绕过
6.1 SMEP/SMAP防护
-
SMEP(Supervisor Mode Execution Protection):
- 阻止内核态执行用户空间代码
- 通过CR4寄存器的第20位控制
-
绕过方法:
- 修改CR4寄存器关闭SMEP
- 使用内核ROP技术
6.2 KASLR防护
- 内核地址空间布局随机化
- 绕过方法:
- 通过信息泄漏获取内核基址
- 计算函数和gadget的实际地址
6.3 Stack Canary防护
- 内核栈保护机制
- 绕过方法:
- 信息泄漏获取canary值
- 在溢出时正确覆盖canary
7. 开发技巧与最佳实践
-
调试技巧:
- 使用
gdb配合vmlinux调试内核 - 设置断点分析执行流程
- 使用
-
利用开发:
- 编写可靠的内核态/用户态切换代码
- 处理不同内核版本差异
-
稳定性考虑:
- 确保ROP链在不同环境下的可靠性
- 处理竞争条件和时序问题
8. 总结与演进
-
技术演进:
- 新版本内核增加了更多防护机制
- 旧技术可能失效,需要持续研究新方法
-
防御建议:
- 及时更新内核版本
- 启用所有可用防护机制
- 监控可疑的内核行为
-
研究方向:
- 新型内核漏洞利用技术
- 绕过最新防护机制的方法
- 更稳定的提权技术