WinPE as a stateless harness for Windows driver testing and fuzzing

#winpe#windows#internals#ci-cd#kernel#nt#qemu

Recently, I analyzed two specific engineering problems in the field of low-level automation. The first one is the orchestration of CI/CD environments for Windows systems, where creating reproducible, deterministic conditions for end-to-end (E2E) tests is incredibly expensive and inefficient. Suppose you manage a repository for a Kernel-Mode Driver Framework (KMDF) kernel driver: how do you deploy fully automated E2E tests?

The classic approach based on full Windows runners is a massive resource burden. The environment is non-deterministic, lacks idempotency, and the boot time from scratch (the so-called cold start) drastically extends the feedback loop in the pipeline.

The second problem is a twin issue but concerns a different stage of the software lifecycle: dynamic fuzzing of a KMDF driver and immediate capture of kernel exceptions (bugcheck / BSOD). Here, the barriers are identical: memory overhead, slow restoration of machine state from a snapshot, and a lack of operating system predictability.

Both of these problems stem from the same root cause. A standard Windows installation carries significant resource overhead from graphical components, background services, and telemetry. For automated testing, this user-mode layer is unnecessary; we only need a minimalist environment running the NT kernel.

The solution is Windows PE (Windows Preinstallation Environment). It is an official, stripped-down environment distributed with every Windows ISO image. It runs entirely in RAM, requires as little as 512 MB of memory, and lacks support for DirectX, the PowerShell subsystem, or the standard graphical shell (Explorer). Booting by default with NT AUTHORITY\\SYSTEM privileges makes it an ideal test harness for both of these tasks.

The following analysis focuses on the low-level mechanisms of WinPE, as well as BCD and QEMU modifications that allow transforming this system into an ultra-fast, idempotent testing environment.

Reconnaissance and boot configuration automation

The WinPE environment boots from a WIM (Windows Image) file, which the bootloader mounts as a RAM disk under the virtual drive letter X:. The entire process is controlled by the BCD (Boot Configuration Data) store. To make the virtual machine spin up in a matter of milliseconds, aggressive boot optimizations must be deployed using the bcdedit tool:

bcdedit /set {default} bootstatuspolicy ignoreallfailures
bcdedit /set {default} recoveryenabled no
bcdedit /set {bootmgr} timeout 0
  • ignoreallfailures – a critical flag for CI/CD systems. It ignores improper shutdown errors, preventing a blocking welcome screen from appearing.
  • recoveryenabled no – completely cuts off automatic system repair attempts, which would end up in a crash loop anyway in a diskless environment.

When testing digitally unsigned KMDF drivers, enabling test mode is the primary requirement. While many legacy guides suggest nointegritychecks yes, this flag has been ignored by the x64 bootloader since Windows Vista/7. Instead, the main lever to disable Virtualization-Based Security (VBS) and Hypervisor-Protected Code Integrity (HVCI) is hypervisorlaunchtype off. To disable these kernel-level protections:

bcdedit /set {default} testsigning yes
bcdedit /set {default} hypervisorlaunchtype off
bcdedit /set {default} isolatedcontext no

Here, testsigning yes permits loading unsigned drivers, hypervisorlaunchtype off disables VBS/HVCI at the hypervisor level, and isolatedcontext no disables isolated user-mode contexts (like Credential Guard and Isolated User Mode). Together, they prevent the hypervisor from blocking unverified code and allow direct manipulation of kernel structures during fuzzing.

Hardware topology architecture in QEMU

Emulating the environment for kernel debugging stability requires a precise choice of chipset. When virtualizing with QEMU, the default, modern q35 profile implements the PCIe (PCI Express) bus along with its full, complex topology (Root Complex, root ports).

For the simplified, built-in network drivers present in WinPE, navigating the PCIe tree can be problematic. The older pc (i440FX) machine profile, which provides a flat, classic PCI bus (bus 0), is significantly more predictable. Device addressing here is trivial, eliminating resource allocation errors at the HAL (Hardware Abstraction Layer) level.

# Recommended, stable base QEMU configuration for Windows (WHPX)
# with a specific CPU model selected instead of 'host' (WHPX has issues with 'host')
# and Hyper-V enlightenments removed by default, so they do not break KDNET:
qemu-system-x86_64.exe -M pc -accel whpx -cpu Skylake-Client-IBRS -m 1024 -vga none -nographic ...

Network debugging via KDNET

The KDNET mechanism enables kernel debugging over the UDP protocol. It operates completely independently of the system network stack (NDIS), working directly at the hardware controller level.

In a minimalist environment, KDNET does not universally query the bus to scan for network cards. You must force hardware mapping using the busparams parameter, providing the exact coordinates on the PCI bus in the Bus.Device.Function format:

bcdedit /dbgsettings net hostip:10.0.2.2 port:50000 key:1.2.3.4
bcdedit /set {dbgsettings} busparams 0.16.0

If you configure the network card in QEMU with the command -device e1000,bus=pci.0,addr=0x10, the address 0x10 (hexadecimal) corresponds to the value 16 in decimal. Hence the busparams=0.16.0 parameter.

Important: KDNET has a hardcoded, highly limited list of hardware identifiers (Vendor ID / Device ID) for natively supported adapters. Classic Intel PRO/1000 emulation (-device e1000) initializes automatically. Attempting to switch to the newer e1000e or paravirtualized interfaces (virtio-net) without injecting the appropriate boot modules into boot.wim will fail.

Absolute determinism and shell modification

WinPE guarantees absolute idempotency: the system boots from a clean WIM image loaded into memory, and any modifications represent an ephemeral state. Permanent architectural changes require offline editing using the DISM tool:

dism /Mount-Image /ImageFile:C:\winpe\boot.wim /Index:1 /MountDir:C:\winpe\mount
# [File / registry modification]
dism /Unmount-Image /MountDir:C:\winpe\mount /Commit

Keep in mind that the BCD store and the system image itself are two separate entities on the media (e.g., an ISO file). The BCD is located in the \\boot\\bcd folder (modified offline using bcdedit /store), whereas the file system is modified by mounting the \\sources\\boot.wim file using the DISM tool.

To maximize initialization speed and eliminate boot time overhead, we must bypass or customize the standard wpeinit.exe process, which parses startup scripts and configures DHCP queries. Disabling NDIS network initialization is accomplished by injecting a proper unattend.xml profile processed during the windowsPE pass:

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="windowsPE">
        <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
            <EnableNetwork>false</EnableNetwork>
        </component>
    </settings>
</unattend>

This will cause the operating system to skip IP negotiation, reducing boot time to a minimum while maintaining KDNET functionality (since, as established, it operates below the NDIS stack).

The most elegant approach to eliminating unnecessary layers of abstraction is a complete replacement of the system shell. By creating a shell configuration file X:\\Windows\\System32\\winpeshl.ini, we instruct the session manager to ignore the cmd.exe interpreter and directly run our binary test agent:

[LaunchApps]
%SYSTEMROOT%\System32\test_agent.exe, "--param1"

Crucially for automation purposes: the architecture of the WinPE logon subsystem is designed so that terminating the process defined as the main shell in winpeshl.ini automatically triggers an immediate reboot of the entire virtual machine. This creates a perfect loop for fuzzers and CI/CD runners. If we add the -no-reboot flag to the QEMU command, when WinPE signals a reboot (after the agent finishes its work), QEMU will simply shut down cleanly and return control to the host shell instead of booting the system anew.

The SAC Console

In scenarios where network connectivity is completely unavailable or the driver under test intentionally breaks the network stack, emulating the EMS (Emergency Management Services) interface becomes a reliable control channel.

Activating the low-level text-based SAC (Special Administration Console) takes place at the BCD configuration level:

bcdedit /ems on
bcdedit /emssettings emsport:1 emsbaudrate:115200

On the QEMU side, we map the COM1 serial port to a host TCP socket:

-chardev socket,id=char0,host=127.0.0.1,port=9999,server=on,wait=off -device isa-serial,chardev=char0,id=serial0

This gives us raw access to the system shell directly via a file descriptor or a TCP socket on the host, completely bypassing network interfaces, graphical layers, and standard remote communication protocols.

Hyper-V enlightenments vs KDNET

If you transition from a Windows/WHPX environment to a Linux host running KVM, it is common practice to enable Hyper-V enlightenments to optimize guest performance. In this setup, the NT kernel attempts to optimize system calls by delegating interrupt management to the synthetic, paravirtualized VMBus.

However, enabling these opt-in enlightenments in QEMU CPU configurations drastically destabilizes KDNET. If flags like hv-relaxed or hv-vapic are active, the kernel stops trusting standard PCI controller interrupts (e.g., the emulated Intel e1000) and attempts to route debugging traffic through Hyper-V mechanisms. This results in a silent initialization error within the internal debugger module. Worst of all, this error does not trigger a BSOD or halt the system—the kernel disables the debugger and continues booting, leaving the engineer with a perpetually frozen Waiting to reconnect... message in the WinDbg window.

Because Hyper-V enlightenments are opt-in and disabled by default, the solution is simple: do not add any hv-* flags to your QEMU CPU configuration. Keep the CPU flags clean to ensure KDNET retains direct access to the emulated PCI controller interrupts:

# Keep the CPU configuration clean without any hv-* flags (like hv-relaxed, hv-vapic, etc.)
-cpu host