48

微软RDP服务高危UAF漏洞分析(CVE-2019-0708)

 5 years ago
source link: https://www.tuicool.com/articles/2mumE3I
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一、简介

CVE-2019-0708经微软披露已经有一个多月了,本文将主要围绕以下几个方面介绍该漏洞。

1、 经过分析验证该漏洞是一个UAF漏洞,引发UAF漏洞的指针是由何时创建以及为何该指针在Free之后又被使用,是本文重点关注的地方。

2、 该漏洞属于RDP协议实现方面的漏洞,文中会列举与该漏洞相关的RDP协议知识。

二、RDP协议介绍

2.1 协议简介

远程桌面协议(RDP, RemoteDesktop Protocol)是一个多通道(multi-channel)的协议。

RDP协议也是C/S网络结构,双方通过TCP连接进行通信,基本也是基于请求/响应这样的数据交换模式,这里贴一张来自微软发布的RDP协议时序图,该图详细描述了RDP连接中请求及响应的顺序及过程。

YjqQju7.jpg!web

该文档([MS-RDPBCGR].pdf)地址如下: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/5073f4ed-1e93-45e1-b039-6e30c385867c

文中不介绍通讯细节,有兴趣的读者可以自行阅读该文档。

2.2 静态虚拟信道(Static Virtual Channels)

静态虚拟通道允许RDP Client和RDP Server通过主RDP数据连接通信。虚拟通道数据是特定于应用程序的,对RDP不透明。连接时最多可以创建31个静态虚拟通道。RDP Client在连接序列的Basic Settings Exchange阶段请求并确认所需虚拟通道列表,并在ChannelConnection阶段进行信道的连接。

每个虚拟通道充当独立的数据流。RDP Client和RDP Server检查在每个虚拟通道上接收的数据,并将数据流路由到适当的处理函数以进行进一步处理。

三、UAF成因及调试过程

已确认的是,CVE-2019-0708为UAF漏洞,众所周知UAF漏洞主要是由于对象指针在释放后再次被使用而引发的安全问题。那么在这一部分内容中,我将描述引发UAF漏洞的指针是由何时创建以及为何该指针在Free之后又被使用。

3.1 触发UAF的过程

1) RDP连接建立,RDP Server 默认调用IcaCreateChannel()创建MS_T120静态虚拟信道,并绑定到0x1F信道号,此时是该信道第一次绑定。

2) RDP Client 在通讯的Channel Connecttion阶段告知 RDPServer 绑定名称为“MS_T120”的信道到指定信道,此时Server使用IcaFindChannelByName()函数搜索到默认创建的MS_T120信道,将该信道对象绑定到用户指定的信道号上。此时是MS_T120信道第二次绑定。

3) 至此,MS_T120信道已经完成2次绑定。随后RDP Client 告知RDP Server断开第二次绑定的信道,该操作会引发RDP Server 调用IcaFreeChannel()释放该信道并释放该对象占用的空间。

4) 随后RDP Client 将通知RDP Server关闭RDP连接,此操作会引发Server调用SingalBrokenConnection()函数释放信道号0x1F的MS_T120信道对象。由于该对象空间已经释放过,这里再次调用IcaFreeChannel()函数执行清理操作,其中ExDeleteResourceLite()会引用该对象的成员数据而触发UAF漏洞。

3.2 调试过程

下图是漏洞补丁修复前后对比图:

ruEJBnI.jpg!web

关键的修改是针对_IcaBindChannel()函数的调用前增加了一个条件判断,判断的内容是stricmp() 返回值,也就是字符串是否相等。

而以字面意思解读icaFindChannelByName(),就是以名字查找信道。下图为该函数的实现,通过遍历列表,可以确定的是信道对象中偏移0×94的位置就是信道名称。

UnAZ3iJ.jpg!web

回过头来看漏洞补丁的代码,实际上打过补丁后,在调用icaBindChannel()函数的之前,也是进行信道名称的判定,当信道名称为”MS_T120” 的时候,后续调用icaBindChannel()的第三个参数,强制改为0x1F。

这里看一看icaBindChannel()函数的实现,关键在第12行的代码中,会将传入该函数参数1的信道指针,写入一个内存地址中。显而易见的是,写入的地方是通过参数2及参数3计算得到。

NVBjQb3.jpg!web

实际上这个函数就是漏洞的关键,至于为什么关键,我们后面再谈。首先先介绍一下,引起UAF的对象指针是何时创建的。

早在前文已经介绍过, RDP协议定义静态虚拟信道,而名称为MS_T120的信道就是其中一个。MS_T120在RDP协议建立之初,就会由RDP服务端主动创建,本次漏洞引起UAF的对象指针就是MS_T120信道。termdd!icaCreateChannel()函数用于创建信道,在该函数设置断点,使用微软远程桌面连接工具连接并观察一下该信道建立的过程。

6vIraqE.jpg!web

通过分析该函数代码可知,参数2偏移0xC的位置为信道名称。建立RDP连接,WinDbg停在IcaCreateChannel()处,其参数中的名称正是MS_T120

IvMRNvq.jpg!web

进一步跟踪该函数,进入了关键函数_IcaAllocateChannel(),如下图,该函数首先调用ExAllocatePoolWithTag()申请空间,之后就是对象成员初始化工作。

eMryMnA.jpg!web

iaayeii.jpg!web

值得注意的是,在初始化完部分成员变量之后,又调用了icaBindChannel(), 在windbg中实时跟踪该调用:

ZVZbQrI.jpg!web

可以发现,此时的参数3为0x1F,此时调用icaBindChannel()将新创建的MS_T120信道放入数组下标0x1F的位置。

也就是说,MS_T120信道对象指针在RDP 连接创建的时候就会建立,并立即绑定到0x1F信道号中。这是该指针创建的地方,在这里还将该信道绑定到了0x1F信道号中。

R7FR7rR.jpg!web

此时回过头来看微软补丁修复的地方,未修复之前,程序代码在调用icaFindChannelByName()之后,紧接着调用icaBindChannel()将信道绑定到指定的信道号中。

在修复之后,会判断信道是否为MS_T120,如果是,将绑定的信道号重定向为0x1F,而不是用户指定的信道号。实际上UAF漏洞的关键就在这里,我们知道MS_T120信道在连接建立之初就已经和0x1F绑定,此时如果再次将MS_T120和另一个信道号绑定,在关键数组中就会存在2个指针值,也就是绑定了2次。

yAniUzI.jpg!web

目前在Github上( https://github.com/n1xbyte/CVE-2019-0708/ )有一份可以UAF导致蓝屏的POC,下面跟踪验证一下。

bmi6Vjm.jpg!web

该POC使用Python编写,在ubuntu上安装python环境即可运行该POC

3uEvIfR.png!web

该POC通过发送MCS Connect Initial请求,触发RDP 服务端中icaBindVirtualChannel()中引发UAF漏洞的代码。

AVbUjyE.jpg!web

继续单步走观察调用_IcaBindChannel()时的信道号,下图可见此时信道号为3.此时会将MS_T120信道与POC中指定的3号信道号绑定。

6b2qimf.jpg!web

之后该POC会发送数据包通知RDP服务端断开3号信道的连接,这将会引发服务端调用icaFreeChannel(), 该函数会调用ExFreePoolWithTag()释放空间。

vaiQJv7.jpg!web

Jf2I3i7.jpg!web

之后POC通知RDP Sever关闭RDP连接,而在关闭连接的时候,会触发默认的位于下标0x1F的信道释放操作,如下图所示(图片为多次调试所截取,其中关键指针值不同不要引起疑惑):

AB36rey.jpg!web

这里继续单步走,可以发现触发了内核异常。

UJFZn2J.jpg!web

F5继续运行系统,引发蓝了。显示如下

FR3ARzY.jpg!web

经过智能分析后如下,核心原因则是0x83e9b362处的代码对ecx保存的内存地址进行了写,可以看到的是,当时的ecx为0,根据异常类型表示,当前IRQL无法对0地址进行读写。

MjQfEjA.jpg!web

UNBfear.jpg!web

仔细看下2张图可以发现,ecx 来源于edi,而edi 是icaFreeChannel()的传入参数,也就是待释放的信道对象指针。已释放的指针被再次引用,所以导致了漏洞。

fMVjue7.jpg!web

fUZnMbm.jpg!web

这里引用的来源即icaFreeChannel()中调用的ExDeleteResouceLite(),在释放信道对象之前,会使用该对象的一些数据。

3eaeAbB.jpg!web

四、结语

通过以上的分析可知,MS_T120信道被绑定两次(一次由RDPserver创建并绑定,第二次由我们发送数据包绑定)。由于信道绑定在两个不同的ID下,我们得到两个独立的引用。

当使用其中的一个引用来关闭信道时,将删除该引用,信道对象也将释放。但是,另一个引用仍然存在。如果我们可以在第一次释放信道对象空间之后,通过内核POOL喷射,获得在该信道对象填充自定义数据的能力,在第二次调用IcaFreeChannel()进行空间释放时,由于该函数会引用已被控制的内核对象,就有机会造成读写任意内核地址进而达到任意代码执行的目的。

*本文作者:alphalab,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK