Analysis of CVE-2019-0708

阅读量    83323 |   稿费 180

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

该作者是国外著名的恶意软件研究安全员,在漏洞爆发之初就开始投入到该漏洞的研究过程中。大概在漏洞公开一周后,他就在twitter上开始分享自己的研究成果,知道5月31号github上公开了蓝屏poc,他才在公开了自己的研究报告。

Binary Diffing

一般情况下,对漏洞的研究会首先从补丁对比入手(此次的漏洞补丁涉及到的文件只有一个:TermDD.sys)。对比结果如下图所示:

TermDD.sys补丁对比

 

Function Analysis

从补丁对比图来看,其他函数的变化较为平常,需要重点关注两个函数: “_IcaBindVirtualChannels” and “_IcaRebindVirtualChannels”。这两个函数包含一样的变化,所以首先从前者开始分析(原因是Bind操作很可能发生在Rebind操作之前)。

IcaBindVirtualChannels函数前后对比
(左侧是原始的IcaBindVirtualChannels函数,右侧是经过补丁更新的IcaBindVirtualChannels函数)

从函数的流程上,补丁更新后添加了新的逻辑,改变了原来调用_IcaBindChannel的方式。

如果比较的字符串等于“MS_T120”,则_IcaBindChannel的第三个参数被设置为31。而且只有在v4+88位置的内容等于”MS_T120”时才会发生变动。

基于以上事实,可以假设如果要触发漏洞,该条件必须为true。接下来,就需要关注一下这个”v4 + 88”了。

看一下IcaFindChannelByName函数的逻辑流程:

IcaFindChannelByName函数

首先从函数名来说,该函数应该是用作按照信道名称寻找信道。进入函数内部看逻辑,该函数似乎对信道表进行迭代,寻找特定信道。

在函数的第18行,a3和v6 + 88之间有一个字符串的比较,如果两个字符串相等则返回v6。

综上假设:a3为要寻找的信道名称,v6位信道的相关结构,v6 + 88返回的是信道结构中的信道名称。

总结以上信息,”MS_T120”是一个信道名称。后续的工作为如何调用此函数,以及如何将信道名称设置为”MS_T120”。

 

Debug Analysis

在函数IcaBindVirtualChannels上设置断点,也就是调用IcaFindChannelByName的位置。之后,发起一个正常的RDP连接。每次触发断点时,检查信道名称和调用堆栈。

堆栈调用

图中显示的是第一次调用IcaBindVirtualChannels时的callstack和channel名称

第一次调用IcaBindVirtualChannels是为了想检查的信道MS_T120。在该信道之后的名称分别是“CTXTW”,“rdpdr”,“rdpsnd”和“drdynvc”。

不幸的是,只有在FindChannelByName成功(即信道已存在)时才会到达易受攻击的代码路径。

在这种情况下,函数失败并导致创建MS_T120信道。要触发漏洞,需要第二次调用IcaBindVirtualChannels,MS_T120作为信道名称。

现在的任务是弄清楚如何调用IcaBindVirtualChannels。从堆栈调用中可以看到IcaStackConnectionAccept,因此信道可能在连接时创建。只需要找到一种在连接后打开任意信道的方法。

 

Traffic Analysis

Connection sequence流量

图中是一个RDP Connection sequence流量。

信道数组

在发送的第二个数据包中,包含传递给IcaBindVirtualChannels的六个信道中的四个,没有了MS_T120和ctxtw。

可以看到MS_T120和CTXTW没有在任何其他地方进行设置,但它们在其余信道之前打开了,这也就说明它们会自动打开。
对于自动打开的MS_T120,如果手动添加一个名称为”MS_T120”的信道将会发生什么?将断点移动到某些代码后(如果FindChannelByName成功,断点触发)

漏洞代码路径

图中可以看到,在将MS_T120信道添加到信道数组中后,断点触发,程序中断

OK,现在漏洞路径已经找到了,后续工作就是要看看能利用这个干点什么了。

 

Continue Debugging

为了对信道的操作更深入地了解,需要了解一下它是由什么创建的,在哪里创建的。
在IcaCreateChannel函数下断,然后重开一个新的RDP连接:

IcaCreateChannel堆栈调用

上图显示的是断点断下时堆栈调用的情况。

跟随堆栈调用,可以看到ntdll!NtCreateFile从用户模式向内核模式的转换。Ntdll仅仅提供了一个thunk,所以不用过多关注。
再往下是ICAAPI,它是用户模式下的TermDD.sys的一个副本。对ICAAPI的调用位于IcaChannelOpen,所以它很可能是IcaCreateChannel的用户模式等效项。
IcaOpenChannel是用于打开所有信道的正常函数,所以需要进入下层去看rdpwsx!MCSCreateDomain。

MCSCreateDomain函数

该函数实现了:1,使用”MS_T120”硬编码的方式调用IcaChannelOpen; 2, 利用返回的信道句柄创建一个IoCompletionPort。
“CompletionPort”变量是Completion Port句柄。通过查看句柄的xrefs可以找到处理端口I/O的函数。

CompletionPort的交叉引用

可以从MCSInitialize函数入手,用作初始化的函数总是最佳切入点:

MCSInitialize函数

为Completion Port创建了一个线程,入口点是IoThreadFunc,跟进:

IoThreadFunc函数

GetQueuedCompletionStatus用于检索送到completion port(即信道)的数据。如果成功接收数据,则将其传递给MCSPortData。

为了证实理解正确,此处使用一个基本的RDP客户端,它具有在RDP通道上发送数据的能力。使用前面解释的方法打开了MS_T120信道。

打开后,在MCSPortData上设置断点;然后,将字符串“MalwareTech”发送到信道。

MCSPortData断点位置

断在了MCSPortData,因为数据已经被发送到了信道。

也就是说,可以向MS_T120信道读取或写入数据。接下来,看一下MCSPortData使用信道数据做了什么操作:

MCSPortData函数

ReadFile告诉我们数据缓冲区从channel_ptr + 116开始。在函数顶部附近是对chanel_ptr + 120执行的检查(偏移4进入数据缓冲区)。如果dword设置为2,则该函数调用HandleDisconnectProviderIndication和MCSCloseChannel。

代码看起来像信道连接/断开事件的某种处理程序。在查看正常情况下触发此功能的内容后,可以确定MS_T120是一个内部通道,通常不会从外部暴露。

如果发送了触发MCSChannelClose调用所需的数据:

蓝屏崩溃

直接蓝屏

查看bugcheck看一下发生了什么:

Bugcheck查找原因

可以看到,当客户端断开时,系统尝试关闭MS_T120信道,但是我们之前就已经关闭了该信道,从而造成了一个双重释放。

由于在Windows Vista之后加入了一些缓解措施,双重释放漏洞通常不好利用,但不代表不能利用。

ChannelId清理函数

图中代码为断开连接时的清理操作的代码。

在内部,系统创建MS_T120通道并使用ID 31绑定它。但是,当使用易受攻击的IcaBindVirtualChannels代码绑定它时,它将与另一个id绑定。

Patch注释对比

从本质上来说,MS_T120信道被绑定了两次(一次在系统内部,一次是我们手动绑定的)。
由于信道绑定在两个不同的id下,我们得到两个单独的引用。 当一个引用用于关闭信道时,引用将被删除,信道也将被删除;但是,另一个引用仍然存在(称为use-after-free)。
使用没有被删除的引用,就可以内核内存了。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多