CVE-2019-8635:macOS提权及任意代码执行漏洞分析

 

0x00 前言

我们在macOS中发现了一个双重释放(double free)漏洞(编码为CVE-2019-8635),该漏洞由AMD组件的内存破坏缺陷所导致。如果成功利用该漏洞,攻击者可以实现权限提升,以root权限在系统上执行恶意代码。我们向Apple反馈了漏洞情况,厂商后面也发布了相应补丁

这个CVE编号实际上覆盖了两个缺陷:discard_StretchTex2Tex方法以及AMDRadeonX400_AMDSIGLContext这个AMD Radeon类中对边带令牌(sindeband token)的处理逻辑AMDRadeonX400_AMDSIGLContext派生自IOAccelGLContext2类,而后者由IOAccelContext2类扩展而来。这些类用来在macOS主机上渲染图像。

漏洞位于discard_StretchTex2Tex以及AMDSIGLContext::process_StretchTex2Tex函数中,这两个函数是AMDRadeonX4000_AMDSIGLContext类的函数,我们可以使用AMDRadeonX4000_AMDSIGLContext userclient以及selector 2对应的函数IOAccelContext2::submit_data_buffers来访问这个类,使用connect type 1来打开AMDRadeonX4000_AMDGraphicsAccelerator客户端。

 

0x01 AMDRadeonX4000_AMDSIGLContext discard_StretchTex2Tex双重释放权限提升漏洞

攻击者可以利用该漏洞在用户空间上执行代码。为了利用该漏洞,攻击者首先必须具备在目标macOS系统上执行低权限代码的能力。

该缺陷是因为系统没有对用户提供的数据进行适当的验证,导致读取操作超出已分配数据结构的末尾地址。攻击者可以利用这一点,再与其他漏洞结合起来,将权限提升至内核级别。

 

0x02 AMDRadeonX4000_AMDSIGLContext双重释放权限提升漏洞

同一个AMD类对边带令牌的处理过程中也存在一个双重释放漏洞。本地攻击者可以利用该漏洞在受影响的macOS上执行任意命令。与上一个漏洞一样,攻击者首先必须具备在目标系统上执行低权限代码的能力,才能进一步利用该漏洞。

虽然前一个漏洞位于AMDRadeonX4000_AMDSIGLContext:discard_StretchTex2Tex函数中,但这个漏洞位于AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex函数中。漏洞成因在于系统在对目标对象执行操作前,没有去验证该对象是否存在。攻击者可以利用该漏洞将权限提升至内核级别。

从本质上讲,这两个漏洞在可能的利用途径方面比较相似,但在于具体利用的函数方面有所区别。

 

0x03 漏洞分析

图1. AMDRadeonX4000_AMDSIGLContext: discard_StretchTex2Tex函数伪代码片段(上图),AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex函数伪代码片段(下图)

如图1(上图)所示,如果(cmdinfo+32)等于0x8c00,那么IOAccelResource v10以及v11都会从IOAccelShared2中取值,对应的索引分别为*(shareMem_start_address_187_offset16+8) 以及*(shareMem_start_address_187_offset16+12)。该函数随后会使用IOAccelResource2::clientRelease()函数来释放两个加速器资源。然而攻击者可以从用户空间中,通过内存映射,使用IOAccelContext2 userclient来直接控制这两个索引。如果用户空间为lookupResource函数映射相同的索引,那么clientRelease就会两次释放相同的资源客户端,此时就会出现双重释放漏洞。

如图1(下)所示,如果v15等于0x8c00,那么accelResource_offset8以及accelResource_offset12都会从IOAccelShared2中取值,以共享内存偏移24及28的值作为索引。最终,该函数会从IOAccelShared2 _rst释放accelResource_offset12,如果accelResource_offset8->member2不等于10,该函数也会从IOAccelShared2释放accelResource_offset8。然而将共享内存偏移24及28对应的值设成相同值,就会导致系统两次释放同一个accelResource

process_StretchTex2Tex()函数中,完成stretch操作时会使用IOAccelResource2::clientRelease()函数来释放两个资源客户端。然而,这两个accelResource2源自AMDRadeonX4000_AMDSIGLContext类中的accelShare2共享内存,使用对应的索引通过IOAccelShared2::lookupResource函数来获取。攻击者可以从用户空间中,通过IOAccelContext2用户客户端,利用内存映射来控制这些索引值。如果用户空间对lookupResource函数映射相同的索引,那么clientRelease就会两次释放相同的资源客户端,最终出现双重释放漏洞。

根据这两处代码执行流,共享内存地址指向的都是commandStreamInfo + 24。然而,commandStreamInfo缓冲区实际上在IOAccelContext2::processSidebandBuffe函数中设置,如下图所示。在图2中,v5指向的是shareMem + 16,而this->member196指向的是commandStreamInfo + 24

图2. IOAccelContext2::processSidebandBuffer伪代码片段

IOAccelContext2::clientMemoryForType函数的伪代码片段如图3所示。该函数由已知的IOConnectMapMemory64 API来调用,而后者会将一个用户缓冲区映射到内核空间中。在使用IOConnectMapMemory64函数时,我们需要设置连接对象、内存类型以及其他一些参数。此处连接对象为IOAccelContext2的实例,内存类型为0,如图3所示。当我们将内存类型设置为0时,clientMemoryForType函数会创建一个缓冲区内存描述符,返回用户空间的起始地址。此外,该函数还会将缓冲区内存地址设置为shareMem_start_vm_address_187变量(我们设置的变量名,非原始代码使用的变量名),该变量实际上正是在IOAccelContext2::processSidebandBuffer函数中使用的值。

根据该代码流程,我们可以控制共享缓冲区,以类似的方式设置这两个资源索引,随后触发双重释放漏洞。

图3. IOAccelContext2::clientMemoryForType函数伪代码片段

应用崩溃日志中的回溯信息如下所示,其中如果使用discard_StretchTex2Tex函数,那么只有AMDRadeonX4000AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex以及process_StretchTex2Tex(IOAccelCommandStreamInfo&) + 2893`函数偏移有所区别。

* thread #1, stop reason = signal SIGSTOP
frame #0: 0xffffff7f8d7adc37 IOAcceleratorFamily2`IOAccelResource2::clientRelease(IOAccelShared2*) + 13
frame #1: 0xffffff7f8d880dad AMDRadeonX4000`AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex(IOAccelCommandStreamInfo&) + 2893
frame #2: 0xffffff7f8d79b5d5 IOAcceleratorFamily2`IOAccelContext2::processSidebandBuffer(IOAccelCommandDescriptor*, bool) + 273
frame #3: 0xffffff7f8d8885e4 AMDRadeonX4000`AMDRadeonX4000_AMDSIGLContext::processSidebandBuffer(IOAccelCommandDescriptor*, bool) + 182
frame #4: 0xffffff7f8d79bae7 IOAcceleratorFamily2`IOAccelContext2::processDataBuffers(unsigned int) + 85
frame #5: 0xffffff7f8d7a2380 IOAcceleratorFamily2`IOAccelGLContext2::processDataBuffers(unsigned int) + 804
frame #6: 0xffffff7f8d798c30 IOAcceleratorFamily2`IOAccelContext2::submit_data_buffers(IOAccelContextSubmitDataBuffersIn*, IOAccelContextSubmitDataBuffersOut*, unsigned long long, unsigned long long*) + 1208
frame #7: 0xffffff800b027a3c kernel.development`::shim_io_connect_method_structureI_structureO(method=, object=, input=, inputCount=, output=, outputCount=0xffffff8742023968) at IOUserClient.cpp:0 [opt]
frame #8: 0xffffff800b025ca0 kernel.development`IOUserClient::externalMethod(this=, selector=, args=0xffffff87420239b8, dispatch=0x0000000000000000, target=0x0000000000000000, reference=) at IOUserClient.cpp:5459 [opt]
*frame #9: 0xffffff800b02ebff kernel.development`::is_io_connect_method(connection=0xffffff80b094e000, selector=2, scalar_input=, scalar_inputCnt=, inband_input=, inband_inputCnt=136, ool_input=0, ool_input_size=0, inband_output=””, inband_outputCnt=0xffffff80b0d81e0c, scalar_output=0xffffff8742023ce0, scalar_outputCnt=0xffffff8742023cdc, ool_output=0, ool_output_size=0xffffff80ab5c7574) at IOUserClient.cpp:3994 [opt]
frame #10: 0xffffff7f913044c2
frame #11: 0xffffff800a9bbd64 kernel.development`_Xio_connect_method(InHeadP=, OutHeadP=0xffffff8742023ce0) at device_server.c:8379 [opt]
frame #12: 0xffffff800a88d27d kernel.development`ipc_kobject_server(request=0xffffff80ab5c7400, option=) at ipc_kobject.c:359 [opt]
frame #13: 0xffffff800a859465 kernel.development`ipc_kmsg_send(kmsg=0xffffff80ab5c7400, option=3, send_timeout=0) at ipc_kmsg.c:1832 [opt]
frame #14: 0xffffff800a878a75 kernel.development`mach_msg_overwrite_trap(args=) at mach_msg.c:549 [opt]
frame #15: 0xffffff800a9f63a3 kernel.development`mach_call_munger64(state=0xffffff80af471bc0) at bsd_i386.c:573 [opt]
frame #16: 0xffffff800a823486 kernel.development`hndl_mach_scall64 + 22

当Mac系统出现内核错误(kernel panic)时,panic文本会被添加到日志中。kernel panic指的是内核检测到的系统错误,根源在于内核代码中没有对处理器异常进行处理(比如引用了无效的内存地址,或者调用链中出现错误,可以对比用户空间代码检测到的错误)。panic日志如下所示:

panic(cpu 6 caller 0xffffff800aa1391c): Kernel trap at 0xffffff7f8d7adc37, type 14=page fault, registers:
CR0: 0x0000000080010033, CR2: 0x0000000000000018, CR3: 0x0000000fea85f063, CR4: 0x00000000001626e0
RAX: 0x0000000000000000, RBX: 0xffffff800b473e28, RCX: 0x00000000ffffffff, RDX: 0x0000000000000000
RSP: 0xffffff8742023610, RBP: 0xffffff8742023610, RSI: 0xffffff80b0f8e470, RDI: 0xffffff80afa29300
R8: 0x0000000000000229, R9: 0xffffff800b2c4d00, R10: 0xffffff800b2c2c70, R11: 0x0000000000000058
R12: 0xffffff87299cb9b4, R13: 0x0000000000000001, R14: 0xffffff80b094e608, R15: 0xffffff80b094e000
RFL: 0x0000000000010282, RIP: 0xffffff7f8d7adc37, CS: 0x0000000000000008, SS: 0x0000000000000010
Fault CR2: 0x0000000000000018, Error code: 0x0000000000000002, Fault CPU: 0x6, PL: 0, VF: 0

与此同时,寄存器调试信息中关于kernel panic的信息如下所示。$r12寄存器指向的是共享内存地址+ 16,资源索引值也为0x42

(lldb) register read
General Purpose Registers:
rax = 0x0000000000000000
rbx = 0xffffff800b473e28 kernel.development`kdebug_enable
rcx = 0x00000000ffffffff
rdx = 0x0000000000000000
rdi = 0xffffff80afa29300
rsi = 0xffffff80b0f8e470
rbp = 0xffffff8742023610
rsp = 0xffffff8742023610
r8 = 0x0000000000000229
r9 = 0xffffff800b2c4d00 kernel.development`zone_array + 8336
r10 = 0xffffff800b2c2c70 kernel.development`zone_array
r11 = 0x0000000000000058
r12 = 0xffffff87299cb9b4
r13 = 0x0000000000000001
r14 = 0xffffff80b094e608
r15 = 0xffffff80b094e000
rip = 0xffffff7f8d7adc37 IOAcceleratorFamily2`IOAccelResource2::clientRelease(IOAccelShared2*) + 13
rflags = 0x0000000000010282
cs = 0x0000000000000008
fs = 0x00000000ffff0000
gs = 0x00000000afa20000

(lldb) x/20g $r12
0xffffff87299cb9b4: 0x00000364001a8c00 0x0000004200000042
0xffffff87299cb9c4: 0x0000104000000101 0x0055550000900002
0xffffff87299cb9d4: 0x0004000800040008 0x1048000000010001
0xffffff87299cb9e4: 0x0055560000900002 0x0002000800020008
0xffffff87299cb9f4: 0x0000000000010001 0x0000000000000000
0xffffff87299cba04: 0x0000000400000004 0x0000000000000000
0xffffff87299cba14: 0x0000000200000002 0x00000364001a8c00
0xffffff87299cba24: 0x0000004200000042 0x0000104800000101
0xffffff87299cba34: 0x0055560000900002 0x0002000800020008
0xffffff87299cba44: 0x1050000000010001 0x0055570000900002

 

0x04 缓解措施

攻击者可以使用双重释放漏洞来攻击未打补丁的macOS系统,成功在目标主机上获取较高权限。Apple已推出安全补丁,改进内存处理机制来修复内存破坏问题。macOS Mojave 10.14.4已经可以使用最新补丁,大家应当尽快更新系统。此外,大家也可以安装类似Trend Micro Antivirus for Mac以及Trend Micro Protection Suites之类的解决方案来检测并阻止使用利用各种缺陷的攻击行为。

(完)