--- title: 'WinPE as a stateless harness for Windows driver testing and fuzzing' description: 'Eliminate the overhead and lack of idempotency of a full Windows system in automated testing. A practical guide to configuring WinPE and QEMU for lightning-fast boot, automating kernel debugging, and avoiding KDNET pitfalls.' date: '2026-06-28' tags: ['winpe', 'windows', 'internals', 'ci-cd', 'kernel', 'nt', 'qemu'] published: true --- 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)](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/winpe-intro?view=windows-11). 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: ```cmd 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: ```cmd 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. ```cmd # 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: ```cmd 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: ```cmd 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 false ``` 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: ```ini [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: ```cmd bcdedit /ems on bcdedit /emssettings emsport:1 emsbaudrate:115200 ``` On the QEMU side, we map the `COM1` serial port to a host TCP socket: ```cmd -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: ```cmd # Keep the CPU configuration clean without any hv-* flags (like hv-relaxed, hv-vapic, etc.) -cpu host ```