导语:2019年5月,Microsoft发布了针对远程代码执行(RCE)漏洞CVE-2019-0708的带外修复程序更新,该漏洞也称为“BlueKeep”,位于远程桌面服务(RDS)的代码中。
2019年5月,Microsoft发布了针对远程代码执行(RCE)漏洞CVE-2019-0708的带外修复程序更新,该漏洞也称为“BlueKeep”,位于远程桌面服务(RDS)的代码中。在过去的一年中,研究人员证明了BlueKeep的可利用性,并提出了检测和预防它的对策。但是,RDP仍然是当今攻击者使用的最受欢迎的攻击媒介之一。为了使RDP攻击更难成功并更好地保护Windows用户,研究人员将在此文中公开有关攻击者如何在Windows RDP终端上利用BlueKeep的详细信息。
在2019年8月,Unit 42的研究人员就对CVE-2019-0708做了详细研究,内容涉及如何使用位图缓存协议数据单元(PDU),Refresh Rect PDU和RDPDR客户端名称请求PDU将数据写入Windows内核内存。此外,2019年10月,Unit 42的研究人员在Microsoft的BlueHat Seattle 2019安全会议上介绍了三种带有RDP PDU新的Windows内核池风水技术和BlueKeep的两种不同利用技术。本文讨论了如何结合使用Refresh Rect PDU和RDPDR客户端名称请求PDU来获得具有系统特权的远程代码执行RCE。
快速查看Refresh Rect PDU和RDPDR客户端名称请求PDU
关于Windows RDP协议中的Refresh Rect PDU和RDPDR客户端名称请求PDU,请点此链接。
通过多次发送PDU,Refresh Rect PDU可用于喷洒许多0x828大小的内核池。那些0x828大小的内核池将按0x1000偏移量对齐,在每个0x828大小的内核池中,RDP客户端在偏移0x2c处控制8个字节的数据。八个字节控制数据的起始地址看起来像0x8xxxx02c,在测试过程中,将0x828大小的内核池从地址0x86000000连续整理到0x8a000000,并将0x1000大小的内核池在研究人员的VMware虚拟机上对齐。在概念开发证明(PoC)中,研究人员使用0x86c1002c作为固定地址来存储函数指针,并且在多次重复使用和重新启动系统时获得了很高的成功率。地址0x88xxx02c和0x89xxx02c也是很好的候选对象,它们在高内存消耗目标中表现出色,例如同时运行许多应用程序的RDP服务器。
RDPDR客户端名称请求PDU可通过多次发送PDU来回收具有受控数据的释放通道对象。 RDP服务器解析客户端名称请求PDU时分配的内核池的大小和数据都是可控的。研究人员可以使RDP服务器分配大小为0xd0的内核池,以声明释放的MS_T120通道对象。通过向内核池中注入特制的数据,研究人员可以控制重用路由以使用可控函数指针执行函数调用,从而控制扩展指令指针(EIP)。研究人员也可以使用客户端名称请求PDU将Shellcode写入内核池,因为客户端名称请求PDU的大小和数据都可以由RDP客户端控制。在以下各节中我们将讨论有关在攻击中如何使用这两个PDU的所有详细信息。
漏洞概述
为了明确说明漏洞利用,研究人员将简要介绍CVE-2019-0708发生的根本原因。 CVE-2019-0708是与悬空对象MS_T120虚拟通道相关的Use After Free(UAF)漏洞。 MS_T120虚拟通道是RDP服务器内部使用的两个默认通道(MS_T120和CTXTW)之一,该默认通道在建立RDP连接时初始化。但是,RDP客户端也可以通过将“MS_T120”项添加到客户端MCS Connect初始PDU中的channelDefArray来创建名为“MS_T120”的自定义虚拟通道,如图1所示。
channelDefArray中的自定义MS_T120通道
RDP服务器接收该请求,并在ChannelPointerTable对象中为MS_T120对象创建引用。图2显示ChannelPointerTable对象中有两个“MS_T120”通道对象引用。第一个为客户端MCS Connect初始PDU请求创建,第二个为RDP服务器内部使用而创建。
创建自定义的MS_T120通道对象
使用MCS通道加入请求加入自定义的MS_T120通道后,RDP客户端可以成功打开MS_T120通道。如果RDP客户端将制作的数据发送到MS_T120通道(如图3所示),则将调用rdpwx.dll模块中的MCSPortData函数。
用于释放MS_T120通道对象的PDU
在函数MCSPortData中,从第一个参数lpAddend的0x74偏移开始的数据由RDP客户端控制,并且当变量“v2”等于值“2”时,将调用MCSChannelClose以释放MS_T120通道对象,如图4所示。
关闭MCSPortData中的通道
在MS_T120通道对象被释放之后,仍然有一个悬空指针在ChannelPointerTable对象的slot 0x1f中指向被释放的MS_T120通道对象,如图5所示。
释放了自定义的MS_T120通道对象
当RDP客户端断开连接时,将调用函数RDPWD!SignalBrokenConnection,然后将调用函数termdd!IcaChannelInput来访问slot 0x1f中释放的MS_T120对象,如图6所示。
重用释放的MS_T120对象调用堆栈
释放后重用(Reuse After Free)
如上所述,研究人员已经讨论了如何释放MS_T120通道对象,并在ChannelPointerTable对象中留下了指向MS_T120通道对象的悬空指针。现在,研究人员将介绍如何重用释放的对象。在termdd!IcaChannelInput函数中,将调用函数IcaFindChannel查找通道对象。当RDP客户端终止连接时,第二个参数slot_base将为0x05,第三个参数slot_index将为0x1f,因此函数IcaFindChannel将释放的MS_T120通道对象设置为返回值。如果使用伪造的MS_T120通道对象回收释放的MS_T120通道对象,则可以通过从伪造的MS_T120通道内部获取函数指针(寄存器eax)的函数调用(call [eax])来控制以下函数执行路径对象,如图7所示。
在IcaChannelInputInternal中重用路由
使用RDPDR客户端名称请求PDU和控制EIP回收释放的MS_T120通道对象
在本节中,我们将讨论如何回收释放的MS_T120通道对象以控制EIP。释放的MS_T120通道的大小为0xc8(包括8个字节的池头的0xd0),如图5所示。termdd!IcaChannelInputInternal函数将分配channel_data_size + 0x20大小的内核池。如果研究人员希望函数termdd!IcaChannelInputInternal函数分配大小为0xc8的内核池,则将客户端名称请求PDU的大小设置为0xa8(0xc8-0x20),并将ComputerNameLen字段相应地设置为0x98。考虑到成功分配池后存在内存复制操作,通过多次发送这些客户端名称请求PDU,研究人员确保释放的MS_T120池slot已被客户端名称请求PDU数据占用。此外,大小为0xc8的内核池的前0x20字节是供termdd模块内部使用的,这意味着前0x20字节是不可控制的,而用于客户端名称请求PDU标头的以下0x10字节也不能控制可控制的,因此可控制的数据大小总计为0x98(0xc8-0x20-0x10)。图8显示了RDP客户端如何构造RDPDR客户端名称请求PDU。
RDPDR客户端名称请求回收释放的MS_T120通道对象
图9显示了通过发送RDPDR客户端名称请求PDU创建的虚假MS_T120通道对象的内存转储。伪造的MS_T120通道对象中的几个重要字段以不同的颜色标记。设置为绿色的4字节(DWORD)值0x00000000可以防止服务在hal!KeAcquireInStackQueuedSpinLockRaiseToSynch中崩溃,这由ExEnterCriticalRegionAndAcquireResourceExclusive调用。设置浅蓝色的DWORD值0x00000000以确保条件检查,并使函数路由通过受控的函数指针eax到达函数调用(call [eax])),如图7所示。
伪造的MS_T120通道对象
紫色的DWORD值0x86c10030设置在伪造的MS_T120通道对象的偏移量0x8c处,如图9所示。图10中的调试日志显示了如何在偏移量0x8c处获取伪造的对象地址并进行函数调用(call [eax])来控制EIP。
在IcaChannelInputInternal中具有受控函数指针的函数调用
现在,如何控制EIP已经解释清楚了,接下来就要讨论为什么将eax设置为地址0x86c10030,而将EIP设置为地址0x86c1002c。使用上一部分中介绍的技术,可以获得从地址0x86xxxxxx到0x8axxxxxx稳定的喷洒。在每个对齐的0x1000地址中,在偏移0x002c处有8个字节的数据是可以控制的,如图11所示。
带有Refresh Rect PDU的稳定喷洒
图12中的代码片段显示了RDP客户端如何构造Refresh Rect PDU以及将其发送到RDP服务器需要的次数。
如何构造Refresh Rect PDU以在RDP客户端中进行喷洒
在每个对齐的0x1000大小的内核池中的8个字节的可控数据中,将偏移量0x0030(0x86c10030)的4个字节设置为0x86c1002c,这是阶段0 shellcode的硬编码地址。偏移量为0x002c(0x86c1002c)的其他4个字节用于存储阶段0的shellcode。下一节将介绍有关Shellcode几个阶段的更多详细信息。
Shellcode
由于只有4个字节可用于阶段0 shellcode,因此研究人员使用了一个4字节shellcode的技巧“add bl,al; jmp ebx”,而不是“call/jmp ebx+30h” 跳转到shellcode中的一个阶段。当termdd!IcaChannelInputInternal执行 “call dword ptr [eax]” 汇编指令时,al为0x30,ebx指向伪造的MS_T120通道对象,RDP客户端可以在其中填充受控数据。第一阶段的shellcode从地址faked_MS_T120_channel_object + 0x30(0x867b3590)进入内核池,整个过程如图13所示。
阶段 0 Shellcode
值得一提的是,使用2字节的汇编代码 “add bl, al”,因为只能使用4字节的shellcode来实现“jmp ebx+0x30”。 这并不完美,因为当“bl”大于0xd0时,“add bl, al”中会出现溢出,使“jmp ebx”能够跳转到错误的地址,从而导致利用失败。但是,这已经足够好了,因为理论上成功率为81.25% (0xd/0x10)。
在讨论阶段一shellcode之前,研究人员将讨论阶段二或最终的shellcode。研究人员注意到,RDPDR客户端名称请求PDU也可以用于将任意大小的最终内核shellcode发送到RDP服务器中的内核池。例如,研究人员构造了一个RDPDR客户端名称请求PDU,其数据长度为0x5c8,并且嵌入了有效载荷,如图14所示。
RDPDR客户端名称请求PDU发送的最终shellcode
当将带有Shellcode嵌入的RDPDR客户端名称请求PDU发送到RDP服务器时,PDMCS – Hydra MCS协议驱动程序会将数据存储在内核池中。有趣的是,该内核池在堆栈上维护了一个引用,阶段一shellcode可以使用该引用来定位最终的shellcode。具体来说,此处的ECX寄存器指向堆栈,地址ECX+0x28存储内核池地址。最终的shellcode位于内核池的0x434偏移处,如图15所示。
阶段一shellcode
偏移量0x434在不同的Windows版本中可能会有所不同,但是,通过在内核池中搜索最终的shellcode,很容易将阶段一shellcode编写为egg hunter ,使其具有通用性。
修复内核以避免崩溃
下面的工作是内核开发的一个例程,最终的shellcode首先修复返回值并修复内核,以免在shellcode完成后崩溃。然后,它执行内核shellcode,以将APC插入lsass.exe或spoolsv.exe并执行用户模式shellcode。图16显示了最终的shellcode如何修复ChannelPointerTable对象,修改返回地址并模拟ExReleaseResourceAndLeaveCriticalRegion函数的执行,以在KTHREAD中包含WORD值。
最终的shellcode修复内核
修复内核后,将执行内核shellcode的函数部分。为了说明此漏洞利用,研究人员使用了Sleepya发布的内核shellcode模板进行eternalblue漏洞利用。用于演示的WinExec(' calc ')用户shellcode如图17所示。
最终的shellcode usermode shellcode
总结
整个攻击链可以描述如下:
1.与受害者建立联系;
2.用Refresh Rect PDU喷洒;
3.发送精心设计的PDU,强制释放MS_T120通道对象;
4.使用多个RDPDR客户端名称请求PDU占用释放的MS_T120通道对象;
5.发送带有RDPDR客户端名称请求PDU的最终shellcode;
6.终止连接以重新使用释放的MS_T120通道对象、控制EIP并执行各个阶段的Shellcode。
本文翻译自:https://unit42.paloaltonetworks.com/cve-2019-0708-bluekeep/如若转载,请注明原文地址: