登陆 注册

分析CVE-2019-0708(BlueKeep)

安云 2019-06-03 CVE-2019-0708BlueKeep漏洞分析
黑白网(heibai.org)成立于2014年,多年来以其专业的视角,优质的服务为广大安全技术爱好者提供了目前国内最全的网络安全技术学习资料,普及中国网络安全知识,宣扬正确的黑客极客文化,全方面提高国内安全技术水平。

我拒绝了这一说明,直到概念验证(PoC)公之于众,以免造成任何伤害。现在github上有多个拒绝服务的PoC,我发布了我的分析。

二进制差异

和往常一样,我开始使用修补程序修改的二进制文件的BinDiff(在这种情况下只有一个:TermDD.sys)。下面我们可以看到结果。


TermDD.sys的BinDiff前后补丁。


除了“_IcaBindVirtualChannels”和“_IcaRebindVirtualChannels”之外,大多数变化都变得非常平凡。这两个函数都包含相同的更改,所以我专注于前者,因为绑定可能会在重新绑定之前发生。


原始IcaBindVirtualChannels位于左侧,修补版本位于右侧。


添加了新逻辑,改变了调用_IcaBindChannel的方式。如果比较的字符串等于“MS_T120”,则_IcaBindChannel的参数3设置为31。

基于仅在v4 + 88为“MS_T120”时才发生更改的事实,我们可以假设要触发错误,此条件必须为真。所以,我的第一个问题是:什么是“v4 + 88”?

看着IcaFindChannelByName里面的逻辑,我很快找到了答案。


在IcaFindChannelByName内


使用英语的高级知识,我们可以解释IcaFindChannelByName按名称查找频道。

该函数似乎迭代通道表,寻找特定通道。在第17行,a3和v6 + 88之间有一个字符串比较,如果两个字符串相等则返回v6。因此,我们可以假设a3是要查找的通道名称,v6是通道结构,v6 + 88是通道结构中的通道名称。

使用以上所有,我得出结论“MS_T120”是一个频道的名称。接下来我需要弄清楚如何调用此函数,以及如何将通道名称设置为MS_T120。

我在IcaBindVirtualChannels上设置了一个断点,就在调用IcaFindChannelByName的地方。之后,我使用合法的RDP客户端连接到RDP。每次触发断点时,我都会检查通道名称和调用堆栈。


第一次调用IcaBindVirtualChannels时的callstack和channel名称


第一次调用IcaBindVirtualChannels是为了我想要的频道MS_T120。随后的通道名称是“CTXTW”,“rdpdr”,“rdpsnd”和“drdynvc”。

不幸的是,只有FindChannelByName成功(即通道已存在)才会触及易受攻击的代码路径。在这种情况下,函数失败并导致创建MS_T120通道。要触发错误,我需要第二次调用IcaBindVirtualChannels,MS_T120作为频道名称。

所以我现在的任务是弄清楚如何调用IcaBindVirtualChannels。在调用堆栈中是IcaStackConnectionAccept,因此通道可能在连接时创建。只需要找到一种在连接后打开任意通道的方法......也许嗅探合法的RDP连接会提供一些见解。


捕获RDP连接序列
通道数组,如WireShark RDP解析器所示


发送的第二个数据包包含我看到传递给IcaBindVirtualChannels的六个通道名称中的四个(缺少MS_T120和CTXTW)。通道按照它们出现在数据包中的顺序打开,所以我认为这正是我需要的。

看到MS_T120和CTXTW没有在任何地方指定,但在其余通道之前打开,我想它们必须自动打开。现在,我想知道如果我实现协议会发生什么,然后将MS_T120添加到通道数组中。

将我的断点移动到某些代码后,如果FindChannelByName成功,我就运行了我的测试。


将MS_T120添加到通道阵列后,会触发断点


真棒!现在,易受攻击的代码路径被击中,我只需要弄清楚可以做些什么......

为了更多地了解频道的作用,我决定找到创建频道的内容。我在IcaCreateChannel上设置了一个断点,然后启动了一个新的RDP连接。


命中IcaCreateChannel断点时的调用堆栈


在调用堆栈向下之后,我们可以看到ntdll!NtCreateFile从用户到内核模式的转换。Ntdll只是为内核提供了一个thunk,因此不感兴趣。

下面是ICAAPI,它是TermDD.sys的用户模式对应物。该调用在IcaChannelOpen的ICAAPI中开始,因此这可能是IcaCreateChannel的用户模式等效项。

由于事实上IcaOpenChannel是用于打开所有通道的通用函数,因此我们将从另一个级别转到rdpwsx!MCSCreateDomain。


rdpwsx!MCSCreateDomain的代码


由于以下几个原因,此功能非常有前景:首先,它使用硬编码名称“MS_T120”调用IcaChannelOpen。其次,它使用返回的通道句柄创建一个IoCompletionPort(完成端口用于异步I / O)。

名为“CompletionPort”的变量是完成端口句柄。通过查看句柄的外部参照,我们可以找到处理端口I / O的函数。


所有对“CompletionPort”的引用


那么,MCSInitialize可能是一个很好的起点。初始化代码始终是一个很好的起点。


MCSInitialize中包含的代码


好的,所以为完成端口创建了一个线程,入口点是IoThreadFunc。我们来看看那里。


完成端口消息处理程序


GetQueuedCompletionStatus用于检索发送到完成端口(即通道)的数据。如果成功接收数据,则将其传递给MCSPortData。

为了证实我的理解,我写了一个基本的RDP客户端,它具有在RDP通道上发送数据的能力。我使用前面解释的方法打开了MS_T120通道。打开后,我在MCSPortData上设置断点; 然后,我将字符串“MalwareTech”发送到频道。


一旦数据被发送到通道,断点就会触发MCSPortData。


这样确认了,我可以读/写MS_T120通道。

现在,让我们看看MCSPortData对通道数据的作用......

MCSPortData缓冲区处理代码

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

嗯,这很有趣。代码看起来像处理通道连接/断开事件的某种处理程序。在查看通常触发此功能的内容后,我意识到MS_T120是一个内部通道,通常不会从外部暴露。

我不认为我们应该在这里......

有点好奇,我发送了触发MCSChannelClose调用所需的数据。当然过早关闭内部渠道不会导致任何问题,是吗?

不好了。我们崩溃了内核!

哎呦!让我们看一下bugcheck,以便更好地了解发生的事情。

似乎当我的客户端断开连接时,系统试图关闭MS_T120通道,我已经关闭它(导致双重释放)。

由于Windows Vista中添加了一些缓解措施,因此通常很难利用双重漏洞。但是,还有更好的东西。

当连接断开时,通道清理代码的内部运行

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

补丁前后的代码差异

本质上,MS_T120通道被绑定两次(一次在内部,然后由我们一次)。由于通道绑定在两个不同的id下,我们得到两个单独的引用。

当使用一个引用来关闭通道时,将删除引用,通道也是如此; 但是,另一个参考仍然存在(称为免费使用后)。使用剩余的引用,现在可以编写不再属于我们的内核内存。

生成海报
请发表您的评论
请关注微信公众号
微信二维码
不容错过
Powered By HeiBaiTeam.