0xCD4/kernel-ctf-lab
GitHub: 0xCD4/kernel-ctf-lab
一套基于 QEMU 的 Linux 内核漏洞利用渐进式训练环境,涵盖从 ret2usr 到竞态条件的多种利用技术与缓解措施绕过方法。
Stars: 20 | Forks: 1
# kernel-ctf-lab
## 规则
- 每个挑战都有一个运行在 QEMU 虚拟机内的 **易受攻击的内核模块**。
- 你的初始身份是 **uid 1000**。flag 位于 `/root/flag.txt` (root:400)。
- 完整的源代码在 `src/` 目录中。阅读它、逆向 `.ko` 文件,或者两者兼做——随你选择。
- 使用任何你想要的工具:Ghidra、IDA、GDB、pwntools、ropper——皆可使用。
- 每个挑战都有一个 **推荐级别**,对应其预期的利用技术。你也可以在任何级别运行任何挑战以进行额外练习。
- 每个挑战都建立在前一个挑战的基础之上。ROP (CH02) 在 CH03(栈迁移)中会被重用,然后仅数据方法 (CH04) 向你展示了你并不总是需要它。CH05 将所有内容串联起来。
## 快速入门
```
git clone https://github.com/0xCD4/kernel-ctf-lab.git
cd kernel-ctf-lab
# 从 Releases 下载预构建的 binaries
# https://github.com/0xCD4/kernel-ctf-lab/releases
sudo apt install -y qemu-system-x86 gdb gcc busybox-static
cd challenges/ch01-echo-chamber
./run.sh 0
```
## 内核
你需要在仓库根目录下有一个 `bzImage`。从 releases 中获取或自行构建一个:
```
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.75.tar.xz
tar xf linux-6.1.75.tar.xz && cd linux-6.1.75
make defconfig
scripts/config -e CONFIG_DEBUG_INFO -e CONFIG_GDB_SCRIPTS \
-e CONFIG_KALLSYMS -e CONFIG_KALLSYMS_ALL
make -j$(nproc)
cp arch/x86/boot/bzImage ../
```
或者运行 `./build.sh` 从头开始构建所有内容。
## 挑战进程
这些挑战被设计成一个连贯的课程。每个挑战恰好引入了一个新的漏洞利用概念,同时建立在以前学过的所有知识之上。
```
CH01 → CH02 → CH03 → CH04 → CH05
│ │ │ │ │
│ │ │ │ └─ + race conditions, + KPTI bypass
│ │ │ └─ + integer bugs, + data-only attacks, + canaries
│ │ └─ + heap exploitation, + SMAP bypass (stack pivot)
│ └─ + kernel ROP, + KASLR bypass, + SMEP
└─ ret2usr fundamentals
```
### 为什么内核 ROP 被提前引入?
本实验有两种漏洞利用风格:
- CH01-CH03:控制流攻击(栈溢出、内核 ROP、结合 SMAP 的栈迁移)。
- CH04-CH05:仅数据攻击(整数/竞态条件 bug、堆破坏、无需 ROP 提升权限)。
CH02 被提前安排是有意的。你只需学习一次如何返回到用户模式,然后就可以在以后重用它。
## 学习路径(可选)
如果你想要一条简单的路线,请使用以下路径之一:
### 路径 A - 控制流
1. **CH01**:ret2usr 基础
2. **CH02**:内核 ROP 和 KASLR 绕过
3. **CH03**:堆 UAF、栈迁移、SMAP 限制
### 路径 B - 仅数据
1. **CH04**:整数溢出导致堆 OOB 和 `modprobe_path`
2. **CH05**:引用计数竞态、double free、直接 `cred` 覆盖
默认顺序依然有效。这些路径是可选的。
## 挑战
### ch01-echo-chamber -- ret2usr 基础
- **设备:** `/dev/echo`
- **接口:** `read()` / `write()`
- **源码:** `src/ch01-echo-chamber/vuln_echo.c`
- **级别:** 0(无缓解措施)
- **新概念:** 基础内核漏洞利用 - 栈布局、`commit_creds(prepare_kernel_cred(0))`、ret2usr
### ch02-echo-chamber-v2 -- 内核 ROP
- **设备:** `/dev/echo2`
- **接口:** `read()` / `write()`
- **源码:** `src/ch02-echo-chamber-v2/vuln_echo2.c`
- **级别:** 2 (SMEP + KASLR)
- **新概念:** 通过 ROP 链绕过 SMEP,通过信息泄漏绕过 KASLR,gadget 搜集
### ch03-object-store -- 堆漏洞利用 (UAF)
- **设备:** `/dev/objstore`
- **接口:** `ioctl()` -- 4 条命令 (create / read / write / delete)
- **源码:** `src/ch03-object-store/vuln_objstore.c`
- **级别:** 3 (SMEP + KASLR + SMAP)
- **新概念:** Use-after-free (释放后使用)、使用 `tty_struct` 进行堆喷射、栈迁移、绕过 SMAP
- **设计说明:** 此接口使用一次 ioctl 拷贝元数据,一次拷贝 payload 数据。许多真实驱动程序都是这样做的。
### ch04-secure-alloc -- 仅数据漏洞利用
- **设备:** `/dev/secalloc`
- **接口:** `ioctl()` -- 4 条命令 (create / write / read / destroy)
- **源码:** `src/ch04-secure-alloc/vuln_secalloc.c`
- **级别:** 4 (SMEP + KASLR + SMAP + stack canaries)
- **新概念:** 大小计算中的整数溢出、通过 `msg_msg` 实现堆 OOB、覆盖 `modprobe_path` - **不需要 ROP**
### ch05-concurrent-log -- 竞态条件
- **设备:** `/dev/conclog`
- **接口:** `ioctl()` -- 4 条命令 (alloc / read / write / put)
- **源码:** `src/ch05-concurrent-log/vuln_conclog.c`
- **说明:** 需要多核。所有级别均使用 `-smp 2` 运行。
- **级别:** 4(所有缓解措施)
- **新概念:** 非原子引用计数 → double free (双重释放)、用于稳定竞态的 `userfaultfd`/FUSE、`pipe_buffer` 喷射、直接 cred 覆盖
## 缓解级别
每个挑战通过 `./run.sh ` 支持 5 个难度级别。挑战默认处于其预期级别,但你可以覆盖它。
| 级别 | SMEP | SMAP | KASLR | KPTI | 你需要做什么 |
|-------|------|------|-------|------|---------------|
| 0 | 关 | 关 | 关 | 关 | ret2usr 直接有效。 |
| 1 | 开 | 关 | 关 | 关 | 无法从 ring 0 执行用户空间代码。需要 ROP。 |
| 2 | 开 | 关 | 开 | 关 | 内核基地址被随机化。需要信息泄漏。 |
| 3 | 开 | 开 | 开 | 关 | 无法从 ring 0 访问用户空间内存。需要栈迁移。 |
| 4 | 开 | 开 | 开 | 开 | 页表分离。需要干净地返回到用户空间。 |
## GDB
每个挑战都在端口 1234 上开放了一个 GDB stub。
```
gdb ./linux-6.1.75/vmlinux
(gdb) target remote :1234
(gdb) c
```
在虚拟机内部查找模块基地址:
```
cat /proc/modules | grep vuln
```
## 逆向 .ko 文件
内核模块是 ELF 对象。把它们扔进 Ghidra 或 IDA 中。寻找:
- `init_module` -- 注册设备
- `file_operations` 结构体 -- read/write/ioctl 的函数指针
- `copy_from_user` / `copy_to_user` -- 跨越内核边界的数据
- `kmalloc` / `kfree` -- 堆分配
- ioctl 命令常量
- 结构体布局
```
file challenges/ch01-echo-chamber/vuln_echo.ko
readelf -s challenges/ch01-echo-chamber/vuln_echo.ko
```
## 返回用户空间
在 `commit_creds(prepare_kernel_cred(0))` 成功后,你需要干净地返回到用户空间。如何做到这一点取决于启用的缓解措施。
**未开启 KPTI(级别 0-3):**
```
swapgs
iretq ← push: SS, RSP, RFLAGS, CS, RIP (in that order) before the chain
```
**在进入内核之前**,保存你的用户模式寄存器(`cs`、`ss`、`rsp`、`rflags`、`rip`)。你的 ROP 链以 `swapgs; iretq` 结束,这会从栈中弹出这五个值,并让你降落回你的用户空间函数(通常是一个调用 `system("/bin/sh")` 的函数)。
**开启 KPTI(级别 4):**
KPTI 分离了内核和用户页表。一个普通的 `swapgs; iretq` 会发生错误,因为内核页表在返回时会消失。内核提供了一个跳板:
```
swapgs_restore_regs_and_return_to_usermode
```
使用 `grep swapgs_restore_regs /proc/kallsyms` 找到它。你的 ROP 链应该跳转到这个函数(跳过前几条压入寄存器的指令 - 落在 `mov rdi, rsp` 这一点上)。它会为你处理页表切换和 `iretq`。
使用 GDB 找到确切的偏移量:
```
(gdb) disas swapgs_restore_regs_and_return_to_usermode
```
## 加载 Exploit
**选项 1:共享文件夹(推荐)** -- 无需重新构建:
```
gcc -static -o exploit exploit.c -lpthread
cp exploit challenges/ch01-echo-chamber/shared/
cd challenges/ch01-echo-chamber && ./run.sh 0
# VM 内部:/shared/exploit
```
所有的 `run.sh` 脚本都已经启用了 virtio-9p 并在 init 中挂载了 `/shared`。因此你可以测试新的 exploit,而无需每次重建 initramfs。
**选项 2:注入到 initramfs 中:**
```
gcc -static -o exploit exploit.c -lpthread
./tools/inject_exploit.sh challenges/ch01-echo-chamber ./exploit
cd challenges/ch01-echo-chamber && ./run.sh 0
# VM 内部:/home/ctf/exploit
```
## 从源码构建
```
sudo apt install -y gcc make flex bison bc libelf-dev libssl-dev \
busybox-static cpio wget qemu-system-x86
./build.sh # full build: kernel + modules + initramfs
./build.sh modules # modules only (needs kernel tree)
./build.sh initramfs # initramfs only (needs compiled .ko files)
```
## 关于真实性和多样性的说明
- CH04 和 CH05 看起来可能很相似,因为两者都可以以仅数据权限提升结束。但是它们使用了不同的 bug 类型和不同的目标(`msg_msg` + 全局目标 vs 竞态 + `pipe_buffer` + 堆 `cred` 目标)。
- 未来的更新可以添加更难的堆整理思路,例如更强的引用计数模式和更少直接暴露的悬空指针。
## 如果你的 PR 没有显示最新的 README
使用此快速检查:
```
git fetch origin
git checkout
git rebase origin/
git status
```
如果 GitHub 仍然显示冲突,请在本地解决 `README.md`,提交并再次推送。
## Flag 验证
```
./tools/verify_flag.sh ch01 "FLAG{your_flag_here}"
```
Flag 根据 SHA-256 哈希进行验证。本仓库中没有明文答案。
0xcd4
提示
该驱动程序将你的消息存储在栈上。当你的消息大于缓冲区时会发生什么?看看局部变量和返回地址之间有什么。`/proc/kallsyms` 告诉你所有东西的存放位置。提示
与 CH01 的溢出相同,但 ret2usr 不再起作用 - SMEP 阻止 CPU 在 ring 0 中执行用户空间代码。read 处理程序给你的字节数多于缓冲区包含的字节数。那些额外的字节中有什么?一旦你知道了内核基地址,就构建一个链:`prepare_kernel_cred` → `commit_creds` → `swapgs_restore_regs_and_return_to_usermode`。提示
创建一个对象,删除它,然后通过悬空指针读取。对象是 1024 字节 - 与 `tty_struct` 位于同一 slab。重复打开 `/dev/ptmx` 以将 tty 结构喷射到已释放的槽位中。`tty_operations` 指针泄漏了内核基地址。覆盖它以将 tty 操作重定向到栈迁移 gadget。SMAP 阻止直接访问用户空间内存 - 你的 ROP 链必须位于内核内存中。提示
该驱动程序使用 32 位运算将 64 字节的标头添加到你请求的大小中。当总和超过 `0xFFFFFFFF` 时会发生什么?极小的分配却有巨大的记录数据大小。使用 `msgsnd`/`msgrcv` 以 `msg_msg` 结构整理堆。破坏相邻的 `msg_msg` 以构建任意读取。找到并覆盖 `modprobe_path` - 然后使用未知二进制格式触发它。栈 canaries 使得 ROP 代价高昂;此挑战奖励仅数据的方法。提示
引用计数是一个普通的 `int`,而不是 `atomic_t`。递减路径使用共享(读)锁 - 两个 CPU 可以同时进入。在 put 命令上竞速两个线程:两者都读取到 refcount=1,两者都递减,两者都释放。使用 `userfaultfd` 来扩大窗口。在 double free 之后,使用 `pipe_buffer` 结构进行喷射(同样是 kmalloc-256 -- 使用 `pipe()` + `write()`)。`pipe_buffer->ops` 指针泄漏了内核基地址。破坏 `pipe_buffer->page` 以实现任意读/写,然后遍历任务列表以找到你的 cred 结构并将 uid/gid 清零。这与 CH04 不同:在那里你针对的是 `modprobe_path`(一个全局变量);在这里你针对的是你进程的 `cred` 结构(在堆上)。标签:CISA项目, CTF竞赛, CTF训练, CTF靶场, GDB调试, Ghidra, IDA Pro, kernel-ctf-lab, Kernel pwn, KPTI绕过, Linux Kernel Exploitation, Linux内核安全, Linux内核模块, pwntools, QEMU虚拟机, SMAP绕过, Vulnerability Exploitation, Web报告查看器, 二进制漏洞, 云资产清单, 内核ROP, 内核漏洞利用, 堆漏洞利用, 安全培训, 安全学习, 安全实验环境, 安全渗透, 客户端加密, 提权漏洞, 数据层攻击, 整数溢出, 栈溢出, 漏洞利用开发, 竞态条件, 身份验证强制, 逆向工程, 金丝雀保护