分析CVE-2019-0708(BlueKeep)

2022-04-25 14:20:09 浏览数 (2)

我拒绝了这一说明,直到概念验证(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下,我们得到两个单独的引用。

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

0 人点赞