.. /blog
2026-03-03 // 5 min

The Registry Write That Would Have Turned On Real Hyper-V

vbsdebuggingclose-call

I was one reboot away from bricking my development machine. Not a hardware brick -- worse. A software configuration that would have launched real Hyper-V on next boot, conflicting with my hypervisor, and potentially locking me out of ring 0 entirely.

This is the story of the dumbest thing I almost did.

The setup

Part of my VBS spoofing involves making Windows think Virtualization-Based Security is enabled. The Windows Security app checks several registry keys to determine VBS/HVCI status. To make the UI show "Core Isolation: On" and "Memory Integrity: On," I needed to set specific values in specific locations.

The keys that matter are under DeviceGuard and CI (Code Integrity). I was writing a function to set them all at once:

// the code I was writing -- DO NOT USE THIS

fn set_vbs_registry_keys() {
    // DeviceGuard policy
    reg_set_dword(
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\DeviceGuard",
        "EnableVirtualizationBasedSecurity",
        1,
    );

    // HVCI enabled
    reg_set_dword(
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\DeviceGuard\\Scenarios\\HypervisorEnforcedCodeIntegrity",
        "Enabled",
        1,
    );

    // CI options
    reg_set_dword(
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\CI",
        "HVCIEnabled",
        1,
    );
}

I had the function written. I had test coverage. I was about to deploy it on my main dev box. Then I stopped and actually thought about what happens on reboot.

The realization

Those registry keys are persistent. They're in CurrentControlSet, which persists across boots. When Windows boots, the kernel reads EnableVirtualizationBasedSecurity = 1 very early -- before my driver loads. It sees that VBS is supposed to be on. So it launches Hyper-V. The real Hyper-V.

And now my test-signed driver can't load at all, because real Hyper-V enables real HVCI, which blocks unsigned and test-signed code from running in the kernel. My hypervisor driver is test-signed. It would be blocked.

The sequence would be:

// what would happen on next reboot

// 1. BIOS hands off to Windows Boot Manager
// 2. bootmgfw reads BCD, sees VBS registry keys
// 3. launches hvloader.dll -> hvix64.exe (real Hyper-V)
// 4. Hyper-V starts Secure Kernel (securekernel.exe)
// 5. Secure Kernel enables REAL HVCI
// 6. ntoskrnl loads with HVCI enforcement ON
// 7. my test-signed driver: BLOCKED
// 8. no way to clear the registry keys from inside Windows
//    because I can't load my driver to undo them
// 9. stuck in a loop: VBS on -> can't load driver -> can't turn VBS off

// recovery: boot from USB, mount offline registry, delete keys
// or: bcdedit from recovery console
// annoying, time-consuming, and completely avoidable
The core problem: I was writing persistent registry keys that configure boot-time behavior. My driver loads after boot. If the boot-time behavior blocks my driver, I've locked myself out. The fix is obvious in hindsight: never write persistent VBS keys. Use volatile keys that disappear on reboot.

The fix: volatile keys

Windows checks VBS status at runtime through a different path than at boot. The boot-time check reads the DeviceGuard keys. The runtime check -- what the Windows Security app uses -- reads from CI\State and queries NtQuerySystemInformation. I only need to fool the runtime check.

Volatile registry keys are created with REG_OPTION_VOLATILE. They exist only in memory. They vanish on reboot. They're perfect for this.

// the safe version -- volatile keys only

fn set_vbs_volatile_keys() {
    // CI\State -- runtime VBS status indicator
    // this is what Windows Security app actually reads
    reg_create_volatile(
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\CI\\State",
    );
    reg_set_volatile_dword(
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\CI\\State",
        "HVCIEnabled",
        1,
    );

    // DeviceGuard volatile state -- for WMI queries
    reg_create_volatile(
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\DeviceGuard\\State",
    );
    reg_set_volatile_dword(
        "HKLM\\SYSTEM\\CurrentControlSet\\Control\\DeviceGuard\\State",
        "SecurityServicesRunning",
        0x02, // bit 1 = HVCI
    );

    // boot DeviceGuard keys: DO NOT TOUCH
    // they stay at their original values (VBS off)
    // Windows boot loader reads them and correctly skips Hyper-V
}

Why this matters

The volatile approach means my VBS spoof has a natural safety valve. If my driver crashes, the machine BSODs, or anything goes wrong -- the volatile keys disappear on reboot. Windows boots normally, no VBS, no HVCI, my driver can load again. The machine is always recoverable.

If I'd deployed the persistent keys and rebooted before catching the mistake, I'd have spent an hour booting from a recovery USB and editing the offline registry hive. Not the end of the world, but exactly the kind of self-inflicted wound that makes you question your life choices.

Lessons

First: always think about what happens on reboot. Hypervisor work happens in the context of a running OS, and it's easy to forget that the OS will boot again someday without your code loaded.

Second: test configuration changes on a VM first. I should have tried this in QEMU before touching my dev machine. I didn't because "it's just a registry write." Famous last words.

Third: volatile keys are underrated. Any time I need to make Windows believe something that's only true while my driver is running, volatile keys are the answer. They're the post-it notes of the registry -- temporary by design.

This is also why Core Isolation's toggle in Windows Settings says "requires restart." It's writing those persistent DeviceGuard keys and relying on the boot loader to act on them. If you could toggle it without rebooting, they'd use volatile keys too.