Hyper-V DoS 漏洞分析(CVE-2020-0890 )

 

简介

启用嵌套虚拟化选项的Guest操作系统会触发漏洞。已在Windows Server 2019 2020年8月更新,Windows 10 20H1 2020年8月更新和Windows 10 21H1 预览版上进行测试。Windows Server 2019的原始版本中也存在漏洞,没有补丁(2018年11月)。

Github上的PoC来源:https://github.com/gerhart01/hyperv_local_dos_poc

 

漏洞分析

在Windows Server 2016和Windows 10(2016年)中引入了Hyper-V嵌套虚拟化技术:https://docs.microsoft.com/en-us/virtualization/hyper-v-on- windows/userguide/nested -virtualization。它可用于在Guest操作系统(或某些功能,例如Windows Sandbox,Hyper-V VM内的MDAG)中启动管理程序。

从技术上讲,Hyper-V编码器的漏洞非常简单:它们没有过滤VP Assist Page的参数,该地址被写入Virtual VP Assist MSR(0x40000073)。早期msr寄存器0x40000073的名称为HV_X64_MSR_APIC_ASSIST_PAGE,现在(在TLFS 6.0中)为HV_X64_MSR_VP_ASSIST_PAGE。

HV_X64_MSR_VP_ASSIST_PAGE MSR 结构:

typedef union _VIRTUAL_VP_ASSIST_PAGE_PFN
{
    UINT64 AsUINT64;
    struct
    {
        UINT64 Enable : 1;
        UINT64 Reserved : 11;
        UINT64 PFN : 52;
    };
} VIRTUAL_VP_ASSIST_PAGE_PFN, * PVIRTUAL_VP_ASSIST_PAGE_PFN;

根据Hyper-V TLFS 6.0 VP Assist Page是重叠页。该页面的GPA地址被写入PFN字段:

typedef union _HV_VP_ASSIST_PAGE
{
    struct
    {
        //
        // APIC assist for optimized EOI processing.
        //

        HV_VIRTUAL_APIC_ASSIST ApicAssist;
        UINT32 ReservedZ0;

        //
        // VP-VTL control information
        //

        HV_VP_VTL_CONTROL VtlControl;
        HV_NESTED_ENLIGHTENMENTS_CONTROL NestedEnlightenmentsControl;
        BOOLEAN EnlightenVmEntry;
        UINT8 ReservedZ1[7];
        HV_GPA CurrentNestedVmcs;
        BOOLEAN SyntheticTimeUnhaltedTimerExpired;
        UINT8 ReservedZ2[7];

        //
        // VirtualizationFaultInformation must be 16 byte aligned.
        //

        HV_VIRTUALIZATION_FAULT_INFORMATION VirtualizationFaultInformation;
    };
    UINT8 ReservedZBytePadding[HV_PAGE_SIZE];
} HV_VP_ASSIST_PAGE, * PHV_VP_ASSIST_PAGE;

如果将zeroed page的PFN写入HV_X64_MSR_VP_ASSIST_PAGE msr,则会得到BSOD。

即使禁用了自动重启选项,Windows 10也会立即重启。如果将调试器连接到hvix64.exe (Windows Server 2019, 08.2020 updates,hvix64.exe,build10.0.17763.1397),则会得到:

hv+0x28af50:
fffff982`efc8af50 cc              int     3
1: kd> g
Access violation - code c0000005 (!!! second chance !!!)
hv+0x27747e:
fffff982`efc7747e 384249          cmp     byte ptr [rdx+49h],al

2: kd> k
 # Child-SP          RetAddr               Call Site
00 00000100`00803d08 fffff982`efc75e1b     hv+0x27747e
01 00000100`00803d10 fffff982`efcfd74f     hv+0x275e1b
02 00000100`00803d60 fffff982`efc82729     hv+0x2fd74f
03 00000100`00803d90 fffff982`efc1691f     hv+0x282729
04 00000100`00803df0 fffff982`efc1816b     hv+0x21691f
05 00000100`00803e80 fffff982`efc8c571     hv+0x21816b
06 00000100`00803fc0 00000000`00000000     hv+0x28c571

2: kd> r
rax=ffffe802c560d000 rbx=ffffe802c5607050 rcx=ffffe802c5608d00
rdx=0000000000000000 rsi=0000000000000000 rdi=ffffe802c5608000
rip=fffff982efc7747e rsp=0000010000803d08 rbp=0000000000000014
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000000014 r12=0000000000000000 r13=ffffe802c56078d0
r14=ffffe802c5608d00 r15=ffffe802c5607630
iopl=0         nv up di pl zr na po nc
cs=0010  ss=0020  ds=0020  es=0020  fs=0020  gs=0020             efl=00010046
hv+0x27747e:
fffff982`efc7747e 384249          cmp     byte ptr [rdx+49h],al ds:0020:00000000`00000049=??

Windows Server 2019生成crash dunp:

Exploit源码仅适用于Intel CPU(技术上,exploit必须工作在AMD平台上,但没有这样CPU的PC),简述:

  • 1.在guest操作系统中激活VMX功能(在操作系统中必须执行以下命令支持:Set-VMProcessor -VMName -ExposeVirtualizationExtensions $true);
  • 2.分配和激活VMXON区域;
  • 3.分配VP Assist Page;
  • 4.获取VP Assist Page物理地址,写入HV_X64_MSR_VP_ASSIST_PAGE msr;
  • 5.执行vmclear,然后vmlaunch,并获取BSOD。

当执行 cmp byte ptr [rdx+49h],al指令时,rdx包含0,我们得到的访问指针为零。它是简单的NULL指针解引用,但rdx不受来宾OS地址空间的控制。

hvix64.exe没有符号,因此过程具有与BSOD相关的名称,其level调用索引,最接近的level为1。该代码块已准备好在hypervisor上下文中执行vmlaunch 指令所需的所有代码。

这个block什么时候执行?调用者block L2并不是很感兴趣。

但是下一个调用者level很重要。

r8b 是如何控制的?

当VP Assist Page归零时:

WINDBG>dps poi(@rsi+198)+40
ffffe802`c5608040  ffffe802`c561c000 – address of overlay VP Assist Page. Don’t changed after host OS reboot.
ffffe802`c5608048  00000000`000f000f
ffffe802`c5608050  00000000`00000000
ffffe802`c5608058  00000000`00000000
ffffe802`c5608060  00000000`00000000

WINDBG>dps ffffe802`c561c000
ffffe802`c561c000  00000000`00000000
ffffe802`c561c008  00000000`00000000
ffffe802`c561c010  00000000`00000000
ffffe802`c561c018  00000000`00000000
ffffe802`c561c020  00000000`00000000
ffffe802`c561c028  00000000`00000000 – rcx+28h
ffffe802`c561c030  00000000`00000000

如果rcx + 28!= 0,则r8b = 1。

我们可以在来宾操作系统中逐步调试PoC驱动程序,并查看变量的物理和虚拟地址,这些地址被传递到hypervisor:

WINDBG> dps ffffe802`c561c000
ffffe802`c561c000 00000000`00000000
ffffe802`c561c008 00000000`00000000
ffffe802`c561c010 00000000`00000000
ffffe802`c561c018 00000000`00000000
ffffe802`c561c020 00000000`00000000
ffffe802`c561c028   00000000`00000001 - pHvVpPage-> EnlightenVmEntry
ffffe802`c561c030   00000000`7ff23000 -pHvVpPage-> CurrentNestedVmcs 
ffffe802`c561c038 00000000`00000000
ffffe802`c561c040 00000000`00000000
ffffe802`c561c048 00000000`00000000
ffffe802`c561c050 00000000`00000000
ffffe802`c561c058 00000000`00000000

下一步操作非常简单,pHvVpPage-> enlightenment vmentry == 0,我们得到BSOD。当执行vmlaunch时,Hypervisor根本不验证VP-Assist Page的内容。

重叠页面初始化问题

BSOD不是一个问题。第二个问题,即使VP Assist Page在写入HV_X64_MSR_VP_ASSIST_PAGE msr之前也填充了实际值,因此参数不会传递给hypervisor。为什么它发生了什么?这是hypervisor重叠page的特性(或bug)。

根据Hyper-V TLFS 6.0的5.2.1小节:

hypervisor定义了几个特殊页面,这些页面“重叠”了guest的GPA空间。hypercall代码页是重叠page(页面)的一个示例。重叠由guest物理地址进行寻址,但不包括在hypervisor内部维护的普通GPA映射中。从概念上讲,它们存在于一个单独的map中,该map覆盖了GPA的map。

如果GPA空间内的一个page(页面)被覆盖,映射到GPA page页面的任何SPA 页面都会被有效地“obscured(掩盖)”,并且通常虚拟处理器无法通过处理器内存来访问它们。此外,访问重叠页面时,将不遵守在底层GPA页面上安装的访问权限。

我们来做实验,在将缓冲区地址写入HV_X64_MSR_VP_ASSIST_PAGE msr之前,需要对其进行分配,用数字0x11填充缓冲区。

FillBuffer((PCHAR)pHvVpPage, PAGE_SIZE, 0x11);
__writemsr(HV_X64_MSR_APIC_ASSIST_PAGE, guestPFN.AsUINT64);

并在LiveCloudKd中查看它的内容,我们从WinDBG知道物理和虚拟地址,在源代码调试模式下启动:

附加到guest操作系统内核调试器:

同时将LiveCloudKd连接到相同的VM。首先,我们可以看到enlightenment结构。CurrentNestedVmcs和EnlightenVmEntry中的某些值:

并在VP Assist Page中看到相同的值:

写入HV_X64_MSR_VP_ASSIST_PAGE msr之后,我们可以看到hypervisor内部有一些历史垃圾(重叠页的地址是不变的,正如我们前面看到的那样,驱动程序重新启动,guest操作系统没有重新启动)。

WINDBG>dps ffffe802`c561c000 – inside hypervisor
ffffe802`c561c000  00000000`00000000
ffffe802`c561c008  00000000`00000000
ffffe802`c561c010  00000000`00000000
ffffe802`c561c018  00000000`00000000
ffffe802`c561c020  00000000`00000000
ffffe802`c561c028  00000000`00000001
ffffe802`c561c030  00000000`7ff23000
ffffe802`c561c038  00000000`00000000
ffffe802`c561c040  00000000`00000000
ffffe802`c561c048  00000000`00000000
ffffe802`c561c050  00000000`00000000
ffffe802`c561c058  00000000`00000000

返回到guest操作系统调试器。重叠页的0x11值被替换为上次HV_X64_MSR_VP_ASSIST_PAGE msr写入的值:

如果我们想写一些东西在重叠页后,

如果我们想在其地址写入HV_X64_MSR_VP_ASSIST_PAGE msr 之后,在重叠页面中写入某些内容,则所有内容都是正确的。

LiveCloudKd显示旧值,因为它会解析guest内存中的原始值,guest内存是使用MDL和hostPFN-to-GuestPFN页面映射在主机系统中进行映射的。当guest操作系统尝试从其分区读取/写入内存时,重叠页存储在hypervisor中,并被完全替换。有趣的是,guest页面中的page属性未更改。

我检查了一下:hypervisor中的重叠页ffffe802`c561c00不断地改变CR3,但它的物理地址没有改变。可能还需要进一步的分析(但这不是与bug相关的研究)。

 

总结

发现了2个Bug:

  • 在vmlaunch 模拟期间对VP Assist Page值的处理不正确,会导致空指针引用错误和下一个BSOD。
  • 重叠page初始化的处理不正确,它只需切换到另一个内存缓冲区,即使没有清除保存在hypervisor中的旧值,或者从guest页面复制值,该地址在HV_X64_MSR_VP_ASSIST_page MSR中。
(完)