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
函数,那么只有AMDRadeonX4000
AMDRadeonX4000_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日志如下所示:
与此同时,寄存器调试信息中关于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之类的解决方案来检测并阻止使用利用各种缺陷的攻击行为。