简介
启用嵌套虚拟化选项的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中。