通过hAFL1 挖掘到Hype-V中影响Azure的9.9分高危漏洞
2021-11-14
来源:嘶吼专业版
0x01 研究概述
在 Hyper-V 的虚拟网络交换机驱动程序 ( vmswitch.sys ) 中发现了一个严重漏洞。
该漏洞是使用我们命名为hAFL1的Fuzzer发现的,我们将其开源出来了(https://github.com/SB-GC-Labs/hAFL1) 。
hAFL1 是 kAFL 的修改版本,可以对 Hyper-V 半虚拟化设备进行模糊测试,并增加了结构感知、详细的崩溃监控和覆盖率指导。
Hyper-V 是Azure (微软的公有云)的底层虚拟化技术。
该漏洞允许远程代码执行(RCE) 和拒绝服务(DoS)。利用它,攻击者可以使用 Azure 虚拟机控制整个公有云平台,并在 Hyper-V 主机上运行任意代码。
该漏洞首次出现在2019 年 8 月的vmswitch版本中,该漏洞可能已经存在一年多。
2021 年 5 月,微软为漏洞CVE-2021-28476分配了9.9 的 CVSS 评分,并为其发布了补丁。
0x02 vmswitch.sys
为什么目标是 Hyper-V?越来越多的公司正在将其工作负载的主要部分迁移到公有云,例如 AWS、GCP 和 Azure。公有云为用户提供了灵活性,让他们无需管理自己的裸机服务器。然而,这些云本质上基于共享基础架构——共享存储、网络和 CPU 能力。这意味着管理程序中的任何漏洞都会产生更广泛的影响;它不仅会影响一台虚拟机,而且可能会影响其中的许多虚拟机。
Hyper-V 是 Azure 的底层虚拟化技术,我们决定以它的虚拟交换机 ( vmswitch.sys ) 为目标,因为它是云功能的核心关键组件。
为什么选择模糊测试?在开发模糊测试工具和静态分析 Hyper-V 的网络驱动程序vmswitch.sys 之间,我们选择了第一个。原因很简单——代码规模。手动审计挖掘漏洞很枯燥乏味,我们希望一个好的 fuzzer 能够找到不止一个Crashs。
在 Hyper-V 术语中,主机操作系统在Root Partition 中运行,客户操作系统在Child Partition 中运行。为了向子分区提供与硬件设备的接口,Hyper-V 广泛使用了半虚拟化设备。通过半虚拟化,VM 和主机都使用修改后的硬件接口,从而带来更好的性能。一种这样半虚拟化设备是网络交换机,这是我们的研究目标。
Hyper-V 中的每个半虚拟化设备都包含两个组件:
1、在子分区中运行的虚拟化服务使用者 ( VSC )。netvsc.sys是网络 VSC。
2、在根分区中运行的虚拟化设备提供程序 ( VSP )。vmswitch.sys是网络 VSP。
这两个组件通过 VMBus(一种基于超级调用的分区内通信协议)相互通信。VMBus 使用两个环形缓冲区——一个发送缓冲区和一个接收缓冲区来在客户和主机之间传输数据。
Hyper-V 中的半虚拟化网络由 netvsc(使用者)和 vmswitch(提供者)组成。
Fuzzing 是一种自动化软件测试技术,涉及提供无效或随机数据作为计算机程序的输入。fuzzer 生成输入,将它们发送到其目标并监控目标上的崩溃或意外行为。模糊测试的核心组件是harness,它负责将输入直接发送到目标。harness与目标紧耦合;它必须通过目标通常使用的通信通道发送输入。为了使模糊测试过程高效,现代Fuzzer实现了几个附加功能。第一个是覆盖指导——能够准确跟踪执行了哪些代码流并相应地改变新输入,目的是增加目标程序中访问的代码量。另一个重要功能是崩溃监控——获取有关模糊测试过程中发生的任何崩溃的详细信息的能力。此类信息可以是堆栈跟踪或触发崩溃的代码行。最后是结构意识; fuzzer 生成符合特定结构(例如网络协议、文件格式等)的输入,而不是发送完全任意的输入。这增加了通过基本验证在早期阶段处理输入而不是丢弃输入的机会。我们使用 fuzzing infrastructure 来指代包含上述组件以执行模糊测试过程的任何软件项目。
0x03 harness
我们的目标是拥有一个能够向vmswitch发送输入的模糊测试基础框架。此外,希望我们的 fuzzer 是可以实现覆盖引导,并提供详细的崩溃报告,准确指出发生崩溃的原因。最后,结构意识对我们来说也很重要,以vmswitch接受的格式发送输入,而不是使用任意输入浪费时间和资源。
开发Fuzzer的第一阶段是设计harness。我们从MSRC 博客文章中汲取灵感,该文章详细介绍了 VPCI(Hyper-V 的半虚拟化 PCI 总线)的模糊测试。由于这个目标与我们的相似,我们开始使用相同的步骤。
https://msrc-blog.microsoft.com/2019/01/28/fuzzing-para-virtualized-devices-in-hyper-v/
Microsoft 帖子中提出的想法很简单——找到 VSC 使用的 VMBus 通道,并使用此通道使用已知的、记录在案的 API 将数据发送到 VSP 。我们的目标是将这些步骤应用于我们的目标:查找netvsc使用的VMBus通道,并使用此通道使用VmbPacketAllocate和VmbPacketSend将数据发送到vmswitch。
1.查找 VMBus 通道
netvsc是在 Hyper-V 子分区的客户操作系统中运行的NDIS驱动程序,并公开虚拟化网络适配器。作为虚拟适配器初始化过程的一部分,netvsc分配了一个名为MiniportAdapterContext 的结构(这是作为函数NvscMicroportInit 的一部分发生的)。MiniportAdapterContext 的偏移量 0x18是我们的 VMBus Channel指针。
作为 netvsc 中初始化过程的一部分,VMBus Channel指针被写入 MiniportAdapterContext 结构。
有了这些新知识,我们编写了一个在子分区上运行的专用驱动程序 ( harness.sys )。它遍历所有 NDIS 微型端口适配器,找到我们想要模糊测试的适配器(通过对其名称进行字符串匹配)并从适配器上下文结构中获取 VMBus Channel指针。有了netvsc使用的 VMBus 通道,驱动程序就会允许我们向vmswitch发送数据。
通过ndis.sys驱动寻找VMBus通道的过程
2.vmswitch
Hyper-V 中的每个 VSP 都必须实现和注册数据包处理回调EvtVmbChannelProcessPacket。每当新数据包到达 VSP 时,都会调用此函数。在vmswitch 中,这个回调函数是VmsVmNicPvtKmclProcessPacket 。
vmswitch需要NVSP类型的数据包,这是一种用于通过 Hyper-V 的 VMBus 传输的数据包的专有格式。有许多 NVSP 数据包类型;有些负责设置VMBus 的发送和接收缓冲区,有些负责执行VSP 和VSC 之间的握手(例如交换NDIS 和NVSP 版本),有些用于在客户和主机之间发送RNDIS 消息。
RNDIS通过抽象控制和数据通道定义了主机和远程 NDIS 设备之间的消息协议。在 Hyper-V 设置中,“host”是客户 VM,“RNDIS 设备”是vmswitch或外部网络适配器,“抽象通信通道”是 VMBus。
我们决定将我们的模糊测试工作集中在处理 RNDIS 消息的代码流上,原因有两个:
1、有很多代码处理 RNDIS 消息。
2、在vmswitch中发现的相当多的漏洞都在 RNDIS 数据包处理中。
处理 RNDIS 消息的函数是VmsVmNicPvtVersion1HandleRndisSendMessage ,它直接会从VmsVmNicPvtKmclProcessPacket 调用。
要使用 RNDIS 消息Fuzzing vmswitch,我们必须调用这些函数并将 RNDIS 消息传递给它。
3.发送 RNDIS 消息
void VmsVmNicPvtKmclProcessPacket
( VMBCHANNEL Channel,
VMBPACKETCOMPLETION Packet,
PVOID Buffer,
UINT32 BufferLength,
UINT32 Flags
)
{…}
VmsVmNicPvtKmclProcessPacket接受五个参数:VMBus Channel 指针、数据包对象、缓冲区及其长度以及flags。buffer 参数用于将数据包元数据发送到vmswitch。它由4个字段组成:
1、msg_type – NVSP 消息类型
2、channel_type – 0 表示数据,1 表示控制
3、send_buf_section_index – 写入数据的发送缓冲区部分的索引。回想一下,VMBus 通过两个环形缓冲区传输数据;此字段指定数据的确切位置
4、send_buf_section_size – 发送缓冲区中数据的大小
数据包处理回调的 Buffer 参数中的不同字段
起初,必须通过 VMBus 向缓冲区发送数据。但是经过一段时间的研究,我们找到了另一种发送 RNDIS 消息的方法,不涉及 VMBus 向缓冲区发送数据。可以分配内存,将数据复制到其中,然后创建指向已分配缓冲区的内存描述符列表(或MDL)。发现这种方式对我们来说更方便,因为它使我们无需将 RNDIS 消息复制到发送缓冲区。
要使用 MDL 发送 RNDIS 消息,上面的缓冲区指定以下值:
●msg_type = NVSP_MSG1_TYPE_SEND_RNDIS_PKT
●channel_type= 1
●send_buf_section_index = -1(表示使用MDL)
●send_buf_section_size = 0(使用 MDL 时忽略此参数)
MDL 本身附加到数据包对象。
此时,不仅能够向vmswitch发送任意输入,而且确切地知道要发送哪些数据包以及如何发送它们,以便执行 RNDIS 代码流。有了这一功能,我们的harness可以触发vmswitch的n day漏洞 :CVE-2019-0717。
0x04 Harness 与 Fuzzer 连接
该过程的下一步是将我们的工具集成到一个模糊测框架中,不需要完全自己实现——编写超级调用、设计突变引擎、解码覆盖跟踪等。有几个选项可用,但我们选择了kAFL 似乎最适合我们的需求——Fuzzing内核模式驱动程序。
我们的Fuzzer有三个级别的虚拟化(用“LN”表示级别 N)。L0 – 裸机服务器 – 将在 Linux 的内置管理程序 KVM 上运行 kAFL。然后将创建我们的 Hyper-V 主机 (L1)——一个运行 Windows 10 且启用了 Hyper-V 的虚拟机。在我们的 Hyper-V 主机之上,将运行两台机器 (L2):root分区,vmswitch将在其中执行,以及一个子分区,我们将从中运行我们的harness和Fuzzing vmswitch。
hAFL1 设置 -:vmswitch 在root分区(L2)内运行,harness在子分区(L2)内运行。
问题是kAFL 不支持嵌套虚拟化,而我们的设置是基于嵌套虚拟化的——我们在 KVM 之上的 Hyper-V 主机之上有一个客户操作系统。通过这样的设置,kAFL 无法直接与在 L2 中运行的组件进行通信。更准确地说,这意味着vmswitch缺乏覆盖信息,并且无法将fuzz有效载荷(输入)从 kAFL 发送到我们的harness。
因此,为了适应 kAFL,我们必须重新设置。如果我们不能从 L2 fuzz,那我们就试试能不能从 L1 fuzz 。实际上,这意味着必须找到一种方法来从 L1 内而不是从root分区内运行vmswitch。然后,我们只需从与vmswitch相同的虚拟化级别运行我们的工具。
幸运的是,我们找到了一个巧妙的解决方法。事实证明,当启用 Hyper-V 功能并禁用 Intel VTx 时,Windows 以回退模式启动,其中 Hyper-V 无法运行,但vmswitch仍会加载到内核内存中!但是,不存在root和子分区,因为 Hyper-V 不运行,所以我们只剩下 L1。这正是我们想要的,现在可以在单个Windows VM 上运行工具并调用我们的目标函数VmsVmNicPvtVersion1HandleRndisSendMessage 。
遇到的下一个问题是缺少 VMBus 通道。完全运行时,vmswitch使用 VMBus 通道与其使用者(netvsc实例)进行通信。但是由于 Hyper-V 处于非活动状态,并且没有正在运行的 VM,因此vmswitch没有这样的 VMBus 通道可供使用。我们需要找到一种方法来为vmswitch提供一个 VMBus 通道,或者让它自己初始化一个。
经过一段时间的逆向,我们在vmswitch 中发现了一个名为VmsVmNicMorph的特殊函数,它完全可以做到这一点——它为vmswitch初始化一个新的 VMBus 通道。然而,简单地调用这个函数会导致蓝屏,因为它试图调用与 VMBus 相关的函数,而 VMBus 并没有运行。我们决定patch所有 VMBus 逻辑。因为 VMBus 是一个独立的通信层,不会干扰正在发送的数据。你可以把它想象成 OSI 网络层模型:VMBus 是传输层,独立于vmswitch,应用层。也就是说,我们可以放弃执行 VMBus 逻辑,而仍然为vmswitch接收适当的 VMBus 通道对象使用。
还有一个问题需要解决。一个名为PatchGuard的 Windows 功能阻止了对签名内核模式代码的更改。所以如果我们想修改vmswitch 中的指令,必须禁用 PatchGuard。为此,我们使用了一个名为EfiGuard的开源工具,它为我们提供了相关功能:它禁用了内核补丁保护和驱动程序强制签名,允许我们在机器上运行我们未签名的harness驱动程序。
https://github.com/Mattiwatti/EfiGuard
我们在构建 hAFL1 过程中的问题和解决方案
当前的设置与我们最初设想的完全不同。vmswitch直接在 Windows 10 主机上运行(而不是在root分区内),我们的harness驱动程序 ( harness.sys ) 运行在同一级别而不是在子分区内。用户模式harness进程通过超级调用从 kAFL 接收fuzz有效载荷,并使用 IOCTL 将它们传递给我们的harness驱动程序。回顾一下——Hyper-V 无法使用,因为 VT-x 被禁用了。但是我们的fuzzer运行了,将模糊测试输入发送到vmswitch并获取覆盖信息以推动fuzzing过程向前发展。
hAFL1 设置 :vmswitch 在 L1 内运行,我们的harness也在L1中运行。
0x05 Fuzzing改进
下面是我们对Fuzzer框架中加入的更多逻辑和功能。
1.覆盖引导
kAFL 利用Intel-PT在整个模糊测试迭代中跟踪指令指针的值,并改变输入以增加它命中的基本块的数量。为了仅从某个进程的上下文harness)中跟踪执行,kAFL 使用CR3 过滤,只有当 CR3 寄存器值与 CR3 过滤器值匹配时,它才会记录执行跟踪。
https://software.intel.com/content/www/us/en/develop/blogs/processor-tracing.htmlhttps://software.intel.com/content/www/us/en/develop/documentation/debug-extensions-windbg-pt-user-guide/top/commands/commands-for-configuration/ip-filter-configuration.html
但是访问基本块的数量太少,即使是单个数据包也应该通过比Fuzzer UI 显示的更多的基本块进行传播。
分析发现,vmswitch以异步、多线程的方式处理数据包。数据包首先经过短暂的同步处理,然后作为工作项推送到队列中,等待由专用系统工作线程处理。显然,该线程与我们的harness具有不同的 CR3 值。这就是为什么 fuzzer 在它源自工作线程时根本不跟踪执行。为了克服这个问题,我们禁用了 CR3 过滤。这不会污染跟踪结果,因为只有我们在vmswitch 中触发了代码。
https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/system-worker-threads
最后,为了监控vmswitch的覆盖率,我们编写了一个 Python脚本将 Intel-PT 数据从 kAFL 格式转换为 IDA 的Lighthouse插件格式。
https://github.com/SB-GC-Labs/hAFL1/blob/main/tools/convert_kAFL_coverage_to_lighthouse.pyhttps://github.com/gaasedelen/lighthouse
vmswitch 覆盖率使用 IDA 的 Lighthouse 插件实现可视化
2.Crashs监控
为了能够有效地监控分析崩溃,Fuzzer有必要生成详细的崩溃报告。但是,kAFL 没有提供很多 Windows 目标中的崩溃信息。例如,它不会输出触发崩溃的目标代码中的堆栈跟踪或确切偏移量,我们需要自己实现这个逻辑。
我们使用了 Xen 代码库的一部分来获取堆栈跟踪和模块信息。然后,编写了两个 KVM 超级调用,将这些信息从 L1 发送回 kAFL。最后,我们实现并注册了一个特殊的 BugCheck 回调来调用这些 KVM 超级调用。
有了这些条件,我们能够获得有关vmswitch 中发生的每次崩溃的详细信息——一个完整的堆栈跟踪,包含函数名称和偏移量,如下截图所示。
来自 hAFL1 的详细崩溃报告,显示了堆栈跟踪、函数名称和其中的偏移量。
3.结构意识
为了更快地进行模糊测试,我们希望Fuzzer生成与目标期望的格式相匹配的输入。在我们的例子中,这些输入是 RNDIS 消息。
我们使用协议缓冲区定义了 RNDIS 消息,并使用libprotobuf-mutator 对它们进行了变异。为了将我们自定义的、基于协议缓冲区的变异策略集成到 kAFL 中,必须创建一个新状态并将其添加到 kAFL 的状态机中,这是一个管道。任何fuzz有效载荷都通过此管道由 kAFL 的内置变异器进行变异。
https://developers.google.com/protocol-buffershttps://github.com/google/libprotobuf-mutatorhttps://github.com/SB-GC-Labs/hAFL1/blob/main/README.md
0x06 漏洞挖掘
在 hAFL1 运行两个小时后,发现了一个关键的 CVSS 9.9 的 RCE 漏洞。
hAFL1 图形用户界面,接口与 kAFL 相同,但可以通过添加新的基于协议缓冲区的变异策略进行扩展。
https://github.com/SB-GC-Labs/hAFL1
该漏洞存在于vmswitch.sys ——Hyper-V 的网络交换机驱动程序中。它是通过从访客虚拟机向 Hyper-V 主机发送特制数据包来触发的,可被利用以实现 DoS 和 RCE。
该漏洞首次出现在 2019 年 8 月的版本中,表明该漏洞已在生产环境中存在了一年半以上。它影响了 Windows 7、8.1 和 10 以及 Windows Server 2008、2012、2016 和 2019。
Hyper-V 是 Azure 的管理程序;因此,Hyper-V 中的漏洞会导致 Azure 中的漏洞,并可能影响公有云的整个区域。从 Azure VM 触发拒绝服务将使 Azure 基础架构的主要部分崩溃,并关闭共享同一主机的所有虚拟机。
通过更复杂的利用链,该漏洞可以授予攻击者远程代码执行能力,通过控制主机和在其上运行的所有虚拟机,攻击者可以访问存储在这些机器上的个人信息,运行恶意软件等。
0x07 背景知识
1.vmswitch
在 Hyper-V 术语中,主机操作系统在“root分区”中运行,而客户操作系统在“子分区”中运行。为了向子分区提供与硬件设备的接口,Hyper-V 广泛使用了半虚拟化设备。通过半虚拟化,VM 知道它是虚拟的;VM 和主机都使用修改后的硬件接口,从而带来更好的性能。一种这样的半虚拟化设备是网络交换机,这是我们的研究目标。
每个半虚拟化设备由两个组件组成:
1、在子分区中运行的虚拟化服务使用者 (VSC)。netvsc.sys是网络 VSC。
2、在根分区中运行的虚拟化设备提供程序 (VSP)。vmswitch.sys是网络 VSP。
这两个组件通过 VMBus(一种基于超级调用的分区内通信协议)相互通信。
VSC 和 VSP 分别运行在根分区(Hyper-V 主机)和客户分区(客户 VM)上。
2.通讯协议
netvsc(网络使用者)使用NVSP类型的数据包通过 VMBus与vmswitch(提供者)通信。这些数据包有多种用途:初始化和建立两个组件之间的 VMBus 通道、配置各种通信参数以及将数据发送到 Hyper-V 主机或其他 VM。NVSP 包括许多不同的数据包类型;其中之一是用于发送 RNDIS 数据包的NVSP_MSG1_TYPE_SEND_RNDIS_PKT 。
3.RNDIS 和 OID
RNDIS通过抽象控制和数据通道定义了主机和远程 NDIS 设备之间的消息协议。在 Hyper-V 设置中,“主机”是客户 VM,“远程 NDIS 设备”是 vmswitch 或外部网络适配器,“抽象通信通道”是 VMBus。
RNDIS 也有各种消息类型——init、set、query、reset、halt等。当 VM 希望设置或查询其网络适配器的某些参数时,它会向vmswitch发送OID 请求——带有相关对象的消息标识符(OID) 及其参数。此类 OID 的两个示例是用于设置适配器 MAC 地址的OID_GEN_MAC_ADDRESS和用于设置适配器当前多播地址列表的OID_802_3_MULTICAST_LIST 。
RNDIS 设置消息结构,来自 RNDIS 规范。OID 是数据包的必填字段之一。
4.虚拟交换扩展
vmswitch,Hyper-V 的虚拟交换机,也被称为“Hyper-V 可扩展交换机”。它的扩展是 NDIS 过滤器驱动程序或 Windows 过滤平台 (WFP) 驱动程序,它们在交换机内部运行,可以捕获、过滤或转发处理的数据包。Hyper-V 可扩展交换机具有 OID 请求的控制路径,如下图所示:
Hyper-V 可扩展交换机扩展作为交换机控制路径的一部分
0x08 漏洞分析
1.臭名昭著的 OID
一些 OID 请求发往外部网络适配器,或连接到vmswitch 的其他网络适配器。此类 OID 请求包括例如硬件卸载、互联网协议安全 (IPsec) 和单root I/O 虚拟化 (SR-IOV) 请求。
当这些请求到达vmswitch接口时,它们被封装并使用OID_SWITCH_NIC_REQUEST类型的特殊 OID 沿可扩展交换机控制路径向下转发。新的 OID 请求形成为NDIS_SWITCH_NIC_OID_REQUEST结构,其成员OidRequest指向原始 OID 请求。生成的消息通过vmswitch控制路径,直到到达其目标驱动程序。流程如下图所示。
Hyper-V 可扩展交换机控制路径中的 OID 请求封装。
Microsoft 记录的 NDIS_SWITCH_NIC_OID_REQUEST 结构
2.漏洞代码
在处理 OID 请求时,vmswitch 会跟踪其内容以进行日志记录和调试;这也适用于OID_SWITCH_NIC_REQUEST 。但是,由于其封装结构,vmswitch需要对此请求进行特殊处理,并取消引用 OidRequest以跟踪内部请求。该缺陷是,vmswitch从未验证的值OidRequest,并因此取消引用无效指针。
以下步骤导致 vmswitch 中的漏洞函数:
1、消息首先由 RndisDevHostControlMessageWorkerRoutine 处理——一个通用的 RNDIS 消息处理函数。
2、vmswitch识别设置请求并将消息传递给更具体的处理程序 - RndisDevHostHandleSetMessage。
3、稍后,消息被传递到 VmsIfrInfoParamsNdisOidRequestBuffer。该函数负责使用IFR (跟踪记录器)跟踪消息参数,这是一种 Windows 跟踪功能,可以实时记录二进制消息。
4、最后,数据包到达 VmsIfrInfoParams_OID_SWITCH_NIC_REQUEST,它专门跟踪 OID_SWITCH_NIC_REQUEST 类型的请求及其各自的结构 NDIS_SWITCH_NIC_OID_REQUEST。
导致bug的函数调用链,处理特定 OID 的 RNDIS 请求消息的跟踪。
3.实现利用
netvsc,网络虚拟服务消费者 (vsc) 。不发送带有OID_SWITCH_NIC_REQUEST 的OID 请求。尽管如此,设计缺陷会导致vmswitch接受并处理此类请求,即使它来自客户 VM。这允许我们通过直接从客户 VM发送带有OID_SWITCH_NIC_REQUEST 的 RNDIS设置消息来触发跟踪机制中的任意指针取消引用漏洞。
这种漏洞可以作为两种利用场景的基础。如果OidRequest成员包含无效指针,Hyper-V 主机将直接崩溃。另一种选择是使主机的内核从内存映射设备寄存器中读取,进一步实现代码执行。Hyper-V 主机上的 RCE 将使攻击者能够随心所欲读取敏感信息、以高权限运行恶意负载等。
0x09 研究总结
该漏洞是由于虚拟机管理程序任意指针取消引用与设计缺陷造成的,该缺陷允许客户和主机之间的通信通道过于宽松。
CVE-2021-28476 等漏洞证明了共享资源模型(例如公有云)带来的风险。事实上,在共享基础设施的情况下,即使是简单的错误也可能导致毁灭性的结果,如拒绝服务和远程代码执行。
软件中的漏洞是不可避免的,这句话也适用于公有云基础设施。这加强了混合云战略的重要性,该战略不会将所有鸡蛋放在一个篮子里或一个区域中的所有实例上。这种方法将有助于迅速从 DoS 攻击中恢复,适当的分段将防止在某些机器被攻破后集群被控制。