第 79 章 内核 Pwn 深度(SMEP / SMAP / KASLR)
第 18 章讲了内核 pwn 的基础路径;本章深入"现代内核防护"下的利用细节:SMEP/SMAP/KASLR/KPTI 下的 ROP、slab 利用原语、以及从 ioctl 漏洞到 root 的完整链。内核利用 = 在"什么都不能执行"的约束下找到出路。
📍 知识点地图 | 主题:内核Pwn深度 | 前置:第18/27章 | 后续:— | 核心概念:SMEP/SMAP、KASLR、slab、modprobe_path
79.1 内核保护机制(先懂敌人)
| 机制 | 作用 | 对利用的影响 |
|---|---|---|
| KASLR | 内核基址随机化 | 需要先 leak 一个地址 |
| SMEP | 禁止从内核执行用户态代码 | 不能直接跳用户 shellcode |
| SMAP | 禁止内核访问用户态内存 | ROP 链不能读用户数据 |
| KPTI | 内核/用户页表隔离 | Meltdown 防护(影响 gadget 选择) |
| 内核堆随机化 | slab 布局随机 | 需要堆喷/信息泄露 |
| FG-KASLR(新) | 函数级随机化 | 更难预测函数地址 |
第一件事永远是查 QEMU 启动参数:+smep +smap 决定 ROP 链怎么写。
79.2 环境搭建(复现内核题)
# 1. 拆 initramfs(改 init 提权,CTF 常用)
mkdir rootfs && cd rootfs
cpio -idm < ../initramfs.cpio
# 修改 init: 给 user 加 root 权限(CTF)/ 或设置 setuid
# 2. 提取 vmlinux(只有 bzImage 时)
# 用内核源码的 extract-vmlinux 脚本
./extract-vmlinux bzImage > vmlinux
# 3. QEMU 启动(关键: 记录 CPU 标志)
qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz \
-append "console=ttyS0 oops=panic panic=1 kaslr" \
-nographic -smp 2 -m 256M \
-cpu kvm64,+smep,+smap \
-s -S # -s: gdb 端口 1234, -S: 暂停启动
# 4. GDB 连接调试
gdb vmlinux -ex "target remote :1234"
79.3 漏洞类型与利用原语(从漏洞到提权)
常见漏洞
□ ioctl 越界写(copy_from_user 长度可控)→ 内核堆溢出
□ UAF(释放后仍引用对象)
□ 任意地址写(写原语)
□ 逻辑漏洞(权限检查缺失)
利用原语(把漏洞变成能力)
堆溢出 → 覆盖相邻对象(msg_msg/tty_struct/pipe_buffer)
UAF → 占位攻击(重新分配可控对象)
任意写 → 直接改关键结构(modprobe_path/cred)
信息泄露 → 未初始化堆/栈泄露内核地址
79.4 slab 利用(内核堆的核心)
内核堆分配器(slab):
kmalloc-8/16/32/64/...(固定大小 cache)
关键对象(利用的"靶子"):
msg_msg: 消息队列(最通用,可跨 cache 使用)
tty_struct: 终端对象(含函数指针表)
pipe_buffer: 管道(含 flags/ops 指针)
seq_operations: 序列文件(内核地址泄露)
经典流程(msg_msg 示例):
1. 触发漏洞溢出/释放 msg_msg
2. 重新分配可控对象占位(伪造 msg_msg 头)
3. 构造任意读写原语
4. 读/写目标(cred/modprobe_path)
79.5 提权路径选择(ROP vs 快速路)
路径 1:commit_creds ROP(标准)
流程:
1. leak 内核基址(KASLR 绕过)
2. 用堆原语构造栈迁移(stack pivot)
3. ROP 链: prepare_kernel_cred(0) → commit_creds
4. swapgs + iretq 返回用户态
5. execve("/bin/sh")
难点: SMEP/SMAP 下不能直接调用户函数 → 全链必须在内核内存
路径 2:modprobe_path(CTF 最省事)
原理: 内核执行未知文件格式时调用 modprobe_path 指定的程序
流程:
1. 任意地址写 modprobe_path → "/tmp/x"
2. 写 /tmp/x(chmod +x,内容是: 复制 /bin/sh 或 chmod 挂载)
3. 创建未知格式文件触发 modprobe
4. 以 root 执行 /tmp/x
优点: 不需要完整 ROP 链(只需任意写)
路径 3:core_pattern(类似 modprobe)
写 /proc/sys/kernel/core_pattern → 崩溃时执行任意程序(root)
79.6 SMEP/SMAP 绕过细节(关键知识)
SMEP 绕过:
□ ROP(不执行用户代码,只跳内核 gadget)← 标准解法
□ 修改 CR4(清 SMEP 位,需内核任意写)→ 旧手法,新内核受限
□ vsyscall 页(部分环境可用)
SMAP 绕过:
□ 用内核内存传参(copy_from_user 前先拷贝到内核缓冲区)
□ ROP 链数据放内核内存(通过堆原语写入)
□ stac/clac 指令(若 gadget 可用)
通用原则: 把"用户态数据"先搬进"内核内存"再操作
79.7 KASLR 绕过(地址泄露)
□ /proc/kallsyms(若未限权)→ 直接读符号地址
□ 未初始化堆喷 → 泄露残留的内核指针
□ seq_operations 泄露(读文件得到函数指针)
□ 用户态侧信道(CPU 特性)
一次泄露全解决: base = leaked - symbol_offset(第 18 章同思路)
79.8 完整利用链(综合)
1. 分析驱动 ioctl → 找漏洞(copy_from_user 长度/下标越界)
2. 构建原语(msg_msg 占位 → 任意读写)
3. leak 内核地址 → 算 KASLR base
4. 选路径: modprobe_path(快)或 commit_creds ROP(稳)
5. 触发 → 拿 root
6. 验证: id = root
79.9 检查清单
□ QEMU CPU 标志确认(smep/smap/kaslr)
□ 驱动逆向(ioctl 处理/漏洞点)
□ 漏洞类型与原语选择
□ slab 对象选择(msg_msg/tty/pipe)
□ KASLR leak 方案
□ 提权路径(modprobe_path vs ROP)
□ SMEP/SMAP 处理
□ root 验证
□ 报告: 完整链+每步证据
动手练习
- 搭一个内核 pwn 环境(提供 vmlinux+bzImage+initramfs),练习改 init 提权。
- 写一个含 ioctl 漏洞的测试驱动(或找练习题),完成"msg_msg 占位→任意读写"。
- 练习 modprobe_path 提权(任意写原语 → root),再对比 commit_creds ROP 的复杂度。
- 用 GDB 调试一次内核 ROP 链(断在 commit_creds,验证执行流)。
深入阅读
- 仓库:
skills/pwn-chain/references/kernel-pwn.md(内核 pwn 全量:环境/原语/提权路径) - 仓库:
skills/reverse-engineering/kernel-driver-reverse.md(驱动逆向) - 仓库:
skills/pwn-chain/SKILL.md(利用工程化) - 实战案例:
skills/field-journal/seed-010_ctf-pwn-rop-x64.md(ROP 基础) - 外部: CTF-Wiki kernel pwn 章节、内核利用公开 writeup