基本介绍
本文描述了我在Fuzzing Windows RDP 客户端和服务端方面所做的一些尝试和挑战,以及Crsahs分析。
Microsoft 的远程桌面协议 (RDP) 持续受到安全社区的关注。从 2019 年发现的几个可能危及数百万面向互联网的服务端的关键漏洞,到RDP 被攻击者用作主要的初始访问向量之一。
我对这个目标最初的兴趣是与 VM 相关的。因为连接到 Azure Windows 机器或 Hyper-V 虚拟机的默认方式是 RDP,所以我认为 RDP 是比较重要的目标。
我很快就发现了 Park、Jang、Kim 和 Lee在 BlackHat Europe 2019 上发表的关于 RDP Fuzzing的精彩分享。演讲者在短短几个小时内使用一个并不高效的 fuzzer 发现了几个漏洞,因此我决定在他们的工作基础上进行构建新的挖掘工具,将 fuzzing 能力扩展到其他方面,改进其性能并挖掘到我自己的 RDP 远程代码执行 (RCE)漏洞。
不幸的是,并不是所有的Fuzzing工具都能挖到严重的漏洞。我目前还没有挖到 RDP 的RCE漏洞,但我确实设法找到了一些bugs,并更好地了解了协议、其组件以及Fuzzing过程和工具。我开发的Fuzzing框架足够通用,有助于对其他目标进行Fuzzing。
在这篇文章中,我将分享我执行上述所有操作的过程。首先,我将概述 RDP 和开发的Fuzzing设置,然后将分享我临的挑战以及如何处理的方法,最后,我将回顾在此过程中发现的几个漏洞。
1.RDP 协议
远程桌面协议是用于远程访问 Windows 计算机的通用协议,Malwaretech 最近将其述为“协议的协议”。RDP 允许在每个连接中运行多个通道,并且每个通道都有不同的用途。这意味着每个通道都有自己的代码来处理其数据、自己的结构定义和数据流。这实质上意味着 RDP 中确实存在多种协议。
RDP 连接中的通道
RDP 通道可以是静态的,也可以是动态的,但对于我们的目的而言,两者之间的区别并不重要。如果你想了解更多关于RDP连接的内部运作原理,我建议你阅读下文。
2.RDP Fuzz的挑战
在“标准”Fuzzing场景中,有一个程序读取由Fuzzer控制的输入,输入可以是文件或任何类型的数据流。然后程序处理数据,同时Fuzzer监视生成的数据的代码覆盖率。基于该覆盖范围,Fuzzer对输入进行变异,再次将变异的输入发送到程序,然后重复该过程。
RDP Fuzzing是不同的,因为我必须始终有一个 RDP 连接处于活动状态。Fuzzer可以输入到程序中的数据需要作为协议数据单元 (PDU) 发送到特定通道(在Fuzzing期间也应处于活动状态)的顶部,并在开放连接中进行。如前所述,每个通道都有自己的协议,因此需要逐个通道进行Fuzzing。这给Fuzzing过程带来了以下挑战(这也可能适用于其他协议/网络相关的Fuzzing):
客户端-服务端架构——在传统的Fuzzing中,Fuzzer可以简单地运行目标应用程序并提供其输入。在客户端-服务端场景中,目标应用程序在连接的一侧运行,而输入从另一侧发送。在RDP的情况下,双方通常在不同的机器上。
状态性- RDP 是一种有状态协议,这意味着你必须在对测试用例进行Fuzzing时考虑连接的状态。这会严重影响Fuzzing的稳定性。
多输入Fuzzing——当对接受文件作为输入的目标进行Fuzzing(文件格式Fuzzing)时,Fuzzer对目标的所有输入都包含在单个文件中。相反,当你对协议进行Fuzzing时,你可能需要发送一些连续的消息才能到达有效的代码路径。
寻找目标代码——当你使用覆盖率引导的Fuzzing时,你通常需要向Fuzzer表明它需要在什么时候开始监控代码覆盖率(即,处理你输入的目标函数是什么?)。RDP 有许多负责其操作的组件,在某些情况下,找到正确的位置可能是一项艰巨的任务。
这四个挑战是我预计Fuzzing RDP的主要挑战。在文中,我将讨论如何克服这些挑战,以及在工作后期出现的其他挑战。
Fuzzing技术细节
在本节中,我将介绍技术细节,以及在工作过程中遇到的挑战。还将解释如何解决它们以实现有效的Fuzzing设置。我在 GitHub 上创建了一个工作存储库,其中包含我在此工作中编写的所有代码。
1.客户端-服务端架构
在这个工作中,我想要Fuzzing Windows 的 RDP 服务端及其 RDP 客户端。对 RDP 服务端进行Fuzzing的动机很明显:攻击者可以使用它来远程入侵 Windows 服务端并获得对其的访问权限。对 RDP 客户端进行Fuzzing的动机是不同的,如果攻击者已经获取了 RDP 服务端权限的场景,然后等待受害 RDP 客户端连接过来。一旦受害者连接上,攻击者也可以通过 RDP 客户端获取受害者的机器权限。当管理员连接到他们管理的服务端时可能会发生这种情况,由于 Hyper-V 利用 RDP 访问其虚拟机,甚至可以用作 VM 逃逸。
RDP 的 fuzzer 需要具有以下基本组件:
◼检测引擎跟踪代码覆盖并检测崩溃
◼变异引擎产生新输入
◼输入馈送器通过适当的目标通道发送fuzzer的测试用例
◼目标二进制文件状态由检测引擎跟踪
客户端和服务端的Fuzzing配置不同,但也有一些相似之处。
基本Fuzzing配置
在目标方面,有以下内容:
◼Fuzzer – 定制的 afl-fuzz.exe
◼检测引擎——定制的 winafl.dll,使用定制的 DynamoRIO 和in_app检测模式
◼输入 ——被写入目标和输入发送者共享目录中的中间文件的变异 PDU
在输入发送方,有一个组件:
◼代理——读取中间文件并在目标通道上发送输入
将输入的生成与向目标的传输分离,允许输入在被目标处理之前从 RDP 连接的一侧移动到另一侧。此外,我使用 WinAFL 的应用内检测模式来不中断正常的执行流程。
为了使覆盖引导的Fuzzing工作,输入和它们触发的代码路径之间必须存在一一对应的关系。为了实现这一点,我开发了“background fuzzing”,它将Fuzzer PDU 与常规 PDU 区分开来,并且只跟踪前者的代码路径。这是必不可少的,因为我只希望Fuzzer跟踪我自己测试用例的覆盖范围,而不是通过连接发送的随机 PDU。
为了说明这一点,让我看看在Fuzzing将音频从 RDP 服务端重定向到客户端的RDPSND虚拟通道时,类似的事情会是什么样子。根据官方文档,每个 PDU 的第一个字节表示发送的消息类型。
资料来源:[ MS-RDPEA ]
msgType字段支持的值为0x01到0x0D。在这种情况下,可以通过以下方式使用第一个字节的最高有效位作为fuzz标记:
◼在发送 PDU 之前,代理会转换第一个字节的最高有效位。
◼WinAFL 在处理消息之前检查第一个字节的最高有效位。如果该位打开,WinAFL 将关闭该位并跟踪此消息的代码覆盖率。如果该位关闭,WinAFL 将忽略该消息并且不跟踪任何覆盖范围。
在了解了客户端设置和服务端设置之间的相似之处之后,让我看看它们之间的区别,先从客户端开始。
2.客户端Fuzz配置
Windows RDP 客户端是mstsc.exe,但处理虚拟通道数据的大部分逻辑都在客户端加载的mstscax.dll中。
Hyper-V 虚拟机的远程访问客户端vmconnect.exe也使用mstscax.dll作为其核心功能。
Windows RDP 客户端
为了简单和高效,我在同一台机器上同时执行客户端和服务端(目标和代理)(使用客户端连接到 localhost/127.0.0)。为了允许并行Fuzzing,我还使用了mimikatz来修补服务端,使其允许并发 RDP 连接。
这些是对客户端进行Fuzzing时的设置组件:
◼目标 - mstsc.exe和mstscax.dll的目标模块
◼检测引擎 – 创建客户端进程的 DynamoRIO 和 winafl.dll,报告代码覆盖率的 DynamoRIO 客户端
◼变异引擎——在同一台机器上运行的 AFL-Fuzz 并将新的测试用例写入文件
◼输入馈送器——我的 RDPFuzzAgent,它打开服务端的句柄并在选定的虚拟通道上发送 PDU。代理从 AFL-Fuzz 创建的文件中获取每个测试用例,并将它们发送到目标通道上。
使用这些组件,我能够实现大约每秒 50-100 次执行的执行速度。
3.服务端Fuzz配置
为了找到包含 RDP 服务端主要逻辑的目标二进制文件,可以简单地查看远程桌面服务服务。