Linux Kernel Pwn 初探
字数 1811 2025-08-24 16:48:16
Linux Kernel Pwn 初探 - 详细教学文档
基础知识
内核主要功能
- 控制并与硬件进行交互
- 提供应用程序能运行的环境
CPU特权级别
Intel CPU将CPU特权级别分为4个级别:Ring 0、Ring 1、Ring 2、Ring 3。
- Ring 0:只给操作系统使用
- Ring 3:所有程序都可以使用
- 内层Ring可以访问外层Ring的资源
进入内核态的方式
- 系统调用:int 0x80、syscall、ioctl
- 产生异常
- 外设产生中断
内核态与用户态切换
- 进入内核态前:保存用户态各个寄存器及执行位置
- 返回用户态:
- 64位系统:执行swapgs和iret指令
- 32位系统:直接iret返回
- 需要栈上布置好恢复的寄存器值
攻击思路
- 寻找内核程序漏洞
- 调用该程序进入内核态
- 利用漏洞进行提权
- 返回用户态
CTF中的Linux Kernel Pwn
常见形式
漏洞通常存在于动态装载模块(LKMs, Loadable Kernel Modules)中,包括:
- 驱动程序(Device drivers)
- 内核扩展模块(modules)
题目文件组成
通常提供四个文件:
baby.ko:有漏洞的驱动程序bzImage:打包的内核,用于启动虚拟机与寻找gadgetInitramfs.cpio:文件系统startvm.sh:启动脚本
有时还包含:
vmlinux:未打包的内核,含有符号信息
文件系统提取方法
-
ext4文件系统:
mkdir ./rootfs sudo mount rootfs.img ./rootfs # 查看启动脚本init或etc/init.d/rcS sudo umount rootfs -
cpio文件系统:
mkdir extracted; cd extracted cpio -i --no-absolute-filenames -F ../rootfs.cpio find . | cpio -o --format=newc > ../rootfs.cpio
内核保护机制
- KPTI:内核页表隔离
- KASLR:内核地址空间布局随机化
- SMEP:管理模式执行保护
- SMAP:管理模式访问保护
- Stack Protector:栈保护(canary)
- kptr_restrict:限制查看内核函数地址
- dmesg_restrict:限制查看printk输出
- MMAP_MIN_ADDR:不允许申请NULL地址
查看保护机制
cat /proc/cpuinfo
关闭保护方法
- KASLR、SMEP、SMAP:修改startvm.sh
- dmesg_restrict、kptr_restrict:修改rcS文件
- MMAP_MIN_ADDR:重新编译内核(.config文件)
准备工作
- 以root权限启动虚拟机
- 修改rcS文件中的权限设置
- 获取关键信息:
lsmod # 查看驱动加载基址 cat /proc/kallsyms | grep "prepare_kernel_cred" # 获取函数地址 cat /proc/kallsyms | grep "commit_creds" # 获取函数地址
漏洞类型与利用
1. 栈溢出漏洞(level1)
- 特征:直接ret2usr
- 利用步骤:
- 构造ROP链
- 覆盖返回地址
- 执行提权代码
2. 信息泄露+ROP(level2)
- 特征:需要绕过SMEP/SMAP
- 利用步骤:
- 泄露内核地址和canary
- 构造ROP链修改cr4寄存器
- 关闭SMEP/SMAP保护
- 执行提权代码
3. 竞争条件漏洞(level3)
- 特征:double fetch漏洞
- 利用步骤:
- 创建两个线程
- 主线程传入用户空间地址
- 子线程在验证间隙修改为内核地址
- 完成验证实现flag打印
4. UAF漏洞(level4)
- 特征:未清理释放的指针
- 利用步骤:
- 第一次UAF泄露内核地址
- 堆喷伪造数据
- 第二次UAF修改cr4
- 第三次UAF执行提权
实战案例
CVE-2019-9213
漏洞描述:Linux内核在mm/mmap.c中的expand_downwards函数缺少对mmap最小地址的检查,导致可以映射NULL地址。
利用方法:
- 使用MAP_GROWSDOWN标志映射内存
- 向低地址扩展
- 映射到NULL地址实现提权
CVE-2019-8956
漏洞描述:sctp_sendmsg()函数处理SCTP_SENDALL标志时存在use-after-free错误。
利用方法:
- 触发空指针解引用
- 结合0虚拟地址映射漏洞
- 伪造指针实现任意代码执行
关键代码片段
保存/恢复状态
void save_stat() {
asm(
"movq %%cs, %0;"
"movq %%ss, %1;"
"movq %%rsp, %2;"
"pushfq;"
"popq %3;"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags) : : "memory");
}
void templine() {
commit_creds(prepare_kernel_cred(0));
asm(
"pushq %0;"
"pushq %1;"
"pushq %2;"
"pushq %3;"
"pushq $shell;"
"pushq $0;"
"swapgs;"
"popq %%rbp;"
"iretq;"
::"m"(user_ss), "m"(user_sp), "m"(user_rflags), "m"(user_cs));
}
绕过SMEP/SMAP
// 修改cr4为0x6f0
int i = 18;
buf[i++] = calc(0xffffffff815033ec); // pop rdi; ret;
buf[i++] = 0x6f0;
buf[i++] = calc(0xffffffff81020300); // mov cr4,rdi; pop rbp; ret;
buf[i++] = 0;
buf[i++] = &templine;
工具与技巧
-
提取vmlinux:
~/linux-4.20/scripts/extract-vmlinux ./bzImage > vmlinux -
提取gadget:
objdump -d ./vmlinux > gadget -
调试技巧:
- 在startvm.sh中添加
-gdb tcp::1234 -S等待调试器连接 - 使用ubuntu 18.04避免玄学问题
- 在startvm.sh中添加
-
编译上传exp:
from pwn import * context.update(log_level='debug') def compile(): os.system("musl-gcc -w -s -static -o3 oob.c -o exp") def upload(): with open("exp", "rb") as f: encoded = base64.b64encode(f.read()) for i in range(0, len(encoded), 300): exec_cmd("echo \"%s\" >> benc" % (encoded[i:i+300])) exec_cmd("cat benc | base64 -d > bout") exec_cmd("chmod +x bout")
总结
Linux Kernel Pwn需要掌握:
- 内核基本工作原理
- 常见保护机制及绕过方法
- 漏洞类型识别与利用技巧
- 内核态与用户态切换机制
- 调试与开发工具使用
通过系统学习和实践,可以逐步掌握内核漏洞利用的技术要点。