KERNEL VIRTUALIZATION / NESTED HVM

Nested HVM 拓扑变化

KSword HVM 不引导第二个 Windows。它对已经在跑的那个系统做一次后期接管:VMLAUNCH 之后,同一个执行上下文原地继续,只是从此运行在 VMX non-root,而 KSword 在 VMX root 处理它产生的每一次 VM exit。下面这台演示机逐步走完这个过程。

DEMO

七步拓扑演示

STEP 01 / 07 前置:开着 VBS 的物理机
01 / 07
物理 CPUIntel VT-x / EPT
硬件
Hyper-V(L0)物理层 hypervisor,独占 VMX root
VMX root
根分区宿主 Windows · VBS / HVCI / 内核隔离
non-root
KswordARK 驱动(根分区内)探测不到 CPUID.1:ECX[5],PREPARE 直接失败
被挡住
Hyper-V 子分区KSword-HVM-Target 虚拟机 · 已开启嵌套虚拟化
non-root
KSword HVM(L1)VMX root · 处理来自 L2 的每一次 VM exit
VMX root
来宾 Windows靶机里唯一那个 Windows,直接跑在子分区上
ring 0 / 3

本步发生了什么

权威判据

CR4.VMXE(bit 13)
0
residentProcessorCount
0
VM-exit 累计
—
sc stop 卸载
允许

KSWORD_ARK_HVM_STATE_*

依赖这一层的能力

演示按 KSwordDEV/KSword@main 的 HVM 协议与嵌套架构文档编排。读数取自文档记录的 2 vCPU 嵌套 Hyper-V 靶机实测,不是裸机结论;本页不执行任何真实虚拟化操作。

它不启动第二个 Windows

这是最容易读反的一点。VMLAUNCH 之后没有重启、没有第二个内核、桌面上什么都不会发生——原本那个 Windows 的执行上下文原地往下跑,只是从此处在 VMX non-root 模式。演示里那一行「Windows」自始至终是同一个节点,第 5 步只是往它上面插了一层。

正因为看不见,它也最容易被误判成「没生效」。所以判据不能靠肉眼:RESIDENT_ACTIVE 是状态位不是心跳,权威判据是 residentProcessorCount > 0,更硬的判据是 CR4.VMXE——蓝屏转储里这一位为 0,就意味着崩溃时这一层根本没在跑。

一次性来宾 vs 常驻

驱动里有两种进入 VMX 的方式,差别不是程度而是种类。

维度

一次性来宾(one-shot guest)

常驻(resident)

谁在 non-root 里跑

驱动自己造的一小段测试代码

正在运行的这个 Windows

持续多久

几条指令,立刻 VMXOFF

一直,直到显式停止

能观测什么

只有那段测试代码

全机器的每一次 VM exit

用途

证明 VMX 能进能出

真正的监控与拦截

只有常驻期间,这一层才存在。

停掉常驻之后 CPU 回到 VMX 关闭状态,EPT 不再被硬件遵守,所有基于 EPT 的能力——隐蔽 Hook、分离视图、执行域、R-1 进程处置——全部同时失效。不是降级,是那一层不在了。

这也解释了两件反直觉的事:EPT 规则、分离视图和 R-1 处置只能在常驻停着时安装(常驻期间退出路径不持锁就在读这些表),所以界面上「装一条处置」实际是 停常驻 → 装 → 重启常驻;而 sc stop 在常驻期间返回 1052,因为卸载守卫在拦——映像正在 VMX root 里被执行,卸载它等于把机器打死。

嵌套带来的三条硬约束

嵌套不是「和裸机一样,只是慢一点」。外层决定了内层能看见什么,下面三条都是在靶机上实测撞出来的。

没有 Monitor Trap Flag

嵌套 Hyper-V 不向来宾通告 MTF。默认那套「写叶 + monitor-trap 恢复」的隐蔽 Hook 后端因此根本装不上,只有 EPTP 切换后端能工作——它只要求 execute-only EPT 叶(IA32_VMX_EPT_VPID_CAP bit 0),而那一位在同一台机器上可用。这是选那套后端的唯一理由:能力,不是性能。

跨核 TLB 失效要自己做

转发给 L0 的 flush hypercall 只让 L0 去失效它认为的那个 L1 VP,而那个 VP 正在跑我们的 L2,兄弟核上的陈旧翻译不会被清掉。这个缺陷只在「我们插在 Windows 和 Hyper-V 中间」时存在。修法是私有 VMX-root IDT + 广播 NMI,并且私有 IDT 是发 NMI 的前提而不是优化——第一次只加 NMI 广播就撞了 0x80 NMI_HARDWARE_FAILURE。

必须显式声明允许嵌套

检测到外层已有 hypervisor 而请求里没带 ALLOW_NESTED 时,驱动直接拒绝启动常驻,而不是静默降级——静默降级出来的东西没人能判断它到底在保护什么。常驻成功时会打上 RESIDENT_NESTED,界面明确显示「作为 L1 运行,性能与可用能力均降级」。

跨核失效:开关一次,现象跟着开关一次

判据不用「跑很久没崩」那种统计实验,而是确定性的:VirtualProtect 返回的语义就是「所有处理器都已看到新保护」。主线程把一页改成 PAGE_NOACCESS 并等它返回,之后绑在别的处理器上的线程再去读——读成功就是用了一条本该已被失效的映射。

轮次

常驻核数

窗口内取样

违规

基线

0

10,429,398

0

常驻(修复前)

2

180,208,570

174,787,358(97%)

基线

0

10,644,062

0

常驻(修复后)×3

2

6,434,264 / 6,952,434 / 6,782,150

0 / 0 / 0

除了违规计数归零,取样量级也从 1.8 亿掉回 600–700 万、与基线同量级——读会正常触发访问违例才会这么慢。只看「违规 = 0」可能是探针失灵,量级一起对上才说明真的在走页表。这个缺陷同时解释了此前三种没有归因的崩溃:0x139 (0x1D)、0x0A 与 0x50。

这条修法依赖「我们没有启用 VPID」。

未启用 VPID 时 VM entry 会失效与 VPID 0000H 关联的线性映射,广播 NMI 才能治好跨核失效。一旦为了性能启用 VPID,那次 entry 什么都不刷,修法会静默失效——失效形态就是上表那个 97%,不会有任何报错。所以这两件事必须一起改。

为什么开着 VBS 的物理机走不通

这不是策略问题,是一条硬事实。开着内存完整性或 Credential Guard 任一项就会拉起 Hyper-V;Hyper-V 独占 VMX root,并且对根分区隐藏 CPUID.1:ECX[5]。于是连「这台机器有 VT-x」都探测不到,PREPARE 就失败了——根本走不到任何一处 HYPERVISOR_PRESENT 判断。

关键在于根分区拿不到嵌套 VMX。Hyper-V 确实支持把虚拟化扩展暴露给 guest(Set-VMProcessor -ExposeVirtualizationExtensions),但那是给它的 guest 分区用的,根分区不在其列——而 KswordARK 驱动就跑在根分区。

因此可选项只有两条,都需要重启,都由使用者自己决定:关掉内存完整性并 bcdedit /set hypervisorlaunchtype off 在本机跑(代价是失去 HVCI 与 Credential Guard),或者保留 VBS、在开了 ExposeVirtualizationExtensions 的子分区里跑。产品侧维持 fail-closed,并把原因如实报出:NestedNotAllowed 与 HypervisorConflict 是分开的两态,因为前者用户自己能解决。

不做的是:探测到 VBS 就尝试抢占 VMX root,或者绕过 CPUID 隐藏去猜 VMX 是否真的存在。两者都是拿使用者的机器去赌一个无法验证的假设。

边界

常驻 HVM 是实验性后端,仅适用于已获授权的实验与诊断环境。启动前请保存工作:VMCS、EPT、固件、Hyper-V/VBS、嵌套虚拟化或处理器实现异常都可能导致系统不稳定、蓝屏或必须重启。

隐蔽 Hook、分离视图与 R-1 进程处置不是安全边界:它们失败即放行,目标若能让代码页换一个客户物理页,就不在被拒绝的那一页上了。这是 R0 之外的一条处置通路,不是用来对抗一个知道它存在的对手。上面的读数来自嵌套靶机,路线图中多项裸机验收仍未完成。