逆向工程实战手册 第 79 章

第 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 验证
□ 报告: 完整链+每步证据

动手练习

  1. 搭一个内核 pwn 环境(提供 vmlinux+bzImage+initramfs),练习改 init 提权。
  2. 写一个含 ioctl 漏洞的测试驱动(或找练习题),完成"msg_msg 占位→任意读写"。
  3. 练习 modprobe_path 提权(任意写原语 → root),再对比 commit_creds ROP 的复杂度。
  4. 用 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