<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: security-boundary</title><description>Posts tagged security-boundary.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 07 Jun 2026 04:13:16 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/security-boundary/rss.xml" rel="self" type="application/rss+xml"/><item><title>Windows Downdate: When the Update Itself Is the Attack</title><link>https://paragmali.com/blog/windows-downdate-when-the-update-itself-is-the-attack/</link><guid isPermaLink="true">https://paragmali.com/blog/windows-downdate-when-the-update-itself-is-the-attack/</guid><description>How Alon Leviev turned Windows Update into a downgrade primitive, rolling fully-patched Windows 11 back to vulnerable VBS components while every signature still verified.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Windows Update was designed to verify the integrity of files, not the monotonicity of versions.** In August 2024 at Black Hat USA, Alon Leviev (SafeBreach Labs) showed that an Administrator-context process can hijack Windows Update&apos;s own post-reboot servicing path by writing a single registry value, and use it to roll fully-patched Windows 11 system files back to historically vulnerable but legitimately Microsoft-signed versions [@safebreach-2024-aug]. Once the components VBS, HVCI, Credential Guard, and the Secure Kernel rely on have been replaced by their own past selves, the protections built on top of them quietly fail open. Microsoft has shipped a per-component revocation policy (`SkuSiPolicy.p7b` in KB5042562) and the substantive CVE-2024-38202 fix (KB5044284, October 2024), but maintains that the underlying primitive is not a security vulnerability because the Windows Security Servicing Criteria does not enumerate Administrator-to-kernel as a security boundary [@kb5042562; @kb5044284; @safebreach-2024-oct; @msft-servicing-criteria].
&lt;h2&gt;1. &quot;Up to Date&quot; Means Less Than It Says&lt;/h2&gt;
&lt;p&gt;Imagine a Windows 11 machine that has installed every cumulative update Microsoft has released this year. Settings says &lt;strong&gt;You&apos;re up to date&lt;/strong&gt;. The Authenticode signature on every system DLL validates against Microsoft&apos;s root. HVCI is on. Credential Guard is on. VBS is on with the UEFI lock engaged. Disk Cleanup is empty. The Servicing Stack reports a healthy state. And somewhere in &lt;code&gt;C:\Windows\System32&lt;/code&gt;, a two-year-old &lt;code&gt;ci.dll&lt;/code&gt; is happily enforcing the code-integrity policy on a kernel that thinks it is current.&lt;/p&gt;
&lt;p&gt;This machine was the demo on the SafeBreach blog in October 2024, and it is not a misconfiguration [@safebreach-2024-oct; @thehackernews-downdate]. The version on disk is &lt;code&gt;10.0.22621.1376&lt;/code&gt;, a build from before May 2024, when Microsoft patched Gabriel Landau&apos;s &lt;em&gt;False File Immutability&lt;/em&gt; race in &lt;code&gt;ci.dll&lt;/code&gt; [@elastic-ffi; @landau-itsnotasecurityboundary-repo]. The signature on that older build is legitimately Microsoft&apos;s. The hash matches a Microsoft-issued security catalog. Windows is perfectly happy to load it.&lt;/p&gt;
&lt;p&gt;The Driver Signature Enforcement policy, the &lt;a href=&quot;https://paragmali.com/blog/authenticode-and-catalog-files-the-crypto-foundation-under-w/&quot; rel=&quot;noopener&quot;&gt;Authenticode chain&lt;/a&gt;, the catalog trust path, the WinSxS component store, and the post-reboot servicing engine were all built on the same shared assumption. Windows Downdate is what happens when you stop assuming it.&lt;/p&gt;
&lt;p&gt;The author of that demo is Alon Leviev, a researcher then at SafeBreach Labs. On August 7, 2024 he presented &lt;em&gt;Windows Downdate: Downgrade Attacks Using Windows Updates&lt;/em&gt; at Black Hat USA, followed by a more detailed walk-through at DEF CON 32 four days later [@safebreach-2024-aug; @bh-leviev-slides].&lt;/p&gt;
&lt;p&gt;The technique he published does one thing and does it well: it takes Administrator-level access on a fully-patched Windows machine and converts it into Microsoft-signed historical code, running in the kernel, inside the Secure Kernel, inside the hypervisor, inside Credential Guard&apos;s isolated user-mode process. The OS does not notice. EDR does not notice. &lt;code&gt;sfc /scannow&lt;/code&gt; does not notice. Settings reports the system as patched, because in every sense Windows can express, it is.&lt;/p&gt;
&lt;p&gt;Leviev framed his goal as a four-property objective. The attack had to be &lt;strong&gt;undetectable&lt;/strong&gt; to endpoint security, &lt;strong&gt;invisible&lt;/strong&gt; to the user, &lt;strong&gt;persistent&lt;/strong&gt; across future updates, and &lt;strong&gt;irreversible&lt;/strong&gt; by repair tooling. The rest of this article will measure each piece of the attack against those four properties, but it is worth pausing on what they imply. They do not require defeating a single Microsoft mitigation. They require defeating &lt;em&gt;all of them simultaneously&lt;/em&gt;, and Leviev&apos;s claim is that one registry write is enough.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every Microsoft mitigation since 2015 implicitly assumed that the OS being protected was the current one. None of them declared &quot;the current one&quot; as a security boundary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That sentence is the spine of the article. To understand why nobody noticed for so long, we have to start somewhere unexpected: with a TLS bug from 2014.&lt;/p&gt;
&lt;h2&gt;2. A History of Downgrade Attacks (Before Windows Knew the Name)&lt;/h2&gt;
&lt;p&gt;If you can convince a system to do something old, you can convince it to do something dangerous. Bodo Möller, Thai Duong, and Krzysztof Kotowicz figured that out first, at least in the public literature. In October 2014 their &lt;em&gt;This POODLE Bites&lt;/em&gt; advisory described an attack on the way browsers retry failed TLS handshakes [@google-poodle-pdf; @nvd-poodle]. A man-in-the-middle could induce a connection failure, the browser would silently retry at a lower protocol version, and the server would accept SSL 3.0, where a CBC padding oracle let the attacker decrypt session cookies one byte at a time. SSL 3.0 had been broken for years, but it remained in the negotiation envelope for backwards compatibility, and the negotiation envelope was where the protocol was weakest.&lt;/p&gt;
&lt;p&gt;POODLE established the pattern. A protocol that retains legacy modes for compatibility creates a downgrade primitive &lt;em&gt;unless the protocol explicitly enforces &quot;highest version available.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Five months later, in March 2015, Karthikeyan Bhargavan and the miTLS team at INRIA found a near-identical pattern with FREAK: a stripped-down &quot;export-grade&quot; RSA cipher suite from the 1990s was still negotiable, and a fast attacker could factor the 512-bit key during the handshake [@freak-attack-site]. That same April, Möller and Adam Langley shipped RFC 7507 to standardise the first explicit in-band signal that a TLS client was deliberately falling back, so that the server could refuse [@rfc7507]. Three years after that, Eric Rescorla&apos;s TLS 1.3 (RFC 8446) baked downgrade resistance directly into the &lt;code&gt;ServerHello.random&lt;/code&gt; nonce -- a structural fix, not a hint [@rfc8446]. The same period gave us SLOTH from Bhargavan and Gaétan Leurent: a transcript-collision attack on TLS, IKE, and SSH whose mitigation pushed TLS 1.3 to &lt;em&gt;bind&lt;/em&gt; downgrade resistance into the transcript hash, making it impossible to rewrite the negotiation without breaking the integrity check [@sloth-ndss].&lt;/p&gt;
&lt;p&gt;The Microsoft UEFI CA 2023 rollout began in February 2024 with a phased deployment that runs through 2026, replacing the Windows Production 2011 CA in firmware databases worldwide [@msft-uefi-ca-2023]. This rollout is the firmware-layer analogue of TLS 1.3&apos;s binding: each rotation is intended to retire trust in the older signer, but the rotation only matters if the &lt;em&gt;consumer&lt;/em&gt; enforces it.&lt;/p&gt;
&lt;p&gt;Meanwhile, four years before POODLE, a small group of NYU and Tor researchers wrote the academic-canonical paper on what happens when an attacker controls a software update repository instead of a network. Justin Samuel, Nick Mathewson, Justin Cappos, and Roger Dingledine published &lt;em&gt;Survivable Key Compromise in Software Update Systems&lt;/em&gt; at ACM CCS 2010. They formalised three update-specific threats nobody had named before: &lt;strong&gt;rollback attacks&lt;/strong&gt; (the repository serves an older, vulnerable copy of metadata), &lt;strong&gt;freeze attacks&lt;/strong&gt; (the repository serves the same copy forever, preventing a client from ever learning about patches), and &lt;strong&gt;replay attacks&lt;/strong&gt; (the repository serves a stale snapshot to a victim selected by network position) [@tuf-spec; @tuf-security].&lt;/p&gt;
&lt;p&gt;The companion specification, now stewarded by the CNCF, says it plainly: &lt;em&gt;&quot;An attacker presents files to a software update system that are older than those the client has already seen. With no way to tell it is an obsolete version that may contain vulnerabilities, the user installs the software&quot;&lt;/em&gt; [@tuf-security]. That is The Update Framework. Sigstore, Docker Notary, PyPI&apos;s PEP 458, and in-toto all inherit its threat model.&lt;/p&gt;
&lt;p&gt;So by 2015 the academic and protocol communities had named the problem, given it a vocabulary, written a specification, and started shipping standards. Three years later the mobile world followed.&lt;/p&gt;
&lt;h3&gt;From protocols to operating systems&lt;/h3&gt;
&lt;p&gt;In August 2017, Android 8.0 shipped Verified Boot 2.0 (AVB), the first widely-deployed &lt;em&gt;operating-system&lt;/em&gt; rollback defence. AVB stamps a &lt;code&gt;rollback_index&lt;/code&gt; into each signed partition and stores per-slot maxima in TrustZone or in RPMB-backed storage; the bootloader refuses any image whose index is below the stored maximum [@aosp-avb]. The Android source page summarises the design goal: &lt;em&gt;&quot;AVB&apos;s key features include delegating updates for different partitions, a common footer format for signing partitions, and protection from attackers rolling back to a vulnerable version of Android&quot;&lt;/em&gt; [@aosp-avb].&lt;/p&gt;
&lt;p&gt;Three years after Android, Apple shipped the Signed System Volume on macOS Big Sur (November 2020). SSV seals the entire system volume into a single Merkle tree whose root is signed by Apple; on iOS and iPadOS the user cannot disable it [@apple-ssv]. The IETF Software Updates for Internet of Things working group standardised the same threat model in RFC 9019 (April 2021) for embedded firmware: &lt;em&gt;&quot;The firmware image is authenticated and integrity protected. Attempts to flash a maliciously modified firmware image or an image from an unknown, untrusted source must be prevented&quot;&lt;/em&gt; [@rfc9019].&lt;/p&gt;
&lt;p&gt;By 2022, every major mobile platform, every IoT firmware standard, and every modern image-based update system had named rollback as a primary threat and shipped a structural fix. Then came BlackLotus.&lt;/p&gt;

gantt
    title Downgrade attacks and defences, 2010-2024
    dateFormat YYYY-MM
    axisFormat %Y
    section Protocol downgrade
    POODLE (SSL 3.0)            :done, 2014-10, 60d
    FREAK (RSA_EXPORT)          :done, 2015-03, 30d
    RFC 7507 (TLS Fallback SCSV):done, 2015-04, 30d
    SLOTH (transcript collision):done, 2016-01, 30d
    TLS 1.3 (RFC 8446)          :done, 2018-08, 30d
    section Update systems
    TUF / CCS 2010              :done, 2010-10, 30d
    RFC 9019 (IETF SUIT)        :done, 2021-04, 30d
    section OS rollback defence
    Android AVB 2.0             :done, 2017-08, 30d
    Apple Sealed System Volume  :done, 2020-11, 30d
    section Windows precedent
    BlackLotus in the wild      :crit, 2022-10, 150d
    BlackLotus public           :crit, 2023-03, 30d
    Windows Downdate disclosure :crit, 2024-08, 30d
&lt;h3&gt;The Windows precedent&lt;/h3&gt;
&lt;p&gt;Martin Smolar&apos;s &lt;em&gt;BlackLotus UEFI Bootkit: Myth Confirmed&lt;/em&gt; arrived in March 2023, and it was the direct precedent Leviev would cite. BlackLotus was a UEFI bootkit that had been on sale on hacking forums since October 2022 [@welivesecurity-blacklotus]. Its key trick was to ship its own copy of a legitimately Microsoft-signed but vulnerable &lt;code&gt;bootmgfw.efi&lt;/code&gt; -- specifically, a build still affected by CVE-2022-21894, the &quot;Baton Drop&quot; &lt;a href=&quot;https://paragmali.com/blog/secure-boot-in-windows-the-chain-from-sector-zero-to-userini/&quot; rel=&quot;noopener&quot;&gt;Secure Boot&lt;/a&gt; bypass that Microsoft had patched in January 2022.&lt;/p&gt;
&lt;p&gt;Smolar wrote: &lt;em&gt;&quot;Although the vulnerability was fixed in Microsoft&apos;s January 2022 update, its exploitation is still possible as the affected, validly signed binaries have still not been added to the UEFI revocation list. BlackLotus takes advantage of this, bringing its own copies of legitimate -- but vulnerable -- binaries to the system in order to exploit the vulnerability&quot;&lt;/em&gt; [@welivesecurity-blacklotus]. The NSA shipped a &lt;em&gt;BlackLotus Mitigation Guide&lt;/em&gt; in June 2023 [@nsa-blacklotus-guide]; Microsoft began the laborious process of populating &lt;code&gt;dbx&lt;/code&gt;, the UEFI Secure Boot revocation list, with the offending hashes [@uefi-revocation-list].&lt;/p&gt;
&lt;p&gt;The point that anchors this section: Microsoft &lt;em&gt;did&lt;/em&gt; patch downgrade extensively at the firmware and boot-loader layer in response to BlackLotus. They updated &lt;code&gt;dbx&lt;/code&gt;. They rolled the UEFI Production CA in February 2024 [@msft-uefi-ca-2023]. The architectural lesson -- &lt;em&gt;if you have not declared which version of a signed binary is current, your signature is not enough&lt;/em&gt; -- had been internalised at the bottom of the stack. Whether anybody closed the same gap at the OS-component layer was a question only Leviev seems to have asked.&lt;/p&gt;
&lt;h2&gt;3. The Vista Bargain -- Component-Based Servicing and the Fourth Principal&lt;/h2&gt;
&lt;p&gt;If you sit down at a Windows 11 machine right now, open an elevated PowerShell as a local Administrator, and try to overwrite &lt;code&gt;C:\Windows\System32\ntoskrnl.exe&lt;/code&gt;, Windows will refuse. The error is &lt;em&gt;Access is denied&lt;/em&gt;. That is unexpected, because you are an Administrator, and on every Windows since NT 3.1 Administrators have been the highest principal on the box. The reason has a date.&lt;/p&gt;
&lt;p&gt;In November 2006, Windows Vista shipped a fourth Windows security principal: &lt;code&gt;NT SERVICE\TrustedInstaller&lt;/code&gt;. Its well-known SID is the long numeric string &lt;code&gt;S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464&lt;/code&gt;, and its job is to be the only identity permitted to write into most of &lt;code&gt;System32&lt;/code&gt; [@safebreach-2024-aug; @ms-learn-servicing-stack-updates]. Administrators can take ownership of files there and grant themselves write access, but the default ACL excludes them. The change accompanied two other Vista deliverables that, taken together, became the contract that Windows servicing has obeyed for two decades.&lt;/p&gt;

A Windows security principal introduced in Vista that owns most of the system files under `C:\Windows\System32`. Its job is to mediate component-based servicing operations: when you install a Windows Update, the work runs in TrustedInstaller&apos;s context, not the Administrator&apos;s. Direct writes to TrustedInstaller-owned files by other principals (including Administrators) are denied at the ACL.
&lt;p&gt;The first deliverable was &lt;strong&gt;Component-Based Servicing (CBS)&lt;/strong&gt;, the replacement for the self-extracting &lt;code&gt;Update.exe&lt;/code&gt; installers that had defined patch delivery from Windows NT 4.0 through Windows XP. CBS reshaped a Windows update from &quot;a small executable that scribbles into your system directory&quot; into &quot;a manifest-driven transaction over a versioned component store.&quot; The second deliverable was &lt;strong&gt;WinSxS&lt;/strong&gt; -- the side-by-side store under &lt;code&gt;C:\Windows\WinSxS\&lt;/code&gt; that holds every version of every CBS-managed component the system has ever installed [@safebreach-2024-aug; @ms-learn-servicing-stack-updates].&lt;/p&gt;

The Windows servicing architecture introduced in Vista that replaced self-extracting update installers. A CBS package contains a Microsoft-signed security catalog (`.cat`) whose hashes cover the package&apos;s manifest files (`.mum`, `.manifest`). The manifests, transitively trusted via the signed catalog, describe which files belong to which component and how those files should be installed. CBS operations are mediated by the TrustedInstaller service.
&lt;p&gt;The third deliverable was the &lt;strong&gt;manifest-and-catalog signing model&lt;/strong&gt; that knit the first two together. A CBS package contains a security catalog (&lt;code&gt;.cat&lt;/code&gt;) signed directly by Microsoft. The catalog&apos;s hashes cover the package&apos;s manifest files (&lt;code&gt;.mum&lt;/code&gt; and &lt;code&gt;.manifest&lt;/code&gt;); the manifests, in turn, name the package&apos;s payload files and describe their installation. The manifest files are &lt;em&gt;not&lt;/em&gt; signed individually, but their hashes appear in the signed catalog, so they are &lt;em&gt;transitively trusted&lt;/em&gt; [@safebreach-2024-aug]. The payload files are also not individually signed; their hashes appear in the manifests, which are themselves catalog-covered. The chain of custody runs catalog -&amp;gt; manifest -&amp;gt; payload, and at the root is Microsoft&apos;s signing key.&lt;/p&gt;
&lt;p&gt;What this gives you is an elegant, declarative contract for system integrity. Microsoft signs a catalog. The catalog vouches for a manifest. The manifest vouches for a file. The file lands on disk. TrustedInstaller is the only principal allowed to write it, and TrustedInstaller has its own protected service that runs the install transactionally.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Signed file + TrustedInstaller-only write = system integrity.&lt;/strong&gt; Microsoft built the Vista servicing stack around this contract in 2006-2007. The contract is true on its face. It is also silent about one thing: &lt;em&gt;which version&lt;/em&gt; of a signed file is allowed to land on disk.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That silence is older than CBS. It traces back through the related 2005-2007 file-integrity work. Kernel Patch Protection, marketed as PatchGuard, shipped in x64 Windows Server 2003 SP1 in March 2005 and watches for tampering with running kernel structures [@msft-patchguard-advisory]. KMCS, the Kernel-Mode Code Signing Walkthrough Microsoft published in July 2007, defined the &lt;a href=&quot;https://paragmali.com/blog/windows-kernel-code-integrity-2006-2026/&quot; rel=&quot;noopener&quot;&gt;&lt;em&gt;Driver Signature Enforcement&lt;/em&gt;&lt;/a&gt; policy that kernel-mode code on x64 Vista and later had to satisfy [@kmcs-walkthrough; @msdocs-kmcs-policy].&lt;/p&gt;
&lt;p&gt;The Microsoft Learn descendant page is more direct: &lt;em&gt;&quot;The kernel-mode driver signing policy for 64-bit versions of Windows Vista and later versions of Windows specifies that a kernel-mode driver must be signed for the driver to load&quot;&lt;/em&gt; [@msdocs-driver-signing]. Each of these primitives bound trust to &lt;em&gt;a Microsoft signature&lt;/em&gt;. None of them bound trust to &lt;em&gt;a Microsoft-asserted version&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This was reasonable in 2007. Update.exe had been the threat: tampering with system files mid-flight, racing the installer, replacing a DLL while the system was rebooting. Vista&apos;s reply was to put the entire operation behind a principal that admins could not impersonate. The threat model said &lt;em&gt;&quot;the attacker is some Administrator-context tool that will try to overwrite system files.&quot;&lt;/em&gt; The reply said &lt;em&gt;&quot;only TrustedInstaller writes system files, and TrustedInstaller will only write files the catalog says are theirs.&quot;&lt;/em&gt; It was a complete answer to the question that had been asked.&lt;/p&gt;
&lt;p&gt;It was an incomplete answer to a question nobody asked: &lt;em&gt;which version of which file goes through that door, and is &quot;which version&quot; a property anyone actually checks?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Eighteen years later, the answer was: no, &quot;which version&quot; is not a property anyone checks at the file layer. The catalog says &quot;this file belongs to this package.&quot; It does not say &quot;this file is the current member of this component family.&quot;&lt;/p&gt;
&lt;p&gt;The Vista bargain locked the door. It signed the keys. It named a single person who could turn the lock. What it did not do -- and was not designed to do -- was care whether the box behind the door was on the latest update or on the build from two years ago. That decision would land on three later generations of Windows integrity walls, and none of them caught it.&lt;/p&gt;
&lt;h2&gt;4. Generation by Generation -- The Walls That Weren&apos;t Walls of Time&lt;/h2&gt;
&lt;p&gt;Between 2007 and 2022, Microsoft built four more integrity walls on top of the Vista bargain. Each one was a generation forward. None of them added the dimension Windows Downdate needed them to add.&lt;/p&gt;
&lt;h3&gt;Generation 1: Kernel-Mode Code Signing / DSE (Vista x64, 2007)&lt;/h3&gt;
&lt;p&gt;The first wall. The kernel refused to load any driver that was not Authenticode-signed by a Microsoft-cross-signed CA [@kmcs-walkthrough; @msdocs-kmcs-policy; @msdocs-driver-signing]. DSE was enforced at driver load time by the kernel loader; non-signed drivers failed to load with an unmissable error. This was the first architectural assertion that &quot;a Microsoft-signed binary is the unit of trust at the kernel boundary.&quot;&lt;/p&gt;
&lt;p&gt;What it said: a kernel driver must be signed by Microsoft.&lt;/p&gt;
&lt;p&gt;What it did not say: &lt;em&gt;which&lt;/em&gt; Microsoft-signed driver. The catalog-trust model treats any Microsoft-signed version of a file as equally legitimate. That assumption is the one Windows Downdate exploits seventeen years later: signature validity is preserved across version rollback, because the catalog of the older version is still a Microsoft-signed catalog and the hash chain still resolves.&lt;/p&gt;
&lt;h3&gt;Generation 2: UEFI Secure Boot (Windows 8, 2012)&lt;/h3&gt;
&lt;p&gt;The second wall pushed the same idea down into firmware. The platform firmware refused to load a boot manager that was not signed by a key in the UEFI signature database (&lt;code&gt;db&lt;/code&gt;), and refused to load any binary whose hash was in the forbidden-signature database (&lt;code&gt;dbx&lt;/code&gt;) [@welivesecurity-blacklotus; @msft-uefi-ca-2023]. For the first time, Windows had a &lt;em&gt;version-specific revocation primitive at the platform layer&lt;/em&gt;: &lt;code&gt;dbx&lt;/code&gt; could enumerate specific binaries known to be unsafe.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;dbx&lt;/code&gt; had two problems. The first was rollout latency: BlackLotus demonstrated in 2023 that vulnerable, validly-signed &lt;code&gt;bootmgfw.efi&lt;/code&gt; binaries were still trusted by firmware a full year after Microsoft patched them in source [@welivesecurity-blacklotus]. The second was scope: &lt;code&gt;dbx&lt;/code&gt; covered boot-time binaries, not run-time OS components. A revocation primitive that only fires before &lt;code&gt;ntoskrnl.exe&lt;/code&gt; loads is not a defence against rolling back &lt;code&gt;ntoskrnl.exe&lt;/code&gt; itself.&lt;/p&gt;
&lt;p&gt;The Microsoft UEFI CA 2023 rollout is on a multi-year phased schedule starting February 13, 2024 and running into 2026 [@msft-uefi-ca-2023]. The phased rollout exists precisely because retiring trust in older signers is slow and operationally risky -- the same shape of problem that &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; now faces at the OS layer.&lt;/p&gt;
&lt;h3&gt;Generation 3: VBS + HVCI + Credential Guard (Windows 10, 2015)&lt;/h3&gt;
&lt;p&gt;The third wall changed the &lt;em&gt;threat model itself&lt;/em&gt;. Virtualization-Based Security used the Hyper-V hypervisor to create a higher-privilege isolation domain, called &lt;em&gt;Virtual Trust Level 1&lt;/em&gt; (VTL1), beneath the NT kernel&apos;s normal VTL0. Microsoft&apos;s own documentation states the new assumption directly: &lt;em&gt;&quot;VBS uses hardware virtualization and the Windows hypervisor to create an isolated virtual environment that becomes the root of trust of the OS that assumes the kernel can be compromised&quot;&lt;/em&gt; [@msdocs-vbs].&lt;/p&gt;

Virtual Trust Level 1 is the higher-privilege half of the Hyper-V-managed split that VBS introduces. VTL0 holds the normal NT kernel and user-mode processes. VTL1 holds the Secure Kernel (`securekernel.exe`), the kernel-mode Code Integrity policy enforcement (`skci.dll`), and Isolated User Mode trustlets such as the LSA isolation process (`LsaIso.exe`). VTL0 cannot read VTL1 memory; transitions between the two go through a narrow set of hypercalls.
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/wdac--hvci-code-integrity-at-every-layer-in-windows/&quot; rel=&quot;noopener&quot;&gt;HVCI&lt;/a&gt; moved the kernel-mode code integrity check inside VTL1: a malicious kernel could no longer disable the check, because the check ran in a memory space the kernel could not write [@msdocs-vbs-hvci]. &lt;a href=&quot;https://paragmali.com/blog/the-empty-hash-credential-guard-the-lsaiso-trustlet-and-the-/&quot; rel=&quot;noopener&quot;&gt;Credential Guard&lt;/a&gt; moved LSA secrets into an Isolated User Mode trustlet, &lt;code&gt;LsaIso.exe&lt;/code&gt;, so a kernel-level attacker could not directly read NTLM hashes or Kerberos TGTs from LSASS memory [@msdocs-credguard]. The explicit, written threat model said: &lt;em&gt;assume the NT kernel can be compromised, and provide a higher-privilege isolation domain for security-critical state.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That sentence is doing all the work. It says VBS is a defence &lt;em&gt;against&lt;/em&gt; a compromised kernel, which means VBS is a defence against an attacker who has reached kernel code execution by any means. And one of the ways an attacker reaches kernel code execution -- one of the obvious ones, on a single-user Windows machine -- is to be an Administrator. The whole point of VBS was that Administrator code execution is the threat. That fact will matter again in section eight.&lt;/p&gt;
&lt;p&gt;The unsaid assumption in 2015 was that VTL1 components -- &lt;code&gt;securekernel.exe&lt;/code&gt;, &lt;code&gt;skci.dll&lt;/code&gt;, &lt;code&gt;LsaIso.exe&lt;/code&gt;, and the hypervisor binaries &lt;code&gt;hvix64.exe&lt;/code&gt; and &lt;code&gt;hvax64.exe&lt;/code&gt; -- were loaded from on-disk files using CBS+catalog trust, and that CBS+catalog trust was version-agnostic. Microsoft was building a higher trust boundary, but the integrity check for the binaries that lived on the other side of that boundary still ran through the Vista contract.&lt;/p&gt;
&lt;h3&gt;Generation 4: The Microsoft Vulnerable Driver Blocklist (2020 opt-in, default-on November 2022)&lt;/h3&gt;
&lt;p&gt;The fourth wall finally introduced a &lt;em&gt;generic version-revocation primitive&lt;/em&gt; for kernel-loaded code. The Microsoft Vulnerable Driver Blocklist was the answer to a class of attacks that had emerged in the 2010s: &lt;em&gt;bring-your-own-vulnerable-driver&lt;/em&gt;, where a malware loader installed a legitimately-signed, third-party driver with a known kernel exploit and used it as a bridge to kernel execution [@msft-driver-blocklist-blog]. The blocklist&apos;s Microsoft Learn page is direct about scope: the policy targets &lt;em&gt;non-Microsoft-developed drivers across the Windows software environment&lt;/em&gt;, and since the Windows 11 2022 update the blocklist is enabled by default for all devices [@msdocs-driver-blocklist].&lt;/p&gt;
&lt;p&gt;Read that sentence again, slowly. &lt;em&gt;Non-Microsoft-developed drivers.&lt;/em&gt; The blocklist is the right &lt;em&gt;mechanism&lt;/em&gt; -- a Microsoft-signed list of hashes of known-vulnerable signed binaries that the kernel refuses to load -- but it is pointed at the wrong &lt;em&gt;inventory&lt;/em&gt;. First-party Microsoft binaries, including the very VBS components VBS depends on, are out of scope. The same Microsoft team that built the only generic version-revocation primitive Windows ships chose, by policy, not to apply it to themselves.&lt;/p&gt;

flowchart TD
    A[&quot;Vista CBS + TrustedInstaller (2007)&lt;br /&gt;Signed file + restricted writer&quot;] --&amp;gt; B[&quot;KMCS / DSE (2007)&lt;br /&gt;Kernel rejects unsigned drivers&quot;]
    B --&amp;gt; C[&quot;UEFI Secure Boot (2012)&lt;br /&gt;Firmware rejects unsigned boot binaries&lt;br /&gt;dbx revokes specific hashes&quot;]
    C --&amp;gt; D[&quot;VBS + HVCI + Credential Guard (2015)&lt;br /&gt;VTL1 isolation, assume-kernel-compromised&lt;br /&gt;CI policy enforced inside Secure Kernel&quot;]
    D --&amp;gt; E[&quot;Vulnerable Driver Blocklist (2020-2022)&lt;br /&gt;Microsoft-signed hash revocation&lt;br /&gt;Third-party drivers only&quot;]
    E --&amp;gt; F[&quot;Gap: First-party VBS components&lt;br /&gt;still load by catalog signature alone&quot;]

The shape of `dbx` and the shape of `SkuSiPolicy.p7b` are the same: a Microsoft-signed list of hashes a loader refuses. `dbx` lives in UEFI firmware variables and is consulted by the platform boot manager. `SkuSiPolicy.p7b` is a Microsoft-signed Code Integrity policy that lives in the EFI System Partition (when the opt-in UEFI lock is applied) or in the boot session (the default-enabled variant), and is consulted by the Windows kernel loader. The conceptual lineage runs *firmware-layer hash revocation in 2012 -&amp;gt; OS-layer hash revocation in 2024*. The intervening twelve years were spent assuming the OS layer did not need it.
&lt;p&gt;By 2022, Microsoft had built every primitive a Downdate defence would have used. A Microsoft-signed hash-revocation list (the Driver Blocklist). A firmware-rooted enforcement chain (Secure Boot + &lt;code&gt;dbx&lt;/code&gt;). A hypervisor-isolated integrity check (HVCI inside VTL1). What had not been built was a &lt;em&gt;first-party&lt;/em&gt; hash-revocation list -- one that named the historical versions of &lt;code&gt;ci.dll&lt;/code&gt;, &lt;code&gt;ntoskrnl.exe&lt;/code&gt;, &lt;code&gt;securekernel.exe&lt;/code&gt;, &lt;code&gt;hvix64.exe&lt;/code&gt;, and &lt;code&gt;LsaIso.exe&lt;/code&gt; and refused to load them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Recall the thesis from §1: every Microsoft mitigation since 2015 implicitly assumes the OS being protected is the current one. The mechanism for declaring it -- hash revocation -- had existed since 2012, but it was always pointed at &lt;em&gt;somebody else&apos;s code&lt;/em&gt;. The Driver Blocklist proves Microsoft can ship a first-party hash-revocation list. It just had not been pointed inward.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So Microsoft had built every primitive it needed by 2022: a hash-revocation list, a firmware-rooted enforcement chain, a hypervisor-isolated integrity check. Why, in 2024, is a fully-patched Windows 11 machine still capable of loading a 2022 &lt;code&gt;ci.dll&lt;/code&gt;?&lt;/p&gt;
&lt;h2&gt;5. The Breakthrough -- Where the Integrity Boundary Moves&lt;/h2&gt;
&lt;p&gt;Leviev started with a simple specification. He wanted a downgrade that satisfied four properties [@safebreach-2024-aug]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Undetectable&lt;/strong&gt; by endpoint security tooling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invisible&lt;/strong&gt; in &lt;code&gt;winver&lt;/code&gt;, Settings, and the system&apos;s own self-reported state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent&lt;/strong&gt; across future Windows Update installations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Irreversible&lt;/strong&gt; by &lt;code&gt;sfc /scannow&lt;/code&gt;, &lt;code&gt;DISM /Online /Cleanup-Image /RestoreHealth&lt;/code&gt;, and other repair tooling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &quot;undetectable&quot; requirement disqualified almost every obvious approach. Disabling Authenticode checking is detectable. Replacing the catalog signing root is detectable. Booting into Safe Mode and overwriting files is detectable. Loading a vulnerable driver is detectable. Whatever the attack ended up looking like, it had to run through &lt;em&gt;the legitimate Windows Update path&lt;/em&gt;, because that path is the one EDR is least suspicious of.&lt;/p&gt;
&lt;p&gt;Reading Leviev&apos;s August 2024 SafeBreach write-up is a study in patient state-machine reverse engineering. He had to discover the architecture of CBS, where TrustedInstaller fits into it, how &lt;code&gt;pending.xml&lt;/code&gt; action lists are written, where the integrity boundary of each phase lies, and which registry values are TrustedInstaller-protected versus which are merely Administrator-protected. Most of the answer turned out to be hidden in plain sight.&lt;/p&gt;
&lt;h3&gt;The Windows Update state machine&lt;/h3&gt;
&lt;p&gt;A Windows Update flows through a small state machine, and Leviev&apos;s contribution is to draw it precisely.&lt;/p&gt;
&lt;p&gt;A client process in Administrator context calls into the Windows Update Agent COM interfaces. Those interfaces transfer the update folder -- a Microsoft-signed package containing a &lt;code&gt;.cat&lt;/code&gt;, several &lt;code&gt;.mum&lt;/code&gt; and &lt;code&gt;.manifest&lt;/code&gt; files, and the new payload binaries -- to a TrustedInstaller-context server (&lt;code&gt;TrustedInstaller.exe&lt;/code&gt;). The server verifies the catalog signature, walks the manifests, and constructs an &lt;em&gt;action list&lt;/em&gt;. The action list is the work order that explains exactly which files will be renamed, hardlinked, deleted, or written, and which registry values will be set, when the system reboots. Microsoft stores it in a file called &lt;code&gt;pending.xml&lt;/code&gt; under a TrustedInstaller-only directory (&lt;code&gt;C:\Windows\WinSxS\pending.xml&lt;/code&gt;) [@safebreach-2024-aug; @ms-learn-servicing-stack-updates].&lt;/p&gt;
&lt;p&gt;On reboot, before the user logs in, a small program named &lt;code&gt;poqexec.exe&lt;/code&gt; reads &lt;code&gt;pending.xml&lt;/code&gt; and applies it. POQ stands for &quot;Primitive Operations Queue.&quot; The program is the post-reboot transactional engine that performs work the running OS could not safely do while it was running (such as overwriting &lt;code&gt;ntoskrnl.exe&lt;/code&gt;).&lt;/p&gt;

The post-reboot primitive-operations-queue executor. Reads the action list (`pending.xml`) at next boot, before normal services start, and applies its verbs -- hardlinks, file moves, registry writes -- to complete the previous boot&apos;s pending update operations. `poqexec.exe` has no notion of &quot;current version&quot; versus &quot;older version&quot;; it executes whatever action list its configuration points it at.
&lt;p&gt;So far, nothing is wrong. The catalog is signed. The manifests are catalog-covered. The action list lives in a TrustedInstaller-only directory. The executor that consumes it is part of the Windows servicing stack. The chain of custody runs from Microsoft&apos;s signing key through to the on-disk binaries the executor produces.&lt;/p&gt;
&lt;h3&gt;The action list integrity model -- and where it breaks&lt;/h3&gt;
&lt;p&gt;The catch is this. &lt;em&gt;The pointer to &lt;code&gt;pending.xml&lt;/code&gt; is not in a TrustedInstaller-only registry key.&lt;/em&gt; It is in &lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SideBySide\Configuration\PoqexecCmdline&lt;/code&gt;. The DACL on that value allows Administrator write. There is a parallel value, &lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\PendingXmlIdentifier&lt;/code&gt;, that carries a nonce binding the action list to the boot-session identity; that value is also Administrator-writable [@safebreach-2024-aug; @splunk-downdate-detection].&lt;/p&gt;

The XML work order that describes the file and registry operations a Windows Update will apply at next boot. It uses a small set of POQ verbs (`HardlinkFile`, `MoveFile`, `CreateFile`, `SetFileInformation`, `DeleteFile`, `CreateDirectory`, `CreateKey`, `SetKeyValue`, `SetKeySecurity`, `DeleteKeyValue`, `DeleteKey`). The default copy lives in a TrustedInstaller-only directory. Which copy `poqexec.exe` parses on next boot is determined by an Administrator-writable registry value, `PoqexecCmdline`.
&lt;p&gt;The Administrator who initiates an update can choose &lt;em&gt;which action list &lt;code&gt;poqexec.exe&lt;/code&gt; parses&lt;/em&gt;. The integrity check on the update folder happened at the start of the transaction, in a different phase, with &lt;code&gt;TrustedInstaller&lt;/code&gt; doing the parsing. Once the action list has been produced, the chain of custody depends on Windows believing that &lt;code&gt;pending.xml&lt;/code&gt; came from a Microsoft-signed package. The mechanism by which Windows believes that is a registry value that an Administrator can rewrite.&lt;/p&gt;
&lt;p&gt;Differential update files are not individually signed -- their hashes appear in the catalog-covered manifest, so they inherit catalog trust by reference. This is a perfectly sensible design &lt;em&gt;if you also assert that the manifest you are using is the manifest for the latest update&lt;/em&gt;, which Windows does not.&lt;/p&gt;
&lt;p&gt;That is the architectural error, and it has a name. &lt;strong&gt;The integrity boundary moves between phases of the update.&lt;/strong&gt; The update folder is verified pre-action-list-creation. The action list is verified by being in a TrustedInstaller directory. &lt;em&gt;But the pointer to the action list is in admin-writable territory.&lt;/em&gt; The same identity (Administrator) that can legitimately initiate an update can choose which action list &lt;code&gt;poqexec.exe&lt;/code&gt; parses, and &lt;code&gt;poqexec.exe&lt;/code&gt; was never built to ask &quot;is this list the one I made?&quot;&lt;/p&gt;

sequenceDiagram
    participant Admin as Admin process
    participant TI as TrustedInstaller
    participant Reg as Registry (PoqexecCmdline)
    participant POQ as poqexec.exe (next boot)
    participant FS as System32 files
    Note over Admin,FS: Legitimate Windows Update
    Admin-&amp;gt;&amp;gt;TI: Submit signed update folder over COM
    TI-&amp;gt;&amp;gt;TI: Verify catalog and manifests
    TI-&amp;gt;&amp;gt;FS: Write pending.xml to WinSxS (TI-only)
    TI-&amp;gt;&amp;gt;Reg: Set PoqexecCmdline to default pending.xml
    Admin-&amp;gt;&amp;gt;Admin: Reboot
    POQ-&amp;gt;&amp;gt;Reg: Read PoqexecCmdline
    POQ-&amp;gt;&amp;gt;FS: Read default pending.xml
    POQ-&amp;gt;&amp;gt;FS: Apply verbs, install new files
    Note over Admin,FS: Windows Downdate
    Admin-&amp;gt;&amp;gt;FS: Write crafted pending.xml to attacker dir
    Admin-&amp;gt;&amp;gt;Reg: Overwrite PoqexecCmdline to crafted path
    Admin-&amp;gt;&amp;gt;Reg: Overwrite PendingXmlIdentifier to matching nonce
    Admin-&amp;gt;&amp;gt;Admin: Reboot
    POQ-&amp;gt;&amp;gt;Reg: Read PoqexecCmdline (now attacker path)
    POQ-&amp;gt;&amp;gt;FS: Read crafted pending.xml
    POQ-&amp;gt;&amp;gt;FS: Hardlink older Microsoft-signed binaries over current ones
&lt;p&gt;Once you can choose which action list &lt;code&gt;poqexec.exe&lt;/code&gt; parses, and &lt;code&gt;poqexec.exe&lt;/code&gt; was never built to ask &quot;is this list the one I made?&quot;, the consequences write themselves. The crafted &lt;code&gt;pending.xml&lt;/code&gt; can issue any verb the executor supports. It can hardlink an older &lt;code&gt;ci.dll&lt;/code&gt; over the current one. It can hardlink an older &lt;code&gt;ntoskrnl.exe&lt;/code&gt; over the current one. It can hardlink an older &lt;code&gt;securekernel.exe&lt;/code&gt; over the current one. The hashes of those older files appear in the Microsoft-signed catalogs of their original packages, which are still on disk in WinSxS. Every signature the kernel loader and the Secure Kernel will ever check resolves to a Microsoft key.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The bug is structural: the integrity boundary moves between phases of the update. The catalog signature verifies one phase; the directory ACL verifies the next; the registry pointer crosses the boundary in the wrong direction. The fix has to declare a new boundary that does not move.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;6. The Downdate Attack End-to-End&lt;/h2&gt;
&lt;p&gt;What does &lt;code&gt;python windows_downdate.py --config-xml downgrade.xml&lt;/code&gt; actually do on the wire? The tool&apos;s GitHub repository, &lt;code&gt;SafeBreach-Labs/WindowsDowndate&lt;/code&gt;, ships a documented schema and eight example chains, from a generic &lt;code&gt;ItsNotASecurityBoundary-Patch-Downgrade&lt;/code&gt; to a fully-formed &lt;code&gt;VBS-UEFI-Locks-Bypass&lt;/code&gt; [@windowsdowndate-repo]. Read alongside Leviev&apos;s blog, the procedure is, in machine terms, embarrassingly small. Eight steps.&lt;/p&gt;
&lt;h3&gt;6.1 The attack sequence&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Parse the config XML.&lt;/strong&gt; Each &lt;code&gt;&amp;lt;UpdateFile source=&quot;...&quot; destination=&quot;...&quot;/&amp;gt;&lt;/code&gt; element names one file to downgrade. If the source file does not exist locally, the tool retrieves the base version from the WinSxS component store [@windowsdowndate-repo].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build a crafted pending.xml.&lt;/strong&gt; For each target, emit a &lt;code&gt;&amp;lt;HardlinkFile&amp;gt;&lt;/code&gt; verb that creates a hardlink from the older file&apos;s location to the current file&apos;s location, plus the other POQ verbs needed to set ACLs, register the new file with the component store, and update the on-disk manifest hash where needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deposit pending.xml in an attacker-writable directory.&lt;/strong&gt; The Administrator does not need TrustedInstaller-write privilege to do this; the tool stores the crafted action list outside the TrustedInstaller-only directories.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compute a matching PendingXmlIdentifier nonce.&lt;/strong&gt; This is the value that &lt;code&gt;poqexec.exe&lt;/code&gt; cross-checks against the action list at parse time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write the two registry values.&lt;/strong&gt; &lt;code&gt;PoqexecCmdline&lt;/code&gt; is set to point at the attacker&apos;s &lt;code&gt;pending.xml&lt;/code&gt;. &lt;code&gt;PendingXmlIdentifier&lt;/code&gt; is set to the nonce computed in step 4.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trigger a reboot.&lt;/strong&gt; This can be a graceful &lt;code&gt;shutdown /r /t 0&lt;/code&gt; or any reboot-causing event.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;At next boot, &lt;code&gt;poqexec.exe&lt;/code&gt; reads PoqexecCmdline and parses the attacker pending.xml.&lt;/strong&gt; It applies the verbs in order. The current &lt;code&gt;ci.dll&lt;/code&gt; is replaced (via hardlink) with &lt;code&gt;10.0.22621.1376&lt;/code&gt;. The current &lt;code&gt;ntoskrnl.exe&lt;/code&gt; is replaced with whichever version the operator chose. The current &lt;code&gt;securekernel.exe&lt;/code&gt; is replaced. The current &lt;code&gt;hvix64.exe&lt;/code&gt; is replaced. The current &lt;code&gt;LsaIso.exe&lt;/code&gt; is replaced.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistence and irreversibility.&lt;/strong&gt; Two further verbs replace &lt;code&gt;poqexec.exe&lt;/code&gt; with a patched copy that NOPs future updates, and replace &lt;code&gt;sfc.exe&lt;/code&gt; with a patched copy that does not flag the downgraded files. Leviev&apos;s claim in the SafeBreach blog is that &lt;code&gt;poqexec.exe&lt;/code&gt; and &lt;code&gt;sfc.exe&lt;/code&gt; are &lt;em&gt;not&lt;/em&gt; Authenticode-signed in the affected builds, so substituting modified versions does not require a code-signing bypass [@safebreach-2024-aug].&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The full set of POQ XML verbs that &lt;code&gt;poqexec.exe&lt;/code&gt; understands, taken from Leviev&apos;s documentation: &lt;code&gt;HardlinkFile&lt;/code&gt;, &lt;code&gt;MoveFile&lt;/code&gt;, &lt;code&gt;CreateFile&lt;/code&gt;, &lt;code&gt;SetFileInformation&lt;/code&gt;, &lt;code&gt;DeleteFile&lt;/code&gt;, &lt;code&gt;CreateDirectory&lt;/code&gt;, &lt;code&gt;CreateKey&lt;/code&gt;, &lt;code&gt;SetKeyValue&lt;/code&gt;, &lt;code&gt;SetKeySecurity&lt;/code&gt;, &lt;code&gt;DeleteKeyValue&lt;/code&gt;, and &lt;code&gt;DeleteKey&lt;/code&gt;. The verb that does most of the work in a Downdate is &lt;code&gt;HardlinkFile&lt;/code&gt;, because it lets the attacker replace a file in &lt;code&gt;System32&lt;/code&gt; without ever calling &lt;code&gt;WriteFile&lt;/code&gt; against a TrustedInstaller-owned path.&lt;/p&gt;

flowchart TD
    A[&quot;Operator config XML&lt;br /&gt;UpdateFile source dest pairs&quot;] --&amp;gt; B[&quot;Fetch source files&lt;br /&gt;from WinSxS or attacker storage&quot;]
    B --&amp;gt; C[&quot;Emit crafted pending.xml&lt;br /&gt;HardlinkFile + ACL verbs&quot;]
    C --&amp;gt; D[&quot;Write pending.xml&lt;br /&gt;to attacker dir&quot;]
    D --&amp;gt; E[&quot;Compute PendingXmlIdentifier nonce&quot;]
    E --&amp;gt; F[&quot;Write PoqexecCmdline registry value&lt;br /&gt;point at crafted pending.xml&quot;]
    F --&amp;gt; G[&quot;Write PendingXmlIdentifier&quot;]
    G --&amp;gt; H[&quot;Reboot&quot;]
    H --&amp;gt; I[&quot;poqexec.exe reads PoqexecCmdline&quot;]
    I --&amp;gt; J[&quot;Apply HardlinkFile verbs:&lt;br /&gt;ci.dll, ntoskrnl.exe, securekernel.exe,&lt;br /&gt;hvix64.exe, LsaIso.exe replaced&quot;]
    J --&amp;gt; K[&quot;Optional: patch poqexec.exe NOP&lt;br /&gt;and sfc.exe to NOP&quot;]
    K --&amp;gt; L[&quot;System boots: every signature valid,&lt;br /&gt;every component historic&quot;]
&lt;h3&gt;6.2 What got downgraded&lt;/h3&gt;
&lt;p&gt;Each Downdate target is a different layer of the threat model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Afd.sys&lt;/code&gt;&lt;/strong&gt; is the Ancillary Function Driver -- a kernel-mode networking driver. In the Black Hat USA demo, Leviev paired the Downdate of &lt;code&gt;Afd.sys&lt;/code&gt; with CVE-2023-21768 to demonstrate Administrator-to-kernel code execution on a fully patched Windows 11 system [@windowsdowndate-repo].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ntoskrnl.exe&lt;/code&gt;&lt;/strong&gt; is the NT kernel image itself. Downgrade to a build with a public elevation-of-privilege chain and the resulting kernel is still Microsoft-signed but is also still vulnerable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;securekernel.exe&lt;/code&gt;&lt;/strong&gt; is the VTL1 &lt;a href=&quot;https://paragmali.com/blog/the-windows-secure-kernel/&quot; rel=&quot;noopener&quot;&gt;Secure Kernel&lt;/a&gt;. It is the keystone of VBS: the Secure Kernel is what HVCI and Credential Guard rely on for isolation. Replace it with an older build that contains a kernel-side bug, and every protection that runs in VTL1 is now running on top of compromised infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;hvix64.exe&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;hvax64.exe&lt;/code&gt;&lt;/strong&gt; are the Intel and AMD Hyper-V hypervisor binaries. Downgrade the hypervisor and the entire VBS trust root has moved beneath you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;LsaIso.exe&lt;/code&gt;&lt;/strong&gt; is the Credential Guard Isolated User Mode trustlet [@msdocs-credguard]. It holds the LSA secrets that Credential Guard protects. An older &lt;code&gt;LsaIso.exe&lt;/code&gt; is, by Microsoft&apos;s own threat model, a known-bad binary running inside the security-feature-of-record.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ci.dll&lt;/code&gt;&lt;/strong&gt; is the keystone of the October 2024 follow-up. The kernel-mode Code Integrity module enforces DSE: it is the gate that asks &quot;is this driver signed?&quot; Roll it back to &lt;code&gt;10.0.22621.1376&lt;/code&gt; and Gabriel Landau&apos;s False File Immutability bypass works again on a fully-patched Windows 11.&lt;/p&gt;
&lt;h3&gt;6.3 Bypassing VBS UEFI locks without physical access&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s &quot;VBS UEFI lock&quot; feature, intended to be the strongest configuration of VBS, copies the VBS configuration registry settings into a UEFI non-volatile, boot-services-only variable called &lt;code&gt;VbsPolicy&lt;/code&gt; [@msdocs-vbs-hvci]. Once set, the lock survives reboots, reinstalls, and most ordinary attacks against the registry, because the firmware re-applies it on every boot. Before Windows Downdate, the canonical advice for the highest-security configuration was: turn on VBS with the UEFI lock. The lock was the moat.&lt;/p&gt;
&lt;p&gt;Leviev&apos;s framing of what he showed was direct: &lt;em&gt;&quot;to my knowledge, this is the first time VBS&apos;s UEFI locks have been bypassed without physical access&quot;&lt;/em&gt; [@safebreach-2024-aug].&lt;/p&gt;

To my knowledge, this is the first time VBS&apos;s UEFI locks have been bypassed without physical access. -- Alon Leviev, SafeBreach Labs, August 2024 [@safebreach-2024-aug]
&lt;p&gt;The mechanism is the cleanest possible illustration of the architectural error. The UEFI lock pins &lt;em&gt;configuration&lt;/em&gt; (&lt;code&gt;VbsPolicy&lt;/code&gt;). It does not pin &lt;em&gt;implementation&lt;/em&gt; (&lt;code&gt;securekernel.exe&lt;/code&gt;, &lt;code&gt;hvix64.exe&lt;/code&gt;, &lt;code&gt;LsaIso.exe&lt;/code&gt;, &lt;code&gt;ci.dll&lt;/code&gt;). Downgrade the implementation and the configuration is still happy. From Windows&apos;s point of view, VBS is on, the lock is engaged, the configuration variable is in firmware, everything checks out. The components doing the actual work are simply not the ones the configuration was checked against. Nobody asked it to check.&lt;/p&gt;
&lt;h3&gt;6.4 ItsNotASecurityBoundary, revived&lt;/h3&gt;
&lt;p&gt;On May 14, 2024, Microsoft shipped KB5037771 for Windows 11 22H2 and 23H2 [@landau-itsnotasecurityboundary-repo]. The preview build had landed on April 23, 2024 as KB5036980. The fix closed a False File Immutability TOCTOU on &lt;code&gt;ci.dll&lt;/code&gt; that Gabriel Landau of Elastic Security Labs had disclosed in February [@elastic-ffi]. Landau&apos;s exploit, which he titled &lt;em&gt;ItsNotASecurityBoundary&lt;/em&gt;, used the FFI race to swap an Authenticode catalog mid-verification, getting an unsigned driver loaded with Microsoft&apos;s blessing.&lt;/p&gt;

A bug class identified by Gabriel Landau (Elastic Security Labs) in 2024. Windows treats files mapped as `SEC_IMAGE` as immutable while a view exists, but the kernel does not always honor that immutability across separate reads of the same file. A verifier that reads the file, then re-reads it after a working-set flush, can be served different bytes the second time. On Authenticode catalogs, this becomes a TOCTOU race that lets the attacker swap the catalog between the verifier&apos;s read and the loader&apos;s load.
&lt;p&gt;The October 26, 2024 SafeBreach follow-up [@safebreach-2024-oct], titled &lt;em&gt;An Update on Windows Downdate&lt;/em&gt;, combined the two. It used Windows Downdate to roll &lt;code&gt;ci.dll&lt;/code&gt; back to the pre-May build (&lt;code&gt;10.0.22621.1376&lt;/code&gt;) and re-enabled the FFI bypass on a fully patched Windows 11 23H2 machine [@thehackernews-downdate]. &lt;em&gt;The Hacker News&lt;/em&gt; confirmed the chain: &lt;em&gt;&quot;The DSE bypass is achieved by making use of the downgrade tool to replace the &apos;ci.dll&apos; library with an older version (10.0.22621.1376) to undo the patch put in place by Microsoft&quot;&lt;/em&gt; [@thehackernews-downdate]. The name of Landau&apos;s exploit became, in retrospect, the most pointed commentary on Microsoft&apos;s servicing policy that anyone has written.&lt;/p&gt;

ItsNotASecurityBoundary&apos;s name is an homage to MSRC&apos;s policy that &apos;Administrator-to-kernel is not a security boundary.&apos; -- Gabriel Landau, Elastic Security Labs [@landau-itsnotasecurityboundary-repo]
&lt;h3&gt;6.5 What Microsoft shipped, and when&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s response unfolded over roughly eleven months. Here is the cadence, anchored to the canonical KB and CVE pages.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Aug 7, 2024&lt;/td&gt;
&lt;td&gt;CVE-2024-21302 and CVE-2024-38202 published; Black Hat USA 2024 talk&lt;/td&gt;
&lt;td&gt;[@nvd-cve-2024-21302; @nvd-cve-2024-38202; @safebreach-2024-aug]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aug 11, 2024&lt;/td&gt;
&lt;td&gt;DEF CON 32 talk&lt;/td&gt;
&lt;td&gt;[@safebreach-2024-aug]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aug 13, 2024&lt;/td&gt;
&lt;td&gt;KB5042562: opt-in &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; revocation policy with optional UEFI lock, plus default-enabled boot-session CI policy on Win10 1507+&lt;/td&gt;
&lt;td&gt;[@kb5042562]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 8, 2024&lt;/td&gt;
&lt;td&gt;KB5044284: substantive code fix for CVE-2024-38202 in Windows 11 24H2 (OS Build 26100.2033); per-SKU equivalents on the same date&lt;/td&gt;
&lt;td&gt;[@kb5044284; @nvd-cve-2024-38202]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 26, 2024&lt;/td&gt;
&lt;td&gt;SafeBreach follow-up &quot;An Update on Windows Downdate&quot; -- ItsNotASecurityBoundary revival via &lt;code&gt;ci.dll&lt;/code&gt; downgrade&lt;/td&gt;
&lt;td&gt;[@safebreach-2024-oct; @thehackernews-downdate]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jul 8-10, 2025&lt;/td&gt;
&lt;td&gt;CVE-2024-21302 mitigations completed across Windows 10 1507, 1607, 1809, Windows Server 2016, and Windows Server 2018&lt;/td&gt;
&lt;td&gt;[@nvd-cve-2024-21302]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;KB5042562 is the more interesting of the two artifacts. It introduces two mechanisms.&lt;/p&gt;
&lt;p&gt;The first is an &lt;em&gt;opt-in&lt;/em&gt; &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; policy: an administrator copies the Microsoft-signed &lt;code&gt;.p7b&lt;/code&gt; file from &lt;code&gt;%windir%\System32\SecureBootUpdates\&lt;/code&gt; to the EFI System Partition&apos;s &lt;code&gt;\EFI\Microsoft\Boot\&lt;/code&gt; directory; on boot, Windows reads the policy and refuses to load any binary whose version is listed as revoked [@kb5042562].&lt;/p&gt;
&lt;p&gt;The second is a &lt;em&gt;default-enabled&lt;/em&gt; boot-session CI policy that ships to every Windows 10 1507+ device and, per the KB, &lt;em&gt;&quot;will be loaded during boot and the enforcement of this policy will prevent rollback of VBS system files during that boot session&quot;&lt;/em&gt; [@kb5042562]. On Windows 11 24H2 and Server 2022/23H2, DRTM (Dynamic Root of Trust for Measurement) binds the VBS-protected encryption keys to the policy version, so a downgraded boot does not unseal the keys.&lt;/p&gt;

A Microsoft-signed Code Integrity policy file shipped in KB5042562 that lists revoked versions of VBS system files (`securekernel.exe`, `hvix64.exe`/`hvax64.exe`, `LsaIso.exe`, `ci.dll`, and others). When deployed to the EFI System Partition with the optional UEFI lock, it survives reformats and binds version-revocation enforcement to a Microsoft signature in firmware-stored state.

A trusted-launch mechanism, available on Windows 11 24H2 and Server 2022/23H2, that uses CPU SMI / SKINIT instructions to establish a measured execution environment after the OS has begun booting. In KB5042562&apos;s context, DRTM binds Virtual Secure Mode&apos;s protected encryption keys to the version of the active CI policy, so a rolled-back boot session cannot unseal the keys.
&lt;p&gt;{`
// Simulate the construction of the WindowsDowndate config XML for a ci.dll downgrade.
// This shows the structure the tool consumes, not the action list it emits.
// Nothing here writes to a real system or runs a real attack.&lt;/p&gt;
&lt;p&gt;const configEntries = [
  {
    source: &quot;C:\\Windows\\WinSxS\\amd64_microsoft-windows-codeintegrity_31bf3856ad364e35_10.0.22621.1376_none\\ci.dll&quot;,
    destination: &quot;C:\\Windows\\System32\\ci.dll&quot;,
    component: &quot;Code Integrity (kernel DSE enforcement)&quot;,
    targetVersion: &quot;10.0.22621.1376&quot;,
    rationale: &quot;Pre-May-2024 build, before the FFI/ItsNotASecurityBoundary fix&quot;
  }
];&lt;/p&gt;
&lt;p&gt;function buildConfigXml(entries) {
  const lines = [&apos;&apos;, &apos;&apos;];
  for (const e of entries) {
    lines.push(
      &apos;  &amp;lt;UpdateFile source=&quot;&apos; + e.source + &apos;&quot;&apos;,
      &apos;              destination=&quot;&apos; + e.destination + &apos;&quot; /&amp;gt;&apos;
    );
  }
  lines.push(&apos;&apos;);
  return lines.join(&apos;\n&apos;);
}&lt;/p&gt;
&lt;p&gt;const xml = buildConfigXml(configEntries);
console.log(xml);&lt;/p&gt;
&lt;p&gt;// What the tool would emit into the crafted pending.xml on next boot:
console.log(&apos;\nResulting POQ verb (illustrative):&apos;);
console.log(&apos;  &amp;lt;HardlinkFile source=&quot;&apos; + configEntries[0].source + &apos;&quot;&apos;);
console.log(&apos;                destination=&quot;&apos; + configEntries[0].destination + &apos;&quot; /&amp;gt;&apos;);
`}&lt;/p&gt;
&lt;p&gt;The mitigation does not patch the primitive. It patches &lt;em&gt;the components Leviev demonstrated against&lt;/em&gt;, one at a time, with a Microsoft-signed list of historical hashes. That choice is deliberate, and the next two sections are about why.&lt;/p&gt;
&lt;h2&gt;7. Competing Approaches -- How Other Platforms Closed the Gap&lt;/h2&gt;
&lt;p&gt;Android made the opposite design decision in 2017. Apple did so in 2020. The TLS working group did so in 2018. The IETF SUIT working group did so in 2021. By the time Leviev presented at Black Hat USA 2024, every major adjacent platform had treated rollback as a primary threat and shipped a structural fix. Windows was the outlier.&lt;/p&gt;
&lt;h3&gt;Android Verified Boot 2.0: per-partition rollback indices&lt;/h3&gt;
&lt;p&gt;AVB stamps a 64-bit &lt;code&gt;rollback_index&lt;/code&gt; into the signed footer of each partition. On each successful boot of a partition image, the bootloader updates a per-slot stored maximum in TrustZone or in eMMC Replay Protected Memory Block storage. On the next boot, the bootloader refuses any image whose &lt;code&gt;rollback_index&lt;/code&gt; is below the stored maximum [@aosp-avb; @avb-readme].&lt;/p&gt;
&lt;p&gt;The check happens in firmware, before the kernel loads. There is no opt-in. There is no enterprise toggle. There is no operational risk warning about the lock being irreversible. The rollback index is a &lt;em&gt;structural&lt;/em&gt; part of the trust architecture, not a policy file that ships through the same update channel that an attacker would compromise.&lt;/p&gt;
&lt;h3&gt;Apple Sealed System Volume: a Merkle seal over the OS&lt;/h3&gt;
&lt;p&gt;Apple&apos;s Signed System Volume (Big Sur, November 2020) takes the Android approach and pushes it further. SSV computes a SHA-256 hash of every file in the system volume, builds a Merkle tree over those hashes, and signs the root with an Apple key [@apple-ssv].&lt;/p&gt;
&lt;p&gt;The Apple Platform Security guide describes it precisely: &lt;em&gt;&quot;SSV features a kernel mechanism that verifies the integrity of the system content at runtime and rejects any data -- code and noncode -- without a valid cryptographic signature from Apple&quot;&lt;/em&gt; and &lt;em&gt;&quot;Each SSV SHA-256 hash is stored in the main file-system metadata tree, which is itself hashed. Because each node of the tree recursively verifies the integrity of the hashes of its children -- similar to a binary hash (Merkle) tree -- the root node&apos;s hash value, called a seal, encompasses every byte of data in the SSV&quot;&lt;/em&gt; [@apple-ssv]. On iOS and iPadOS, &lt;em&gt;&quot;Users aren&apos;t allowed to turn off the protection of a signed system volume&quot;&lt;/em&gt; [@apple-ssv]. The check is structural and mandatory.&lt;/p&gt;
&lt;h3&gt;IETF SUIT: rollback in the IoT firmware threat model&lt;/h3&gt;
&lt;p&gt;RFC 9019 standardised the firmware-update threat model the IoT industry now treats as canonical. The document does not mince words: &lt;em&gt;&quot;The firmware image is authenticated and integrity protected. Attempts to flash a maliciously modified firmware image or an image from an unknown, untrusted source must be prevented&quot;&lt;/em&gt; [@rfc9019]. The Update Framework&apos;s CCS 2010 paper and its present-day specification share the same vocabulary: &lt;em&gt;&quot;Rollback attacks. An attacker presents files to a software update system that are older than those the client has already seen. With no way to tell it is an obsolete version that may contain vulnerabilities, the user installs the software&quot;&lt;/em&gt; [@tuf-security]. TUF, now a CNCF graduated project, is the academic-canonical reference [@tuf-spec].&lt;/p&gt;
&lt;h3&gt;TLS 1.3: rollback baked into the protocol&lt;/h3&gt;
&lt;p&gt;The protocol world&apos;s answer is in RFC 8446 section 4.1.3 [@rfc8446]. A TLS 1.3 server that detects a downgrade attempt -- a client that supports TLS 1.3 but is being routed through a man-in-the-middle that strips it back to TLS 1.2 -- writes a specific magic constant into the last 8 bytes of &lt;code&gt;ServerHello.random&lt;/code&gt;. A genuine TLS 1.3 client, completing the handshake, checks those bytes and aborts the connection if the magic is present. The integrity check is bound into the transcript hash, so a network attacker cannot rewrite it without breaking the handshake. The mitigation is part of the protocol, not a guideline operators can apply.&lt;/p&gt;

flowchart LR
    A[&quot;Android AVB 2.0&quot;] --&amp;gt; A1[&quot;TrustZone / RPMB&lt;br /&gt;per-slot rollback_index&quot;]
    B[&quot;Apple SSV&quot;] --&amp;gt; B1[&quot;Secure Enclave / T2&lt;br /&gt;signed Merkle root&quot;]
    C[&quot;IETF SUIT (RFC 9019)&quot;] --&amp;gt; C1[&quot;Spec-defined&lt;br /&gt;device-side state&quot;]
    D[&quot;TLS 1.3 (RFC 8446)&quot;] --&amp;gt; D1[&quot;In-protocol&lt;br /&gt;ServerHello.random bytes&quot;]
    E[&quot;TUF&quot;] --&amp;gt; E1[&quot;Snapshot + timestamp roles&lt;br /&gt;monotonic version numbers&quot;]
    F[&quot;Windows SkuSiPolicy.p7b&quot;] --&amp;gt; F1[&quot;EFI System Partition&lt;br /&gt;opt-in UEFI lock&quot;]
    F --&amp;gt; F2[&quot;Boot session only&lt;br /&gt;default-enabled CI policy&quot;]
&lt;p&gt;The differences between these designs are not cosmetic. They are decisions about &lt;em&gt;where the rollback state lives&lt;/em&gt; and &lt;em&gt;who is authorised to write it&lt;/em&gt;. Read the table below row by row and the contrast is uncomfortable.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Windows &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Android AVB 2.0&lt;/th&gt;
&lt;th&gt;Apple SSV&lt;/th&gt;
&lt;th&gt;IETF SUIT&lt;/th&gt;
&lt;th&gt;TUF&lt;/th&gt;
&lt;th&gt;TLS 1.3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Initiation&lt;/td&gt;
&lt;td&gt;Opt-in (strong) / default-enabled (boot-session)&lt;/td&gt;
&lt;td&gt;Mandatory&lt;/td&gt;
&lt;td&gt;Mandatory&lt;/td&gt;
&lt;td&gt;Adopter-defined&lt;/td&gt;
&lt;td&gt;Adopter-defined&lt;/td&gt;
&lt;td&gt;Mandatory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protected unit&lt;/td&gt;
&lt;td&gt;Per-component hash list&lt;/td&gt;
&lt;td&gt;Per-partition signed image&lt;/td&gt;
&lt;td&gt;Whole system volume&lt;/td&gt;
&lt;td&gt;Per-firmware image&lt;/td&gt;
&lt;td&gt;Per-package metadata&lt;/td&gt;
&lt;td&gt;Per-handshake nonce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version-state storage&lt;/td&gt;
&lt;td&gt;EFI System Partition + optional UEFI lock&lt;/td&gt;
&lt;td&gt;TEE / RPMB rollback index&lt;/td&gt;
&lt;td&gt;Secure Enclave / signed root&lt;/td&gt;
&lt;td&gt;Device-side spec&lt;/td&gt;
&lt;td&gt;Snapshot + timestamp roles&lt;/td&gt;
&lt;td&gt;In-protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware-root binding&lt;/td&gt;
&lt;td&gt;Optional via UEFI lock; DRTM on Win11 24H2+&lt;/td&gt;
&lt;td&gt;TrustZone / RPMB&lt;/td&gt;
&lt;td&gt;Apple silicon / T2&lt;/td&gt;
&lt;td&gt;Spec abstract&lt;/td&gt;
&lt;td&gt;Spec abstract&lt;/td&gt;
&lt;td&gt;None required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coverage gaps&lt;/td&gt;
&lt;td&gt;First-party components not in the policy still load&lt;/td&gt;
&lt;td&gt;Slots not covered by AVB&lt;/td&gt;
&lt;td&gt;None on iOS/iPadOS; SSV must remain on macOS&lt;/td&gt;
&lt;td&gt;Adopter-defined&lt;/td&gt;
&lt;td&gt;Out-of-spec metadata roles&lt;/td&gt;
&lt;td&gt;None within scope&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Every other major platform&apos;s modern update architecture treats rollback as a primary threat baked into the trust architecture. Windows treats it as a privilege-boundary question -- and the answer it picked, &quot;Administrator-to-kernel is not a security boundary,&quot; excludes the most common attacker.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If Apple, Google, and the IETF have all figured out the answer, why hasn&apos;t Microsoft?&lt;/p&gt;
&lt;h2&gt;8. Theoretical Limits -- &quot;Admin-to-Kernel Is Not a Boundary&quot;&lt;/h2&gt;
&lt;p&gt;Microsoft&apos;s answer is that they have no need to.&lt;/p&gt;
&lt;h3&gt;8.1 The Microsoft position&lt;/h3&gt;
&lt;p&gt;The Windows Security Servicing Criteria is the public document where Microsoft enumerates which Windows interfaces it treats as security boundaries [@msft-servicing-criteria]. The document defines the concept: a security boundary provides a logical separation between the code and data of security domains with different levels of trust, with kernel-mode versus user-mode as the canonical example.&lt;/p&gt;
&lt;p&gt;It then asks the servicing test: &lt;em&gt;&quot;Does the vulnerability violate the goal or intent of a security boundary or a security feature?&quot;&lt;/em&gt; [@msft-servicing-criteria]. If the answer is yes, Microsoft commits to ship a security update. If the answer is no, the issue can still be fixed -- in a quality update, in a refactor, in a future feature -- but it does not get a CVE and the standardised servicing cadence does not apply.&lt;/p&gt;
&lt;p&gt;The boundaries Microsoft enumerates in that document include network boundaries (machine-to-machine), kernel-mode-to-user-mode separation, hypervisor-to-VM separation, and several others. &lt;strong&gt;Administrator-to-kernel is not on the list.&lt;/strong&gt; By the document&apos;s own logic, an Administrator who reaches kernel code execution has not crossed a boundary, because the document does not declare a boundary there for them to cross. That is the policy position Landau&apos;s exploit title was named after.&lt;/p&gt;
&lt;p&gt;The two CVEs that &lt;em&gt;were&lt;/em&gt; assigned are the boundary-crossing parts of the chain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CVE-2024-21302&lt;/strong&gt; is the Secure Kernel Mode Elevation of Privilege (VTL0-to-VTL1) -- a downgrade-induced compromise of &lt;code&gt;securekernel.exe&lt;/code&gt; does cross a defined boundary, because the kernel-to-Secure-Kernel separation is on the list [@nvd-cve-2024-21302]. &lt;strong&gt;CVE-2024-38202&lt;/strong&gt; is the basic-user-to-Administrator elevation via the restore-point flow -- a basic user induced into authorising a system restore can be parked into a state that triggers a downgrade, which crosses the user-to-admin boundary [@nvd-cve-2024-38202]. Both CVEs were assigned and patched from August 7, 2024 onward; CVE-2024-38202 received its substantive code fix on October 8, 2024 [@kb5044284; @nvd-cve-2024-38202].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Microsoft assigned CVE-2024-21302 (Secure Kernel EoP) and CVE-2024-38202 (basic-user-induced restore-point EoP) on August 7, 2024 and patched both. What Microsoft has &lt;em&gt;not&lt;/em&gt; committed to fix as a security vulnerability is the underlying Downdate primitive itself -- the Administrator-context modification of &lt;code&gt;PoqexecCmdline&lt;/code&gt;. Per the Servicing Criteria, that primitive does not cross a declared boundary [@safebreach-2024-oct; @msft-servicing-criteria].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Leviev quoted Microsoft&apos;s framing of this distinction in his October 2024 follow-up:&lt;/p&gt;

CVE-2024-21302 was patched because it crossed a defined security boundary, the Windows Update takeover which was reported to Microsoft as well, has remained unpatched, as it did not cross a defined security boundary. Gaining kernel code execution as an Administrator is not considered as crossing a security boundary (not a vulnerability). -- Alon Leviev, summarising the Microsoft position, October 2024 [@safebreach-2024-oct]
&lt;h3&gt;8.2 The internal tension&lt;/h3&gt;
&lt;p&gt;That position has an obvious problem. The VBS documentation says VBS &lt;em&gt;assumes the kernel can be compromised&lt;/em&gt; [@msdocs-vbs]. The Secure Kernel exists because the NT kernel is, in the threat model VBS publishes, untrusted. If the kernel is the attacker, then anyone who can compromise the kernel is the attacker VBS is designed to mitigate. On a single-user Windows machine, the obvious path to kernel compromise is to be an Administrator and load a vulnerable signed driver, or to be an Administrator and exploit a kernel race, or to be an Administrator and downgrade &lt;code&gt;ci.dll&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Leviev makes the point directly in the same blog post: &lt;em&gt;&quot;the reason VBS was created is because the kernel is assumed compromised, and there was a need for a secure place to implement security features&quot;&lt;/em&gt; [@safebreach-2024-aug]. If the kernel is &lt;em&gt;assumed&lt;/em&gt; compromised in VBS&apos;s threat model, then the Administrator who can compromise the kernel is precisely the attacker VBS was built to mitigate -- which makes Microsoft&apos;s servicing-criteria position and VBS&apos;s threat model load-bearing on opposite sides of the same boundary.&lt;/p&gt;
&lt;p&gt;This is the article&apos;s most argumentative sentence: the position is not a &lt;em&gt;security&lt;/em&gt; decision (the primitive is not a vulnerability) but a &lt;em&gt;resourcing&lt;/em&gt; decision (we will not CVE the primitive, but we will harden it). The per-component &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; rollout, the default-enabled boot-session CI policy, DRTM on Win11 24H2+, and the multi-quarter cleanup across Windows 10 1507 through Windows Server 2018 are exactly what one would expect from an organisation patching a &lt;em&gt;class&lt;/em&gt; one component at a time, while declining to declare the class.&lt;/p&gt;
&lt;h3&gt;8.3 What a hardened position would look like&lt;/h3&gt;
&lt;p&gt;The fixes are not conceptually hard. Microsoft could:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Sign &lt;code&gt;poqexec.exe&lt;/code&gt; and &lt;code&gt;sfc.exe&lt;/code&gt; with HVCI-enforced integrity.&lt;/strong&gt; That removes the persistence and irreversibility steps of the chain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Move &lt;code&gt;PoqexecCmdline&lt;/code&gt; under a TrustedInstaller-only DACL with a UEFI-bound mirror.&lt;/strong&gt; That removes the registry pivot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Introduce a monotonic update-generation counter in the TPM, bound to a transcript hash of the cumulative-update history, consulted by the boot manager and the Secure Kernel.&lt;/strong&gt; That is the architectural fix -- the version of the answer Apple and Android shipped.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The current &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; mechanism is the &lt;em&gt;first step&lt;/em&gt; on the third path. It is per-component and opt-in for the strong variant, but the conceptual shape is right: a Microsoft-signed list of historical hashes, consulted by the kernel loader, with a UEFI-bound anchor for the strong configuration [@kb5042562]. The work is real, and it is well-executed within the constraints Microsoft has set itself. The question is whether the constraints will give. Whether the policy will eventually cover every Microsoft-shipped binary with a known EoP. Whether the strong variant will become the default. Whether Administrator-to-kernel will be declared a security boundary in the published criteria.&lt;/p&gt;

The one VBS configuration that Leviev has not bypassed is the &quot;Mandatory&quot; flag (the `HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Mandatory` REG_DWORD value, mirrored into the `VbsPolicy` UEFI variable on next boot). When set in combination with the UEFI lock, the flag causes boot failure if any VBS-protected binary is corrupted -- so the &quot;invalidate `securekernel.exe`, boot without VBS, downgrade `ci.dll`&quot; trick that defeats the ordinary UEFI lock no longer works. Leviev&apos;s October 2024 follow-up is blunt: *&quot;I have not found a way around this&quot;* [@safebreach-2024-oct].&lt;p&gt;The catches are operational. The Mandatory flag is not set by default when the UEFI lock is enabled; it has to be set manually. Once set with the UEFI lock, the VBS configuration cannot be modified -- the lock must be deleted via &lt;code&gt;SecConfig.efi&lt;/code&gt;, the flag set, and the lock re-enabled.&lt;/p&gt;
&lt;p&gt;There is also a real boot-reliability risk: any update that corrupts a VBS-protected binary on a Mandatory-flagged machine will brick the boot, with no error displayed beyond the firmware silently moving to the next boot option [@safebreach-2024-oct; @kb5042562]. Microsoft documented the Mandatory flag in September 2024, after Leviev&apos;s findings, but has not made it the default [@msdocs-vbs-hvci]. Few production machines run with it.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;These are not conceptually hard. They are organisationally hard, because they require Microsoft to ship a &lt;em&gt;new&lt;/em&gt; security boundary, declared as such, after declining to do so for nearly two decades.&lt;/p&gt;
&lt;h2&gt;9. Open Problems -- What the August 2024 Cadence Left Open&lt;/h2&gt;
&lt;p&gt;If you are reading this in 2026, the parts of Windows Downdate that Microsoft chose to call vulnerabilities have been patched. The parts they chose not to call vulnerabilities are exactly as exploitable today as they were on August 7, 2024.&lt;/p&gt;
&lt;p&gt;The general primitive remains. The Administrator-context modification of &lt;code&gt;PoqexecCmdline&lt;/code&gt; is, by Microsoft&apos;s stated policy, intentionally unpatched [@safebreach-2024-oct]. Every cumulative update Microsoft ships will continue to flow through the same servicing-stack path that Leviev hijacked, because that path is the path everything else depends on. The fix has to come from somewhere else.&lt;/p&gt;
&lt;h3&gt;Components not yet in the revocation policy&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; covers the VBS-protected components Leviev demonstrated against -- &lt;code&gt;securekernel.exe&lt;/code&gt;, &lt;code&gt;hvix64.exe&lt;/code&gt;, &lt;code&gt;hvax64.exe&lt;/code&gt;, &lt;code&gt;LsaIso.exe&lt;/code&gt;, &lt;code&gt;ci.dll&lt;/code&gt;, and others. It does not cover every Microsoft-shipped DLL or driver with a public EoP history [@kb5042562]. Each Microsoft binary outside the policy that has a known kernel-relevant vulnerability is, in principle, a Downdate target. The inventory is open. Microsoft does not publish &quot;the set of historical hashes of &lt;code&gt;ntoskrnl.exe&lt;/code&gt; that we consider unsafe to load&quot; -- and the absence of that list is the absence of the version-monotonicity boundary at scale.&lt;/p&gt;
&lt;h3&gt;Hot Patching as a parallel servicing surface&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/from-hotpatch-to-150-a-core-the-live-patch-pipeline-microsof/&quot; rel=&quot;noopener&quot;&gt;Hot Patching&lt;/a&gt; is the Windows servicing variant that applies code-level updates to running processes without a reboot. It reached general availability on Windows Server 2022 Datacenter: Azure Edition in February 2022 (Server Core) and July 2023 (Desktop Experience), and on Windows 11 Enterprise 24H2 in April 2025 via Microsoft Autopatch and Intune [@msdocs-hotpatch-server; @msdocs-hotpatch-win11].&lt;/p&gt;
&lt;p&gt;Its threat model is &lt;em&gt;forward-delta application&lt;/em&gt;: how do we apply a code patch to a running binary safely? It does not consider &lt;em&gt;rollback prevention&lt;/em&gt; as a separate concern. Whether the hot-patch path admits its own rollback primitive -- whether you can roll back a hot patch, restoring the older code into the running process, by abusing the hot-patch infrastructure the same way Downdate abuses CBS -- is an open question that nobody has publicly answered.&lt;/p&gt;
&lt;h3&gt;WinRE as adjacent surface&lt;/h3&gt;
&lt;p&gt;Leviev&apos;s Black Hat USA 2025 talk, with Netanel Ben Simon of Microsoft&apos;s MORSE team, did not extend Downdate. It went sideways. &lt;em&gt;BitUnlocker: Leveraging Windows Recovery to Extract BitLocker Secrets&lt;/em&gt; targeted the &lt;a href=&quot;https://paragmali.com/blog/the-day-85-million-devices-couldnt-boot----and-how-microsoft/&quot; rel=&quot;noopener&quot;&gt;Windows Recovery Environment&lt;/a&gt;, demonstrating four CVEs that together permit a physical-access attacker to extract BitLocker keys from the WinRE servicing surface [@itnews-bitunlocker; @infocondb-defcon33-bitunlocker]. The bugs were patched in the July 2025 Patch Tuesday cumulative updates.&lt;/p&gt;

The corrected CVE-to-file mapping, per the iTnews coverage and the independent garatc/BitUnlocker proof-of-concept, is:
- **CVE-2025-48804**: SDI / `Boot.sdi` parsing -- the boot-manager downgrade keystone that garatc&apos;s PoC chains to access BitLocker-encrypted disks in under five minutes on fully patched Windows 11 [@itnews-bitunlocker; @garatc-bitunlocker].
- **CVE-2025-48800**: Offline Scanning -- abuses the legitimately-signed time-travel debugger `tttracer.exe` to proxy-execute `cmd.exe` against BitLocker-encrypted volumes without triggering the recovery-mode re-lock.
- **CVE-2025-48003**: `SetupPlatform.exe` and the Shift+F10 path via `ReAgent.xml`.
- **CVE-2025-48818**: BCD-store complete-chain exploit.&lt;p&gt;BitUnlocker is not a continuation of Windows Downdate. It is the same meta-pattern in a different servicing surface: WinRE is a servicing path inherited from a less hostile era, and Microsoft is now patching its threat model. The shape of the work is identical.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Alon Leviev now works on the Microsoft Offensive Research and Security Engineering (MORSE) team alongside Netanel Ben Simon. Leviev&apos;s institutional follow-up to Downdate is therefore happening from inside Microsoft -- a notable signal about how seriously Microsoft is taking the surface even as it declines to declare the boundary [@infocondb-defcon33-bitunlocker].&lt;/p&gt;
&lt;h3&gt;Two revocation policies, one product&lt;/h3&gt;
&lt;p&gt;The Microsoft Vulnerable Driver Blocklist (for third-party kernel code) and &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; (for first-party VBS system files) are &lt;em&gt;two separate revocation policies&lt;/em&gt; that ship on different cadences, in different formats, and through different update channels. The Driver Blocklist updates quarterly and via monthly cumulative updates [@msdocs-driver-blocklist]; &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; is shipped as part of major mitigation rollouts and is opt-in for the strong variant [@kb5042562]. Whether Microsoft unifies them -- whether the eventual answer is one unified hash-revocation policy covering all kernel-loaded code, Microsoft-shipped or not -- is an open architectural question.&lt;/p&gt;
&lt;h3&gt;Linux desktop coverage&lt;/h3&gt;
&lt;p&gt;The image-based Linux distributions (Fedora Silverblue, Ubuntu Core, openSUSE MicroOS) have all moved toward AVB-style or SSV-style architectures. Classical &lt;code&gt;dpkg&lt;/code&gt;- and &lt;code&gt;rpm&lt;/code&gt;-based distributions have not. Apt&apos;s package authentication is signature-based and largely version-aware via the &lt;code&gt;Release&lt;/code&gt; file&apos;s &lt;code&gt;Date&lt;/code&gt; and &lt;code&gt;Valid-Until&lt;/code&gt; headers, but the security guarantees rely on a trusted repository and a TUF-style snapshot role that most distributions do not yet ship. The Windows lesson generalises: any update mechanism that can write into a higher-privilege domain has to enforce version monotonicity in the same domain that performs the write.&lt;/p&gt;
&lt;p&gt;There is one open problem the rest depend on. &lt;em&gt;Will Microsoft declare a version-monotonicity security boundary?&lt;/em&gt; The answer to that question -- whether by a future revision of the Windows Security Servicing Criteria, by a default-on Mandatory flag, by an exhaustive first-party Driver Block List equivalent, or by something nobody has prototyped yet -- is the substantive resolution of the story. The August 2024 patches were not it.&lt;/p&gt;
&lt;h2&gt;10. A Practical Guide for Defenders, Detection Engineers, and System Designers&lt;/h2&gt;
&lt;p&gt;Three audiences, three concrete tracks.&lt;/p&gt;
&lt;h3&gt;10.1 Defenders&lt;/h3&gt;
&lt;p&gt;If you operate Windows 10 or 11 endpoints, the first thing to do is apply the cumulative updates that contain the substantive code fix for CVE-2024-38202. On Windows 11 24H2, that is KB5044284 (October 8, 2024, OS Build 26100.2033) [@kb5044284]. On older SKUs, the equivalent updates landed on the same date or in the multi-quarter sweep that completed July 8-10, 2025 across Windows 10 1507, 1607, 1809, Server 2016, and Server 2018 [@nvd-cve-2024-21302]. Apply them in your normal patch ring with no special handling.&lt;/p&gt;
&lt;p&gt;The second thing to do is keep HVCI / memory integrity enabled so the default-enabled boot-session CI policy and the Vulnerable Driver Blocklist both fire [@msdocs-driver-blocklist; @msdocs-vbs-hvci]. On Windows 11 22H2 and later, both are on by default. On older SKUs, they are not, and the gap is meaningful.&lt;/p&gt;
&lt;p&gt;The third thing -- the one that requires care -- is to consider deploying &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; with the optional UEFI lock where your operational risk tolerance allows.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Microsoft&apos;s own KB is unambiguous about what happens if you apply the UEFI lock and later try to roll back: &lt;em&gt;&quot;If the UEFI lock is applied and the policy is removed or replaced with an older version, the Windows boot manager will not start, and the device will not start... Even reformatting the disk will not remove the UEFI lock of the mitigation if it has already been applied&quot;&lt;/em&gt; [@kb5042562]. Disable Secure Boot to remove the lock. Test on a pilot ring before deploying broadly. Back up BitLocker recovery keys first. Verify that the Windows Recovery Environment is updated to the latest Safe OS Dynamic Update (released July 8, 2025) before applying [@kb5042562].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A safe deployment plan: (1) pick a pilot ring of representative hardware; (2) verify each pilot machine has the July 8, 2025 WinRE Safe OS Dynamic Update applied; (3) back up BitLocker recovery keys to a secondary store you control; (4) deploy &lt;code&gt;SkuSiPolicy.p7b&lt;/code&gt; from &lt;code&gt;%windir%\System32\SecureBootUpdates\&lt;/code&gt; to the EFI System Partition&apos;s &lt;code&gt;\EFI\Microsoft\Boot\&lt;/code&gt; directory &lt;em&gt;without&lt;/em&gt; the UEFI lock first; (5) verify boot integrity for several reboot cycles; (6) apply the UEFI lock; (7) confirm no other CI policy is being shipped that conflicts; (8) graduate the policy to the wider fleet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally: the VBS &quot;Mandatory&quot; flag (&lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Mandatory&lt;/code&gt;) is the only configuration Leviev has not bypassed [@safebreach-2024-oct]. Set it where boot reliability is acceptable and you have rehearsed the recovery procedure (&lt;code&gt;SecConfig.efi&lt;/code&gt; to delete the lock, the flag set, the lock re-enabled). Inventory the count of Administrator accounts on each machine -- the Downdate primitive&apos;s blast radius is bounded by the number of identities that can write &lt;code&gt;PoqexecCmdline&lt;/code&gt;. Every reduction in that count is a direct reduction in the attack surface.&lt;/p&gt;
&lt;h3&gt;10.2 Detection engineers&lt;/h3&gt;
&lt;p&gt;The detection signature is the registry pivot. Authenticode-based file-integrity tooling will &lt;em&gt;not&lt;/em&gt; catch Windows Downdate, because the downgraded files are legitimately Microsoft-signed [@safebreach-2024-aug]. The Splunk Security Content &lt;code&gt;windows_downdate_registry_activity&lt;/code&gt; analytic is the canonical reference detection [@splunk-downdate-detection]. The logic is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consume Sysmon EventIDs 12, 13, and 14 (Registry creation, set-value, and rename).&lt;/li&gt;
&lt;li&gt;Match &lt;code&gt;TargetObject&lt;/code&gt; against &lt;code&gt;*PoqexecCmdline&lt;/code&gt; or &lt;code&gt;*COMPONENTS\PendingXmlIdentifier&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Suppress writes whose calling &lt;code&gt;ProcessPath&lt;/code&gt; is under &lt;code&gt;*:\Windows\WinSxS\*&lt;/code&gt; (the legitimate-update path).&lt;/li&gt;
&lt;li&gt;Map to MITRE ATT&amp;amp;CK T1112 (Modify Registry, Defense Impairment) and T1689 (Downgrade Attack, Persistence/Exploitation/Installation).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Splunk detection is &lt;em&gt;disabled by default&lt;/em&gt; in Splunk Enterprise Security. Operators must enable it explicitly [@splunk-downdate-detection]. The point of attack on the EDR side is the registry pivot, not the file substitution -- the substitution looks like a normal update because, by every check Windows performs, it is one.&lt;/p&gt;
&lt;p&gt;{`
// Demonstrates the decision logic of the windows_downdate_registry_activity detection.
// Not a real Sysmon parser -- the inputs would be Event 12/13/14 records in production.&lt;/p&gt;
&lt;p&gt;function isLegitimateUpdatePath(processPath) {
  // Legitimate Windows Update operations run under WinSxS.
  return processPath.toLowerCase().includes(&apos;\\windows\\winsxs\\&apos;);
}&lt;/p&gt;
&lt;p&gt;function classifyRegistryWrite(event) {
  const sensitiveValues = [
    &apos;PoqexecCmdline&apos;,
    &apos;COMPONENTS\\PendingXmlIdentifier&apos;
  ];&lt;/p&gt;
&lt;p&gt;  const matchesSensitive = sensitiveValues.some(v =&amp;gt;
    event.TargetObject.endsWith(v)
  );&lt;/p&gt;
&lt;p&gt;  if (!matchesSensitive) return &apos;ignore&apos;;
  if (isLegitimateUpdatePath(event.ProcessPath)) return &apos;allow&apos;;
  return &apos;alert&apos;;
}&lt;/p&gt;
&lt;p&gt;const sample = [
  { TargetObject: &apos;HKLM\\...\\PoqexecCmdline&apos;, ProcessPath: &apos;C:\\Windows\\WinSxS\\amd64_x\\TrustedInstaller.exe&apos; },
  { TargetObject: &apos;HKLM\\...\\PoqexecCmdline&apos;, ProcessPath: &apos;C:\\Users\\alice\\Desktop\\windows_downdate.exe&apos; },
  { TargetObject: &apos;HKLM\\...\\COMPONENTS\\PendingXmlIdentifier&apos;, ProcessPath: &apos;C:\\tools\\suspicious.exe&apos; }
];&lt;/p&gt;
&lt;p&gt;for (const ev of sample) {
  console.log(classifyRegistryWrite(ev), &apos;&amp;lt;-&apos;, ev.ProcessPath);
}
`}&lt;/p&gt;

The Splunk detection only watches the two registry pivots. A more aggressive variant also watches for unexpected `pending.xml` files appearing outside `C:\Windows\WinSxS\` and for file integrity changes against expected hashes of `poqexec.exe` and `sfc.exe`. The trade-off is false positives: enterprise patch tooling and configuration-management agents touch the servicing path more often than you would expect, and an aggressive variant requires tuning before it is fleet-ready.
&lt;h3&gt;10.3 System designers&lt;/h3&gt;
&lt;p&gt;If you are building a new operating system in 2026, you do not have an excuse. The architectural takeaway from Windows Downdate is general: &lt;em&gt;any update mechanism that can write into a higher-privilege domain must enforce version monotonicity in the same domain that performs the write.&lt;/em&gt; If the update is processed by a TrustedInstaller-context service, version monotonicity has to be enforced by TrustedInstaller (or by something even further from the attacker -- the boot manager, the firmware, the TPM). If the version-state pointer lives in a registry value an Administrator can rewrite, the boundary moves and the design is broken.&lt;/p&gt;
&lt;p&gt;Concrete reference patterns are available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TPM-stored generation counters.&lt;/strong&gt; Bind the counter to a transcript hash of the cumulative-update history; the boot manager and Secure Kernel refuse to load components older than the stored counter [@tuf-spec]. This is the Apple SSV / Android AVB pattern translated into Windows-shaped trust roots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monotonic catalog versions.&lt;/strong&gt; Sign the per-component catalog with an explicit &lt;code&gt;&amp;lt;min-version&amp;gt;&lt;/code&gt; field; the kernel loader refuses to load any catalog whose &lt;code&gt;&amp;lt;min-version&amp;gt;&lt;/code&gt; is below the loader&apos;s stored maximum. This is the TUF snapshot-role pattern [@tuf-spec].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In-protocol monotonicity at the update edge.&lt;/strong&gt; Use the IETF SUIT envelope format and the &lt;code&gt;seq-num&lt;/code&gt; field defined in RFC 9019 [@rfc9019]. The receiver tracks the highest sequence number it has seen and refuses lower ones at the spec level, not the policy level.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reference designs exist. TUF [@tuf-spec], IETF SUIT [@rfc9019], and AOSP AVB [@aosp-avb] are open. Pick one. Adapt it. Ship it. The hard part is not the engineering. The hard part is the institutional commitment to declaring a security boundary you did not declare last year.&lt;/p&gt;
&lt;p&gt;If you are building a new OS in 2026, you do not have an excuse.&lt;/p&gt;
&lt;h2&gt;11. Misconceptions, Corrections, and What Comes Next&lt;/h2&gt;
&lt;p&gt;Windows Downdate spawned a press cycle that got several things wrong. Here is what is actually true.&lt;/p&gt;

No. The substantive code fix for CVE-2024-38202 (the basic-user-induced restore-point variant) shipped on **October 8, 2024** as KB5044284 for Windows 11 24H2 and OS Build 26100.2033, with per-SKU equivalents on the same date [@kb5044284; @nvd-cve-2024-38202]. The earlier mitigation guidance, KB5042562 (the opt-in `SkuSiPolicy.p7b` policy and the default-enabled boot-session CI policy), shipped on August 13, 2024 [@kb5042562]. There is no November 2024 anchor. The multi-quarter cleanup across older SKUs completed July 8-10, 2025 [@nvd-cve-2024-21302].

No. *Secure Boot* bypass via downgrade is BlackLotus (CVE-2022-21894, &quot;Baton Drop&quot;), which used a vulnerable Microsoft-signed `bootmgfw.efi` that was not in the UEFI `dbx` revocation list [@welivesecurity-blacklotus]. Windows Downdate bypasses *VBS UEFI locks* and downgrades the VBS-protected components (`securekernel.exe`, `hvix64.exe`, `LsaIso.exe`, `ci.dll`) along with the NT kernel itself [@safebreach-2024-aug]. The two attacks operate at different layers: BlackLotus at the firmware/boot manager, Downdate at the OS component layer. The mechanism shape is similar; the targets are distinct.

No. *ItsNotASecurityBoundary* is Gabriel Landau&apos;s (Elastic Security Labs) exploit name for a False File Immutability TOCTOU on `ci.dll`, disclosed in early 2024 and patched in **May 14, 2024 (KB5037771)** with a preview build on April 23, 2024 (KB5036980) [@landau-itsnotasecurityboundary-repo; @elastic-ffi]. Leviev&apos;s October 26, 2024 SafeBreach follow-up *revived* Landau&apos;s exploit by using Windows Downdate to roll `ci.dll` back to the pre-May build [@safebreach-2024-oct]. Leviev&apos;s actual Black Hat USA 2025 talk, with Netanel Ben Simon, was **BitUnlocker**, on Windows Recovery Environment attacks -- patched in July 2025 Patch Tuesday [@itnews-bitunlocker; @infocondb-defcon33-bitunlocker].

No. Microsoft assigned and patched both CVEs from disclosure day (August 7, 2024) onward [@nvd-cve-2024-21302; @nvd-cve-2024-38202]. The position that Microsoft *has* taken is narrower: the underlying *primitive* (the Administrator-context modification of `PoqexecCmdline` that the Downdate technique relies on) has remained unpatched because it does not cross a defined security boundary in the published Windows Security Servicing Criteria [@safebreach-2024-oct; @msft-servicing-criteria]. The two assigned CVEs are the *boundary-crossing parts* of the chain. The unassigned primitive is the part that runs on each side of the boundary that nobody declared.

No. Authenticode does what it says: it verifies that a binary was signed by a specific certificate (and that the certificate chains to a trusted root). The older `ci.dll` that Windows Downdate installs is legitimately signed by Microsoft. Authenticode never claimed to assert &quot;and is the current version&quot; -- it is a *signature* check, not a *staleness* check [@safebreach-2024-aug; @msdocs-driver-signing]. The mistake is in the design above Authenticode that treats signature validity as equivalent to current-version-validity, not in Authenticode itself.

Per Leviev&apos;s claim in the August 2024 SafeBreach blog, `poqexec.exe` is not Authenticode-signed in the affected Windows builds, and neither is `sfc.exe` [@safebreach-2024-aug]. The persistence step of Windows Downdate exploits this directly: replacing `poqexec.exe` with a patched copy that NOPs future updates does not require a code-signing bypass. The hardening suggested in section 8 -- sign both binaries, enforce HVCI on them -- removes the persistence and irreversibility steps, but signing alone is not sufficient: the `PoqexecCmdline` registry pivot survives signing, because the pivot points the *legitimately signed* `poqexec.exe` at an attacker-chosen action list.

EDR coverage focuses on the known indicators: registry writes to `PoqexecCmdline` and `PendingXmlIdentifier` by processes that are not under `C:\Windows\WinSxS\`, and unexpected `pending.xml` files outside the TrustedInstaller-only directories [@splunk-downdate-detection]. The downgrade itself, once executed, is by design indistinguishable from a legitimate update at the file-signature layer. Whether a given EDR product has shipped a detection for the registry pivot is product-specific; the Splunk Security Content analytic referenced above is the cleanest published reference signature [@splunk-downdate-detection]. Microsoft Defender for Endpoint operators should verify with their account team that an equivalent detection is enabled in their tenant.
&lt;p&gt;Microsoft built a security boundary in 2007 that depended on the assumption that updates only move forward. We now know they do not. The work of declaring the new boundary -- one component, one SKU, one cumulative update at a time -- is what Microsoft is doing right now. Whether the boundary will eventually be declared in the Windows Security Servicing Criteria, rather than implemented quietly as a per-component policy, is the question whose answer we will know in 2027.&lt;/p&gt;
&lt;p&gt;For now, the lesson is portable. The shape of Windows Downdate generalises: every signed-update system needs a version-monotonicity primitive co-located with the trust root that enforces signatures. The reason it took until 2024 to demonstrate this on Windows is not that the bug was deep. It is that nobody, in eighteen years of Windows servicing engineering, had been asked to declare the boundary the bug crossed. Once Leviev asked the question, the answer wrote itself.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;windows-downdate-when-the-update-itself-is-the-attack&quot; keyTerms={[
  { term: &quot;TrustedInstaller&quot;, definition: &quot;A Windows security principal introduced in Vista that owns most system files. ACLs deny direct write to other principals, including Administrators.&quot; },
  { term: &quot;Component-Based Servicing (CBS)&quot;, definition: &quot;The Vista-era Windows servicing architecture that replaced self-extracting installers with manifest-and-catalog-driven transactions over a versioned component store (WinSxS).&quot; },
  { term: &quot;poqexec.exe&quot;, definition: &quot;The post-reboot Primitive Operations Queue executor. Reads pending.xml at next boot and applies its verbs (HardlinkFile, MoveFile, ...).&quot; },
  { term: &quot;PoqexecCmdline&quot;, definition: &quot;The Administrator-writable registry value that tells poqexec.exe which pending.xml to parse on next boot. The Windows Downdate primitive.&quot; },
  { term: &quot;SkuSiPolicy.p7b&quot;, definition: &quot;A Microsoft-signed Code Integrity policy that revokes specific versions of VBS system files. Shipped in KB5042562, August 2024.&quot; },
  { term: &quot;VBS UEFI lock&quot;, definition: &quot;A configuration mode for Virtualization-Based Security that pins the VBS configuration into a UEFI non-volatile variable. Pins configuration, not implementation.&quot; },
  { term: &quot;DRTM&quot;, definition: &quot;Dynamic Root of Trust for Measurement. On Win11 24H2+, binds Virtual Secure Mode protected keys to the active CI policy version.&quot; },
  { term: &quot;False File Immutability (FFI)&quot;, definition: &quot;Gabriel Landau&apos;s bug class. Windows treats SEC_IMAGE-mapped files as immutable, but the kernel does not consistently honor that immutability across separate reads -- enabling TOCTOU on catalogs.&quot; }
]} questions={[
  { q: &quot;Why does Leviev&apos;s attack succeed even though every binary Windows loads is legitimately Microsoft-signed?&quot;, a: &quot;Because Authenticode and the CBS catalog-trust model assert signature validity, not version currency. A 2022 ci.dll is signed by the same Microsoft key as a 2024 ci.dll.&quot; },
  { q: &quot;Which registry value is the pivot at the heart of Windows Downdate, and what is its ACL?&quot;, a: &quot;HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SideBySide\Configuration\PoqexecCmdline. The DACL allows Administrator write, breaking the TrustedInstaller-only chain of custody for the action list pointer.&quot; },
  { q: &quot;What does the VBS UEFI lock pin, and what does it not pin?&quot;, a: &quot;It pins VBS configuration (the VbsPolicy UEFI variable). It does not pin VBS implementation (securekernel.exe, hvix64.exe, LsaIso.exe, ci.dll). Downgrading the implementation while leaving the configuration alone is the attack.&quot; },
  { q: &quot;How is the rollback-defence story different on Android and Apple platforms?&quot;, a: &quot;Android AVB stores per-partition rollback indices in TrustZone or RPMB. Apple SSV signs a Merkle tree over the whole system volume. Both are structural and mandatory. Windows SkuSiPolicy.p7b is shipped via the same update channel as the components it protects, with an opt-in UEFI lock for the strong variant.&quot; },
  { q: &quot;Why does Microsoft consider the underlying Downdate primitive (modifying PoqexecCmdline) not a security vulnerability?&quot;, a: &quot;Because the Windows Security Servicing Criteria does not enumerate Administrator-to-kernel as a security boundary. Per Microsoft&apos;s published policy, gaining kernel code execution as an Administrator does not violate the goal or intent of any declared boundary, so the primitive falls outside the servicing-CVE process.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>downgrade-attack</category><category>vbs</category><category>hvci</category><category>windows-update</category><category>security-boundary</category><category>patch-management</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>Protected Process Light: When the Administrator Isn&apos;t Enough</title><link>https://paragmali.com/blog/protected-process-light-when-the-administrator-isnt-enough/</link><guid isPermaLink="true">https://paragmali.com/blog/protected-process-light-when-the-administrator-isnt-enough/</guid><description>How a single byte in EPROCESS encodes a signer lattice that denies SYSTEM-integrity admins the right to read LSASS -- and why every public bypass since 2018 attacks the same structural seam.</description><pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Windows Protected Process Light (PPL) re-asks the question of who can touch whom one level below the token model.** A single byte in `EPROCESS` packs a process&apos;s protection type, audit bit, and signer rung; the kernel&apos;s lattice check inside `NtOpenProcess` rejects memory-read attempts from below the target&apos;s rung even when the caller is SYSTEM with `SeDebugPrivilege` enabled. Every public bypass since 2018 lives in one structural class -- the kernel verifies the channel by which code enters a PPL, not the behaviour of that code once mapped -- which is why Microsoft classifies PPL as defense in depth rather than a security boundary, and why Credential Guard / `LsaIso.exe` is its necessary VBS-anchored companion.
&lt;h2&gt;1. Mimikatz on a Protected Box&lt;/h2&gt;
&lt;p&gt;A red team operator has done everything right. The shell is SYSTEM-integrity. &lt;code&gt;SeDebugPrivilege&lt;/code&gt; is enabled in the token. &lt;code&gt;whoami /priv&lt;/code&gt; shows every privilege Windows defines. The operator types &lt;code&gt;mimikatz.exe&lt;/code&gt;, then &lt;code&gt;privilege::debug&lt;/code&gt; -- &lt;em&gt;OK&lt;/em&gt;. Then &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; -- and Mimikatz answers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ERROR kuhl_m_sekurlsa_acquireLSA ; Handle on memory : (0x00000005) Access is denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mechanism that just denied them is not a privilege check at all. It is not an ACL decision. It is not the integrity-level mediator. itm4n recreated exactly this failure in 2021 against a vanilla Windows install with one registry value set [@itm4n-runasppl]. The error code &lt;code&gt;0x00000005&lt;/code&gt; is &lt;code&gt;ERROR_ACCESS_DENIED&lt;/code&gt; -- the Win32 surface that &lt;code&gt;GetLastError&lt;/code&gt; exposes for the kernel&apos;s NTSTATUS &lt;code&gt;STATUS_ACCESS_DENIED = 0xC0000022&lt;/code&gt;. The kernel returns the NTSTATUS out of &lt;code&gt;NtOpenProcess&lt;/code&gt; before the security descriptor of &lt;code&gt;lsass.exe&lt;/code&gt; has been consulted; &lt;code&gt;RtlNtStatusToDosError&lt;/code&gt; then maps it to the Win32 &lt;code&gt;0x5&lt;/code&gt; that surfaces in &lt;code&gt;kuhl_m_sekurlsa.c&lt;/code&gt;.&lt;/p&gt;

A kernel-enforced gating model that decorates a process with a *protection level* -- a structured byte combining a type field, an audit bit, and a signer rung -- and rejects `OpenProcess` requests from callers whose protection level is below the target&apos;s, regardless of token privileges or security-descriptor ACLs.
&lt;p&gt;Picture the scenario concretely. A 2026 red-team engagement against a hardened Windows 11 24H2 endpoint. &lt;code&gt;RunAsPPL&lt;/code&gt; audit-mode is on by default after the Windows 11 22H2 rollout extended audit-default to consumer SKUs [@learn-runasppl]. A third-party EDR daemon is already running, signed at the Antimalware rung via the vendor&apos;s Microsoft Virus Initiative enrollment. The operator owns local administrator. The operator has SYSTEM. The operator holds every privilege Windows defines. They still cannot read a single byte of LSASS memory.&lt;/p&gt;
&lt;p&gt;The denial trace, walked carefully, looks like this. Mimikatz calls &lt;code&gt;OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, lsass_pid)&lt;/code&gt;. The Win32 thunk lands on &lt;code&gt;NtOpenProcess&lt;/code&gt;, which dispatches to the object-manager callback &lt;code&gt;PspProcessOpen&lt;/code&gt;. That callback calls &lt;code&gt;PspCheckForInvalidAccessByProtection&lt;/code&gt;, which calls &lt;code&gt;RtlTestProtectedAccess&lt;/code&gt; against the caller&apos;s &lt;code&gt;EPROCESS.Protection&lt;/code&gt; byte and the target&apos;s &lt;code&gt;EPROCESS.Protection&lt;/code&gt; byte. The lattice test fails. The kernel strips &lt;code&gt;PROCESS_VM_READ&lt;/code&gt; from the requested mask. With the surviving limited mask, the request continues into &lt;code&gt;SeAccessCheck&lt;/code&gt;, but Mimikatz never wanted the limited mask; it wanted to read memory. The handle returned (or the failure path taken) gives Mimikatz exactly the path that produces &lt;code&gt;0x00000005&lt;/code&gt; in &lt;code&gt;kuhl_m_sekurlsa.c&lt;/code&gt;The relevant commit is &lt;code&gt;fe4e98405589e96ed6de5e05ce3c872f8108c0a0&lt;/code&gt;, cited by itm4n as the source for the exact failure path that yields &lt;code&gt;0x00000005&lt;/code&gt; [@mimikatz-sekurlsa]..&lt;/p&gt;

sequenceDiagram
    participant Mim as Mimikatz (SYSTEM, SeDebugPrivilege)
    participant K32 as kernel32 / OpenProcess
    participant NtOP as NtOpenProcess
    participant PsPO as PspProcessOpen
    participant CHK as PspCheckForInvalidAccessByProtection
    participant Lat as RtlTestProtectedAccess
    participant SAC as SeAccessCheck&lt;pre&gt;&lt;code&gt;Mim-&amp;gt;&amp;gt;K32: OpenProcess(PROCESS_VM_READ, lsass)
K32-&amp;gt;&amp;gt;NtOP: syscall NtOpenProcess
NtOP-&amp;gt;&amp;gt;PsPO: object-manager callback
PsPO-&amp;gt;&amp;gt;CHK: check caller.Protection vs target.Protection
CHK-&amp;gt;&amp;gt;Lat: lattice rule (signer rungs)
Lat--&amp;gt;&amp;gt;CHK: full mask denied
CHK--&amp;gt;&amp;gt;PsPO: strip PROCESS_VM_READ
PsPO-&amp;gt;&amp;gt;SAC: residual mask (limited only)
SAC--&amp;gt;&amp;gt;NtOP: limited handle (read denied)
NtOP--&amp;gt;&amp;gt;Mim: STATUS_ACCESS_DENIED (NTSTATUS 0xC0000022, Win32 GetLastError = 5)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If every privilege Windows defines is held by the caller, what is doing the denying? The answer is a kernel structure that the token model does not see and the security descriptor does not influence -- a byte in &lt;code&gt;EPROCESS&lt;/code&gt; named &lt;code&gt;Protection&lt;/code&gt;, mediating a lattice the access check consults &lt;em&gt;before&lt;/em&gt; it ever asks &lt;code&gt;SeAccessCheck&lt;/code&gt; about privileges.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is not a workaround pattern. It is a new dimension. The token model is unchanged. The integrity level is unchanged. The security descriptor on &lt;code&gt;lsass.exe&lt;/code&gt; is unchanged. What changed is that the kernel now answers a question it did not ask before: &lt;em&gt;what kind of trust does the caller have to manipulate the address space of the callee?&lt;/em&gt;&lt;/p&gt;

PPL re-asks the question of who can touch whom one level below the token model.
&lt;p&gt;That mechanism has a name (Protected Process Light), an encoding (a single &lt;code&gt;UCHAR&lt;/code&gt;), and a history that does not begin where you would expect. To understand the byte, we have to understand why Microsoft built it in the first place. The next section starts where the history starts: a 2006 Microsoft whitepaper about Hollywood.&lt;/p&gt;
&lt;h2&gt;2. Historical Origins -- Vista, DRM, and the First Protected Process&lt;/h2&gt;
&lt;p&gt;The kernel mechanism that today denies admins access to LSASS was invented in 2006 to keep Hollywood happy. The cover page of Microsoft&apos;s &lt;code&gt;process_vista.doc&lt;/code&gt; whitepaper opens with a sentence almost no one quotes today:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Microsoft Windows Vista operating system introduces a new type of process known as a protected process to enhance support for Digital Rights Management functionality in Windows Vista.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The whitepaper was published November 27, 2006, two months before Vista&apos;s GA, and it is the architectural seed of the byte we will be staring at for the rest of this article [@vista-process-doc]. The motivation was not credential theft. It was HD-DVD and Blu-ray content protection. Studio licensing agreements required that even an administrator on the local machine could not read the audio device graph isolation host&apos;s memory while protected content was playing. The Protected Media Path required a kernel-enforced barrier between admin user-mode and the media pipeline.&lt;/p&gt;

The Vista-era set of components that decrypt and render high-definition video and audio content under DRM. PMP requires kernel-enforced isolation of `audiodg.exe` and a small set of related processes so that local administrators cannot dump intermediate content keys from process memory.
&lt;p&gt;The Vista design was minimal. A single bit in &lt;code&gt;EPROCESS&lt;/code&gt; marks a process as protected. At &lt;code&gt;NtCreateUserProcess&lt;/code&gt;, the kernel parses the main image&apos;s Authenticode signature and looks for a specific Microsoft EKU OID that only the PMP signing root can issue [@forshaw-2018-10]. If the EKU is present and the chain resolves to that root, the kernel flips the bit. On every subsequent &lt;code&gt;NtOpenProcess&lt;/code&gt; against that process, the kernel strips a fixed set of access rights from the mask, no matter who is asking.&lt;/p&gt;
&lt;p&gt;Alex Ionescu, then a Windows internals researcher and now CrowdStrike&apos;s Chief Technology Innovation Officer, enumerated the denials in 2007 [@ionescu-pp-bad-idea]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A typical process cannot perform operations such as the following on a protected process: Inject a thread into a protected process; Access the virtual memory of a protected process; Debug an active protected process; Duplicate a handle from a protected process; Change the quota or working set of a protected process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Five denials. One bit. One certificate root. Ionescu&apos;s same essay, titled &quot;Why Protected Processes Are A Bad Idea,&quot; made a structural argument that aged well: putting a DRM mechanism in the kernel is a category error. The mechanism is too narrow for non-DRM use because the only certificate accepted is Microsoft&apos;s PMP signing root, and the only operations gated are the ones Hollywood cared about. Third parties cannot opt in, and Microsoft itself cannot graduate the level of trust.Ionescu&apos;s 2007 critique remains worth reading on its own merits. The argument that DRM-shaped kernel features tend to be reused for security mitigations and that this reuse changes their threat-model semantics is exactly what plays out over the next seven years [@ionescu-pp-bad-idea].&lt;/p&gt;
&lt;p&gt;The seven-year pause is its own story. Vista shipped, Vista was followed by Windows 7, and Windows 7 was followed by Windows 8 -- and through all of it, the access-check primitive that protects &lt;code&gt;audiodg.exe&lt;/code&gt; from administrators remained a DRM artefact. The primitive existed; the &lt;em&gt;graduated trust dimension&lt;/em&gt; did not. Two parallel failures pushed Microsoft toward widening the encoding.&lt;/p&gt;
&lt;p&gt;The first was Mimikatz. Benjamin Delpy&apos;s tool was first released in May 2011 and refined through 2013 [@mimikatz-wikipedia]; it made it trivial for an administrator to extract NTLM hashes and Kerberos session keys from &lt;code&gt;lsass.exe&lt;/code&gt;. The countermeasure of restricting &lt;code&gt;SeDebugPrivilege&lt;/code&gt; was useless; an attacker who has SYSTEM has every privilege. What Mimikatz exploited was a primitive gap: the kernel had no way to say &quot;lsass is protected against administrators but reachable from privileged Microsoft services.&quot;&lt;/p&gt;
&lt;p&gt;The second was Mateusz Jurczyk&apos;s CSRSS jailbreak of Windows 8 RT in 2013. Jurczyk (who writes as &lt;code&gt;j00ru&lt;/code&gt;) catalogued more than seventy Win32k system calls that the kernel guarded with the pattern &lt;code&gt;if (PsGetCurrentProcess() != gpepCsrss) return STATUS_ACCESS_DENIED;&lt;/code&gt; [@j00ru-1393]. That gating mechanism worked only as long as nobody could inject code into &lt;code&gt;csrss.exe&lt;/code&gt;. On Windows 8 RT, an attacker who could inject into &lt;code&gt;csrss.exe&lt;/code&gt; could bypass Microsoft&apos;s locked-down Surface RT shell. Ionescu later observed that &quot;In Windows 8.1 RT, this jailbreak is &apos;fixed&apos;, by virtue that code can no longer be injected into Csrss.exe for the attack&quot; [@ionescu-part2]. The fix made &lt;code&gt;csrss.exe&lt;/code&gt; a PPL at the &lt;code&gt;WinTcb&lt;/code&gt; rung, and the same machinery was generalised to &lt;code&gt;lsass.exe&lt;/code&gt; and the Antimalware tier.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Mimikatz proved Microsoft needed a graduated trust dimension for &lt;code&gt;lsass.exe&lt;/code&gt;. The j00ru CSRSS jailbreak proved Microsoft needed it for &lt;code&gt;csrss.exe&lt;/code&gt; too. The same widening of the encoding answered both.&lt;/p&gt;
&lt;/blockquote&gt;

flowchart LR
    subgraph Vista2006[Vista 2006 -- single bit]
        V1[EPROCESS protected = 0 or 1]
        V2[Certificate root: PMP only]
        V3[Access denials: hardcoded 5-tuple]
    end
    subgraph Win81[Windows 8.1 -- _PS_PROTECTION byte]
        W1[Type: 3 bits]
        W2[Audit: 1 bit]
        W3[Signer rung: 4 bits]
        W4[Certificate roots: per-EKU sub-OIDs]
        W5[Access denials: lattice over signer]
    end
    V1 --&amp;gt; W1
    V2 --&amp;gt; W4
    V3 --&amp;gt; W5

The DRM-to-credentials repurposing is not unique to PPL. The same pattern shows up in HVCI (originally a Hyper-V kernel-mode integrity feature, later repurposed for general code-integrity enforcement) and in Trustlets (originally an enterprise feature for Credential Guard, later generalised). Kernel mechanisms born in one threat model rarely stay confined to it.
&lt;p&gt;Microsoft already had the access-check primitive. What it didn&apos;t have, in 2007, was a way to ask &quot;how much trust does this process carry?&quot; The fix would not arrive until Windows 8.1 in October 2013, and when it arrived, it would fit in a single byte.&lt;/p&gt;
&lt;h2&gt;3. &lt;code&gt;_PS_PROTECTION&lt;/code&gt; -- The Single-Byte Encoding&lt;/h2&gt;
&lt;p&gt;The 8.1 fix is so compact it fits in a single byte. Ionescu&apos;s Part 1 of the &quot;Evolution of Protected Processes&quot; series, published November 22, 2013, gives the kernel structure verbatim [@ionescu-part1]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct _PS_PROTECTION {
    union {
        UCHAR Level;
        struct {
            UCHAR Type   : 3;
            UCHAR Audit  : 1;
            UCHAR Signer : 4;
        };
    };
} PS_PROTECTION, *PPS_PROTECTION;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three fields. One byte. The union with &lt;code&gt;Level:UCHAR&lt;/code&gt; exists so that two &lt;code&gt;_PS_PROTECTION&lt;/code&gt; values can be compared with a single byte load and a single byte compare. The kernel does this on every &lt;code&gt;NtOpenProcess&lt;/code&gt;. Speed matters; this is the hot path of the security model.&lt;/p&gt;

The kernel structure that encodes a process&apos;s protection state in eight bits: three bits of Type (`None`, `ProtectedLight`, `Protected`), one bit of Audit (intended as a forensic side-channel hint, although the exact runtime semantics are not enumerated in the public sources cited here), and four bits of Signer rung. Stored as `EPROCESS.Protection`.
&lt;p&gt;The Type field has three values. &lt;code&gt;PsProtectedTypeNone = 0&lt;/code&gt; marks a regular process. &lt;code&gt;PsProtectedTypeProtectedLight = 1&lt;/code&gt; marks a PPL -- the graduated path introduced in 8.1. &lt;code&gt;PsProtectedTypeProtected = 2&lt;/code&gt; marks a &quot;heavy&quot; Vista-style PP. Heavy PPs still exist; they retain the original DRM semantics where almost nothing from below the protection level may touch them. PPLs are the new general-purpose path where the &lt;em&gt;signer rung&lt;/em&gt; mediates a graduated lattice.&lt;/p&gt;
&lt;p&gt;The Audit bit is the least documented of the three fields. Ionescu Part 1 lists it as &lt;code&gt;Audit : Pos 3, 1 Bit&lt;/code&gt; with no semantic gloss; itm4n&apos;s RunAsPPL header annotates it as &lt;code&gt;// Reserved&lt;/code&gt;; Microsoft Learn enumerates CodeIntegrity events &lt;code&gt;3033&lt;/code&gt;, &lt;code&gt;3063&lt;/code&gt;, &lt;code&gt;3065&lt;/code&gt;, and &lt;code&gt;3066&lt;/code&gt;, but those are triggered by the &lt;code&gt;AuditLevel&lt;/code&gt; configuration under &lt;code&gt;Image File Execution Options\LSASS.exe&lt;/code&gt; and concern DLL-load failures, not per-process &lt;code&gt;OpenProcess&lt;/code&gt; denials [@ionescu-part1] [@itm4n-runasppl] [@learn-runasppl]. The field&apos;s name implies a forensic side-channel, and the bit-position is reserved; the precise runtime emission shape is not enumerated in the public sources cited here.&lt;/p&gt;
&lt;p&gt;The Signer field is the structurally interesting one. Ionescu&apos;s 2013 enumeration names eight values [@ionescu-part1]:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signer constant&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Used for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerNone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Non-protected (no rung)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerAuthenticode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Generic third-party Authenticode (early PPL guests)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerCodeGen&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;.NET native runtime code generators&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerAntimalware&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;EDR / AV daemons admitted via ELAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerLsa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lsass.exe&lt;/code&gt; under &lt;code&gt;RunAsPPL&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerWindows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Microsoft Windows components below TCB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerWinTcb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;csrss.exe&lt;/code&gt;, &lt;code&gt;smss.exe&lt;/code&gt;, &lt;code&gt;services.exe&lt;/code&gt; -- the inbox TCB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PsProtectedSignerMax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Sentinel value (enumeration upper bound)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Ionescu&apos;s 2013 list is the authoritative &lt;em&gt;baseline&lt;/em&gt; enumeration. It is not a permanent enumeration. By 2018, James Forshaw&apos;s PowerShell tooling (&lt;code&gt;NtApiDotNet&lt;/code&gt;) was enumerating an additional &lt;code&gt;App = 8&lt;/code&gt; signer used for AppContainer / TruePlay scenarios [@forshaw-2018-10]. Newer builds of Windows extend the enumeration further. The article will name &lt;code&gt;WinTcb&lt;/code&gt; (Microsoft&apos;s documented inbox-TCB rung) and &lt;code&gt;Antimalware&lt;/code&gt; (the only non-Microsoft-admissible rung) repeatedly, because they are the load-bearing ones. The intermediate values evolve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adjacent to &lt;code&gt;EPROCESS.Protection&lt;/code&gt; are two related fields, &lt;code&gt;EPROCESS.SignatureLevel&lt;/code&gt; and &lt;code&gt;EPROCESS.SectionSignatureLevel&lt;/code&gt;, which Ionescu introduces in Part 3 [@ionescu-part3]. These fields encode the &lt;em&gt;binary integrity&lt;/em&gt; the kernel demands at process creation and at every subsequent section load, and they are filled in from a 16-entry Signing Level table that runs from &lt;code&gt;Unchecked = 0&lt;/code&gt; up to &lt;code&gt;Windows TCB = 14&lt;/code&gt;. The Signer rung in &lt;code&gt;Protection&lt;/code&gt; answers &quot;what kind of trust does this process hold?&quot; The SignatureLevel pair answers &quot;what binaries is this process allowed to map?&quot; They are not the same question.&lt;/p&gt;
&lt;p&gt;Now the worked decode. Given the byte value &lt;code&gt;0x41&lt;/code&gt;, the encoding falls out by hand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low three bits (Type): &lt;code&gt;0x41 &amp;amp; 0x07 = 0x01&lt;/code&gt; -- &lt;code&gt;PsProtectedTypeProtectedLight&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bit 3 (Audit): &lt;code&gt;(0x41 &amp;gt;&amp;gt; 3) &amp;amp; 0x01 = 0&lt;/code&gt; -- Audit off.&lt;/li&gt;
&lt;li&gt;High four bits (Signer): &lt;code&gt;(0x41 &amp;gt;&amp;gt; 4) &amp;amp; 0x0F = 0x04&lt;/code&gt; -- &lt;code&gt;PsProtectedSignerLsa&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A process with &lt;code&gt;EPROCESS.Protection = 0x41&lt;/code&gt; is a PPL signed at the &lt;code&gt;Lsa&lt;/code&gt; rung. That is exactly what &lt;code&gt;lsass.exe&lt;/code&gt; looks like on a host with &lt;code&gt;RunAsPPL = 1&lt;/code&gt;. Ionescu&apos;s blog explicitly states: &quot;it&apos;s easy to read 0x41 as Lsa (0x4) + PPL (0x1)&quot; [@ionescu-part1]. The Defender service &lt;code&gt;MsMpEng.exe&lt;/code&gt;, signed at the Antimalware rung, has &lt;code&gt;Protection = 0x31&lt;/code&gt;. The session manager &lt;code&gt;csrss.exe&lt;/code&gt;, signed at WinTcb, has &lt;code&gt;Protection = 0x61&lt;/code&gt;.&lt;/p&gt;

flowchart TD
    B[byte: 8 bits]
    B --&amp;gt; F1[bits 0..2: Type]
    B --&amp;gt; F2[bit 3: Audit]
    B --&amp;gt; F3[bits 4..7: Signer]
    F1 --&amp;gt; T0[0 = None]
    F1 --&amp;gt; T1[1 = ProtectedLight PPL]
    F1 --&amp;gt; T2[2 = Protected PP]
    F3 --&amp;gt; S0[0 None]
    F3 --&amp;gt; S1[1 Authenticode]
    F3 --&amp;gt; S2[2 CodeGen]
    F3 --&amp;gt; S3[3 Antimalware]
    F3 --&amp;gt; S4[4 Lsa]
    F3 --&amp;gt; S5[5 Windows]
    F3 --&amp;gt; S6[6 WinTcb]
&lt;p&gt;{`
function decodeProtection(byteValue) {
  const type = byteValue &amp;amp; 0x07;
  const audit = (byteValue &amp;gt;&amp;gt; 3) &amp;amp; 0x01;
  const signer = (byteValue &amp;gt;&amp;gt; 4) &amp;amp; 0x0F;
  const typeNames = [&apos;None&apos;, &apos;ProtectedLight&apos;, &apos;Protected&apos;];
  const signerNames = [
    &apos;None&apos;, &apos;Authenticode&apos;, &apos;CodeGen&apos;, &apos;Antimalware&apos;,
    &apos;Lsa&apos;, &apos;Windows&apos;, &apos;WinTcb&apos;, &apos;Max&apos;
  ];
  return {
    raw: &apos;0x&apos; + byteValue.toString(16).padStart(2, &apos;0&apos;),
    type: typeNames[type] || &apos;unknown(&apos; + type + &apos;)&apos;,
    audit: audit ? &apos;on&apos; : &apos;off&apos;,
    signer: signerNames[signer] || &apos;unknown(&apos; + signer + &apos;)&apos;
  };
}&lt;/p&gt;
&lt;p&gt;// Worked examples from real Windows processes
console.log(&apos;MsMpEng.exe (Defender):&apos;, decodeProtection(0x31));
console.log(&apos;lsass.exe under RunAsPPL:&apos;, decodeProtection(0x41));
console.log(&apos;csrss.exe (WinTcb):&apos;, decodeProtection(0x61));
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; One byte, three fields, eight signer rungs. The kernel reads it on every &lt;code&gt;OpenProcess&lt;/code&gt;, before any token check, before any ACL evaluation. The encoding is the entire vocabulary the kernel has for asking &lt;em&gt;how trusted&lt;/em&gt; a process is.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The encoding tells the kernel &lt;em&gt;what kind&lt;/em&gt; of trust a process holds. It says nothing about &lt;em&gt;who can touch whom&lt;/em&gt; across rungs. That rule -- the lattice -- is the structure imposed on top of the bytes. The next section is the lattice.&lt;/p&gt;
&lt;h2&gt;4. The Signer Lattice -- Who Can Open Whom&lt;/h2&gt;
&lt;p&gt;itm4n&apos;s 2021 walkthrough states the three rules verbatim, and they have the rare quality of being short enough to memorise [@itm4n-scrt]:&lt;/p&gt;

A PP can open a PP or a PPL with full access if its signer type is greater or equal. A PPL can open a PPL with full access if its signer type is greater or equal. A PPL cannot open a PP with full access, regardless of its signer type.
&lt;p&gt;Three rules. They settle every cross-process access question PPL gates. Let us name them and then read off their consequences.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule 1.&lt;/strong&gt; A PP at signer $S_c$ may open with full access a PP or PPL at signer $S_t$ if and only if $S_c \ge S_t$.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule 2.&lt;/strong&gt; A PPL at signer $S_c$ may open with full access a PPL at signer $S_t$ if and only if $S_c \ge S_t$.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule 3.&lt;/strong&gt; A PPL cannot open a PP with full access, regardless of signer.&lt;/p&gt;
&lt;p&gt;The qualifier &quot;with full access&quot; is load-bearing. PPL&apos;s lattice gates the &lt;em&gt;full&lt;/em&gt; mask -- &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;, &lt;code&gt;PROCESS_VM_WRITE&lt;/code&gt;, &lt;code&gt;PROCESS_CREATE_THREAD&lt;/code&gt;, &lt;code&gt;PROCESS_DUP_HANDLE&lt;/code&gt;, &lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt;. A separate &lt;em&gt;limited&lt;/em&gt; mask (&lt;code&gt;SYNCHRONIZE&lt;/code&gt;, &lt;code&gt;PROCESS_QUERY_LIMITED_INFORMATION&lt;/code&gt;, &lt;code&gt;PROCESS_SET_LIMITED_INFORMATION&lt;/code&gt;, &lt;code&gt;PROCESS_SUSPEND_RESUME&lt;/code&gt;, and -- for callers below the &lt;code&gt;Authenticode&lt;/code&gt;/&lt;code&gt;CodeGen&lt;/code&gt;/&lt;code&gt;Windows&lt;/code&gt; tier -- &lt;code&gt;PROCESS_TERMINATE&lt;/code&gt;) is allowed when the security descriptor permits. The tier matters. Ionescu&apos;s verbatim &lt;code&gt;RtlProtectedAccess[]&lt;/code&gt; table widens the deny mask from &lt;code&gt;0xFC7FE&lt;/code&gt; to &lt;code&gt;0xFC7FF&lt;/code&gt; at the &lt;code&gt;Antimalware&lt;/code&gt;, &lt;code&gt;Lsa&lt;/code&gt;, and &lt;code&gt;WinTcb&lt;/code&gt; rungs -- one extra bit, bit 0, which is &lt;code&gt;PROCESS_TERMINATE&lt;/code&gt; [@ionescu-part2]. So an administrator can still call &lt;code&gt;OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, ...)&lt;/code&gt; against a protected &lt;code&gt;lsass.exe&lt;/code&gt; to enumerate threads, but cannot terminate a &lt;code&gt;PPL/Antimalware&lt;/code&gt;, &lt;code&gt;PPL/Lsa&lt;/code&gt;, or &lt;code&gt;PPL/WinTcb&lt;/code&gt; daemon via a direct kill. The lattice does not lock the process; it locks the &lt;em&gt;interesting&lt;/em&gt; access, and for the top-tier rungs it also locks the kill.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Caller signer \ Target signer&lt;/th&gt;
&lt;th&gt;None&lt;/th&gt;
&lt;th&gt;Authenticode (1)&lt;/th&gt;
&lt;th&gt;Antimalware (3)&lt;/th&gt;
&lt;th&gt;Lsa (4)&lt;/th&gt;
&lt;th&gt;Windows (5)&lt;/th&gt;
&lt;th&gt;WinTcb (6)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;None (admin, integrity SYSTEM)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Authenticode (1)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Antimalware (3)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Lsa (4)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Windows (5)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/WinTcb (6)&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Where &quot;denied&quot; means the &lt;em&gt;full&lt;/em&gt; mask is rejected; the limited mask continues to apply per the target&apos;s security descriptor.&lt;/p&gt;

flowchart BT
    None[None / unprotected]
    Auth[Authenticode]
    CG[CodeGen]
    AM[Antimalware]
    Lsa[Lsa]
    Win[Windows]
    Tcb[WinTcb]
    None --&amp;gt; Auth
    Auth --&amp;gt; CG
    CG --&amp;gt; AM
    AM --&amp;gt; Lsa
    Lsa --&amp;gt; Win
    Win --&amp;gt; Tcb
&lt;p&gt;The Enhanced Key Usage side of the design holds the lattice together. Microsoft&apos;s EKU OID arc &lt;code&gt;1.3.6.1.4.1.311.10.3.*&lt;/code&gt; defines sub-OIDs per signer rung [@iana-pen311] [@oid-base-eku-arc], and at process creation the kernel parses the main image&apos;s Authenticode signature and walks its EKU extensions to determine which rung the binary is entitled to claim. If the certificate chain resolves cleanly to a Microsoft-issued root &lt;em&gt;and&lt;/em&gt; carries the rung&apos;s sub-OID, the kernel records the rung. Otherwise the process either starts unprotected or refuses to start at all.&lt;/p&gt;

An X.509 v3 certificate extension that asserts what specific purposes a certificate is allowed to certify. Microsoft uses sub-OIDs under `1.3.6.1.4.1.311.10.3.*` to encode protected-process signer rungs as EKU values [@iana-pen311] [@oid-base-eku-arc]. The kernel checks the EKU at process creation; the certificate chain anchors which Microsoft-issued sub-CA may issue at each rung.The IANA Private Enterprise Number `311` is registered to Microsoft under the PEN prefix `1.3.6.1.4.1.` [@iana-pen311], so `1.3.6.1.4.1.311.*` is the catch-all namespace for Microsoft-specific X.509 extensions; the `10.3.*` arc within it is the Microsoft Enhanced Key Usage (purpose) sub-tree [@oid-base-eku-arc], and `10.3.` slots map to specific signer purposes including protected-process rungs.
&lt;p&gt;The most important property of this design is the resolution point. The kernel parses the EKU exactly once, at &lt;code&gt;NtCreateUserProcess&lt;/code&gt;. It stores the resulting rung in &lt;code&gt;EPROCESS.Protection&lt;/code&gt;. On every subsequent &lt;code&gt;OpenProcess&lt;/code&gt; against that process, the kernel consults the byte, not the certificate. This makes the access check fast (one byte load, one byte compare) and decouples policy at runtime from policy at signing time. It also creates the structural seam that every public bypass since 2018 has exploited, because the kernel&apos;s confidence in the byte is exactly the confidence it had in the certificate at process-create time, projected forward indefinitely.&lt;/p&gt;
&lt;p&gt;Ionescu&apos;s Part 2 names the implementation directly. The lattice is not code; it is a data table named &lt;code&gt;RtlProtectedAccess[]&lt;/code&gt; baked into &lt;code&gt;ntoskrnl.exe&lt;/code&gt; [@ionescu-part2]. Each row of that table corresponds to a (signer, target-type) pair and encodes which access bits are allowed in the full mask. The relevant runtime routines are &lt;code&gt;PspProcessOpen&lt;/code&gt; and &lt;code&gt;PspThreadOpen&lt;/code&gt; (the object-manager open callbacks), &lt;code&gt;PspCheckForInvalidAccessByProtection&lt;/code&gt; (which performs the check), &lt;code&gt;RtlTestProtectedAccess&lt;/code&gt; (which applies the lattice row), and &lt;code&gt;RtlValidProtectionLevel&lt;/code&gt; (which sanity-checks the encoded byte for consistency).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The decision of who can touch whom is encoded in a table inside &lt;code&gt;ntoskrnl.exe&lt;/code&gt;. Changing the lattice means changing a table; widening or narrowing it does not require new code. This is why Microsoft can add &lt;code&gt;App = 8&lt;/code&gt; to the enumeration over time without touching the access-check routine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note one symmetry that becomes important later. &quot;Greater or equal&quot; means that within a rung, every PPL can read every other PPL. Two co-resident &lt;code&gt;PPL/Antimalware&lt;/code&gt; daemons -- Microsoft Defender&apos;s &lt;code&gt;MsMpEng.exe&lt;/code&gt; and a third-party EDR&apos;s agent -- can call &lt;code&gt;PROCESS_VM_READ&lt;/code&gt; on each other. Within-rung peers leak to each other by design. The lattice prevents &lt;em&gt;escalation&lt;/em&gt;, not &lt;em&gt;peer access&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The lattice settles the rule. The next question is admission: who decides which binaries are allowed to claim the Antimalware rung, and how does Microsoft admit third-party code into it at all? The answer is a driver.&lt;/p&gt;
&lt;h2&gt;5. The Antimalware Rung -- ELAM and Third-Party Code at PPL&lt;/h2&gt;
&lt;p&gt;PPL is interesting only if it admits non-Microsoft code at &lt;em&gt;some&lt;/em&gt; rung. The Vista PP design admitted nobody; it required a Microsoft PMP root certificate, full stop. PPL inherited that constraint at every rung except one. The Antimalware rung -- signer value &lt;code&gt;3&lt;/code&gt; -- is the only rung where third-party vendors can ship their own user-mode binaries as protected processes. The admission mechanism is the Early Launch Anti-Malware driver.&lt;/p&gt;

A specially signed Microsoft-certified kernel driver shipped by an anti-malware vendor that loads before any other boot-start driver. The ELAM driver participates in trusted-boot measurement, vouches for follow-on drivers, and -- critical to PPL -- carries an embedded resource section enumerating the vendor&apos;s user-mode signing certificate hashes. The kernel uses that resource section to admit the vendor&apos;s user-mode daemon binaries to `PPL/Antimalware` at service start.
&lt;p&gt;Microsoft Learn&apos;s &quot;Protecting Anti-Malware Services&quot; page describes the boot-time admission flow in two sentences [@learn-am-services]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The driver must have an embedded resource section containing the information of the certificates used to sign the user mode service binaries. During the boot process, this resource section will be extracted from the ELAM driver to validate the certificate information and register the anti-malware service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two consequences. First, the third-party signer set is bounded by a &lt;em&gt;kernel-readable resource section&lt;/em&gt;, not by an open EKU. Microsoft, not the vendor, controls which user-mode binaries are admissible. Second, the certificate hashes are baked into the driver at signing time and re-validated at every service start. A vendor cannot widen the admissible set after the fact; an attacker cannot drop in their own user-mode binary unless its hash is already listed.&lt;/p&gt;
&lt;p&gt;The gate that decides which vendors get ELAM drivers in the first place is the Microsoft Virus Initiative. Microsoft Learn&apos;s MVI criteria page enumerates the requirement explicitly [@learn-mvi]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your security solution must be certified within the last 12 months by at least one of the organizations listed below: AV-Comparatives, AVLab Cybersecurity Foundation, AV-Test, MRG Effitas, SE Labs, SKD Labs, VB 100, West Coast Labs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The same page requires &quot;use of Trusted Signing,&quot; Microsoft&apos;s cloud-managed code signing service. The implications are operational. To ship code at &lt;code&gt;PPL/Antimalware&lt;/code&gt;, a vendor must (a) hold MVI membership, (b) maintain independent-lab certification, (c) author an ELAM driver, (d) get the driver through Microsoft WHQL and have it Microsoft co-signed, and (e) embed the user-mode certificate hashes in the driver&apos;s resource section.&lt;/p&gt;

A Microsoft program for anti-malware vendors that gates access to ELAM driver signing and to specific Defender APIs. Membership requires independent-lab certification (renewed annually) and Trusted Signing usage; in practical terms, MVI membership is the entry ticket to deploying user-mode binaries at `PPL/Antimalware`.

The implication of MVI is that an indie security tool, however technically sound, cannot deploy as `PPL/Antimalware`. The gate is not technical but commercial: independent-lab certification fees, annual renewals, and the engineering investment of building a production-grade ELAM driver. The signer rung is *signed*; the signing program is *gated*.

sequenceDiagram
    participant BM as Boot manager
    participant K as Windows kernel
    participant ELAM as Vendor ELAM driver (.sys)
    participant SCM as Service Control Manager
    participant CI as ci.dll (CodeIntegrity)
    participant Svc as Vendor service (e.g. EDR daemon)
    BM-&amp;gt;&amp;gt;K: load boot drivers
    K-&amp;gt;&amp;gt;ELAM: load ELAM driver early
    K-&amp;gt;&amp;gt;ELAM: read embedded ELAM resource section
    K-&amp;gt;&amp;gt;K: cache vendor user-mode cert hashes
    Note over K,SCM: Boot continues, OS initialises
    SCM-&amp;gt;&amp;gt;Svc: start vendor service
    Svc-&amp;gt;&amp;gt;CI: validate service binary signature
    CI-&amp;gt;&amp;gt;K: lookup vendor cert against cached hashes
    K--&amp;gt;&amp;gt;CI: match -- admit at PPL/Antimalware
    CI--&amp;gt;&amp;gt;Svc: launch as PPL/Antimalware (Protection = 0x31)
&lt;p&gt;By 2024, every major commercial EDR ships through this path. Microsoft Defender&apos;s &lt;code&gt;MsMpEng.exe&lt;/code&gt; uses the inbox &lt;code&gt;WdBoot.sys&lt;/code&gt; ELAM driver&lt;code&gt;WdBoot.sys&lt;/code&gt; (&quot;Windows Defender Boot Driver&quot;) is Microsoft&apos;s inbox first-party ELAM driver; it ships in every Windows install and is loaded before any third-party ELAM driver. The canonical reference implementation of the ELAM resource-section pattern is Microsoft&apos;s &lt;code&gt;Windows-driver-samples/security/elam&lt;/code&gt; repository [@ms-elam-sample], which also documents the Early Launch EKU &lt;code&gt;1.3.6.1.4.1.311.61.4.1&lt;/code&gt; verbatim.. Third-party members of Microsoft&apos;s Virus Initiative -- the cohort gated by the MVI criteria quoted above [@learn-mvi] -- ship their own vendor ELAM drivers and run their main user-mode daemons at &lt;code&gt;PPL/Antimalware&lt;/code&gt;. Microsoft Learn&apos;s &quot;Early Launch Antimalware&quot; page is the canonical confirmation [@learn-elam]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because an ELAM service runs as a PPL (Protected Process Light), you need to debug using a kernel debugger.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One Microsoft-signed sentence and a billion endpoints. EDR vendors get protection against administrator-level tampering for free, on top of the kernel telemetry their drivers already collect. Microsoft gets a viable third-party security market without widening the EKU gates beyond a controllable set of vendors.&lt;/p&gt;
&lt;p&gt;ELAM admits the &lt;em&gt;daemon&lt;/em&gt;. The next operational question is what Microsoft does for &lt;code&gt;lsass.exe&lt;/code&gt; itself -- the canonical credential store, the original Mimikatz target. The mechanism is called &lt;code&gt;RunAsPPL&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;6. RunAsPPL -- Hardening LSASS&lt;/h2&gt;
&lt;p&gt;The registry value that produced the Mimikatz failure in Section 1 is a single DWORD. itm4n&apos;s walkthrough names it verbatim [@itm4n-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Open the key &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa&lt;/code&gt;; add the DWORD value &lt;code&gt;RunAsPPL&lt;/code&gt; and set it to 1; reboot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After reboot, &lt;code&gt;lsass.exe&lt;/code&gt; launches at &lt;code&gt;PPL/Lsa&lt;/code&gt;, signer rung 4, protection byte &lt;code&gt;0x41&lt;/code&gt;. Mimikatz running with full SYSTEM-integrity and &lt;code&gt;SeDebugPrivilege&lt;/code&gt; then receives &lt;code&gt;0x00000005&lt;/code&gt; on &lt;code&gt;OpenProcess(PROCESS_VM_READ, lsass.exe)&lt;/code&gt;. The registry knob is one DWORD; the consequences are large.&lt;/p&gt;

The Windows user-mode process that holds NTLM password hashes, Kerberos Ticket Granting Tickets, MSV1_0 credential caches, DPAPI master keys, and (on legacy builds before Microsoft&apos;s 2014 KB2871997 update [@ms-kb2871997]) WDigest plaintext passwords. The canonical target of credential-theft tooling since 2011.
&lt;p&gt;The threat being mitigated is simple. Mimikatz reads LSASS memory via &lt;code&gt;OpenProcess(PROCESS_VM_READ, lsass.exe)&lt;/code&gt;, walks the internal key-store structures, and extracts NTLM hashes, Kerberos session keys, and (on older configurations) cached plaintext. Restricting &lt;code&gt;SeDebugPrivilege&lt;/code&gt; does not work, because an attacker with SYSTEM has every privilege. Restricting the security descriptor on &lt;code&gt;lsass.exe&lt;/code&gt; does not work either, because legitimate services need to interact with it. PPL is the right primitive: it gates the &lt;em&gt;full&lt;/em&gt; mask irrespective of token state, and the kernel admits only Microsoft-signed code into the &lt;code&gt;Lsa&lt;/code&gt; rung.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RunAsPPL = 1&lt;/code&gt; is the stronger form of the setting on Secure Boot-capable machines. On the next boot, the kernel automatically mirrors the policy into a Secure Boot-anchored UEFI variable; once set, the protection survives registry rollback. An attacker who removes the registry key finds that LSASS still launches as PPL on the next boot. The only path to remove the protection is to disable Secure Boot at the firmware level, which requires physical access and which trips other defences. Microsoft Learn&apos;s documentation describes it verbatim [@learn-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can achieve further protection when you use Unified Extensible Firmware Interface (UEFI) lock and Secure Boot. When these settings are enabled, disabling the &lt;code&gt;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa&lt;/code&gt; registry key has no effect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is &lt;code&gt;RunAsPPL = 1&lt;/code&gt;. For environments that need admin-removable protection without the UEFI lock, &lt;code&gt;RunAsPPL = 2&lt;/code&gt; (available on Win11 22H2 and later) omits the UEFI variable. The policy lives in the registry only and is removable by any administrator (or by malware running as administrator) who simply deletes the registry value before reboot.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;RunAsPPL&lt;/code&gt; value&lt;/th&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;th&gt;Removable by?&lt;/th&gt;
&lt;th&gt;Persistence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt; (or absent)&lt;/td&gt;
&lt;td&gt;LSASS runs unprotected&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LSASS runs as PPL/Lsa; policy mirrored to UEFI variable on Secure Boot machines&lt;/td&gt;
&lt;td&gt;Physical access + Secure Boot disable&lt;/td&gt;
&lt;td&gt;Firmware-anchored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LSASS runs as PPL/Lsa; registry only (Win11 22H2+ only)&lt;/td&gt;
&lt;td&gt;Any admin who deletes the key&lt;/td&gt;
&lt;td&gt;Registry only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;RunAsPPL = 1&lt;/code&gt; setting is the practical answer to &quot;what stops an attacker who is willing to reboot?&quot; Once the UEFI variable is set, neither registry rollback nor PE-based offline attacks on the registry hive can disable LSA protection on the next boot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The deployment cost of &lt;code&gt;RunAsPPL&lt;/code&gt; is compatibility with third-party authentication modules. LSASS hosts a set of plug-ins: smart-card middleware, third-party Cryptographic Service Providers (CSPs), password-filter DLLs, alternative authentication packages. Under &lt;code&gt;RunAsPPL&lt;/code&gt;, the kernel demands that every DLL loaded into LSASS be Microsoft-signed at the LSA level (signer rung 4). Vendor DLLs that lack the right EKU are rejected at section creation. The rejections surface as CodeIntegrity events in the system event log. Microsoft Learn enumerates the two relevant event IDs [@learn-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Event 3065 occurs when a code integrity check determines that a process, usually LSASS.exe, attempts to load a driver that doesn&apos;t meet the security requirements for shared sections.&lt;/p&gt;
&lt;p&gt;Event 3066 occurs when a code integrity check determines that a process, usually LSASS.exe, attempts to load a driver that doesn&apos;t meet the Microsoft signing level requirements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is why Microsoft recommends running the setting in &lt;em&gt;audit mode&lt;/em&gt; before enforcement. Audit mode is enabled by setting a separate &lt;code&gt;AuditLevel&lt;/code&gt; DWORD to &lt;code&gt;8&lt;/code&gt;, but -- critically -- under a &lt;em&gt;different&lt;/em&gt; registry key from the one that hosts &lt;code&gt;RunAsPPL&lt;/code&gt;. Microsoft Learn places &lt;code&gt;AuditLevel&lt;/code&gt; under the Image File Execution Options hive for &lt;code&gt;LSASS.exe&lt;/code&gt; and names the path verbatim [@learn-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Open the Registry Editor, or enter RegEdit.exe in the Run dialog, and then go to the &lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\LSASS.exe&lt;/code&gt; registry key. Open the &lt;code&gt;AuditLevel&lt;/code&gt; value. Set its data type to &lt;code&gt;dword&lt;/code&gt; and its data value to &lt;code&gt;00000008&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;RunAsPPL&lt;/code&gt; sits under &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa&lt;/code&gt;. &lt;code&gt;AuditLevel = 8&lt;/code&gt; sits under &lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\LSASS.exe&lt;/code&gt;. A defender who edits &quot;the same key&quot; silently sets the wrong value and audit mode never engages. The deployment looks correct from the registry; the log surface is empty; the rollout breaks production on enforcement day. Two values. Two hives. Read this twice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In audit mode, the kernel emits the same 3065 / 3066 events for would-be load rejections but allows the loads to proceed. Two months of audit-mode telemetry typically surfaces every smart-card middleware DLL, every password-filter, every third-party CSP on a corporate fleet. Once the audit log is clean (every vendor&apos;s modules have been re-signed at the LSA level or replaced), enforcement mode can be turned on without breaking production logins.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Skipping audit mode is the most common cause of LSA protection rollouts being rolled back after a wave of authentication failures. See §11 Item 1 for the full audit-then-enforce-then-UEFI-lock recipe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The deployment cadence has been deliberately glacial. &lt;code&gt;RunAsPPL&lt;/code&gt; shipped in Windows 8.1 in October 2013 -- &lt;em&gt;opt-in&lt;/em&gt;. It remained opt-in for nine years. Microsoft Learn records the inflection [@learn-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Audit mode for added LSA protection is enabled by default on devices running Windows 11 version 22H2 and later.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Audit mode default-on. Not enforcement. The Windows 11 24H2 release expanded the audit-mode rollout further. Eleven years from opt-in to effective default. The pace reflects the compatibility risk: every domain with a single non-Microsoft-signed LSASS plug-in would have surfaced as a support call.&lt;/p&gt;
&lt;p&gt;The registry knob is simple. The &lt;em&gt;kernel&lt;/em&gt; check that enforces it is not. The next section walks the access-check pipeline in detail, because the structural reason &lt;code&gt;SeDebugPrivilege&lt;/code&gt; cannot help an attacker is the order in which the kernel asks its questions.&lt;/p&gt;
&lt;h2&gt;7. The Kernel Access Check -- What Happens Inside &lt;code&gt;NtOpenProcess&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Recall the trace from Section 1. The denial happens before &lt;code&gt;SeAccessCheck&lt;/code&gt; runs. The reason &lt;code&gt;SeDebugPrivilege&lt;/code&gt; does not help is not that the kernel decided to override the privilege; it is that the kernel never asked about the privilege. The order matters. Let us walk it.&lt;/p&gt;
&lt;p&gt;The Win32 caller invokes &lt;code&gt;OpenProcess&lt;/code&gt;, which thunks through &lt;code&gt;kernel32.dll&lt;/code&gt; to the syscall &lt;code&gt;NtOpenProcess&lt;/code&gt;. &lt;code&gt;NtOpenProcess&lt;/code&gt; does its handle-lookup and dispatches to the process-type object-manager open callback, &lt;code&gt;PspProcessOpen&lt;/code&gt;. Ionescu&apos;s Part 2 names the path verbatim [@ionescu-part2]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Access to protected processes (and their threads) is gated by the &lt;code&gt;PspProcessOpen&lt;/code&gt; and &lt;code&gt;PspThreadOpen&lt;/code&gt; object manager callback routines, which perform two checks. The first, done by calling &lt;code&gt;PspCheckForInvalidAccessByProtection&lt;/code&gt; (which in turn calls &lt;code&gt;RtlTestProtectedAccess&lt;/code&gt; and &lt;code&gt;RtlValidProtectionLevel&lt;/code&gt;) ...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;PspCheckForInvalidAccessByProtection&lt;/code&gt; does two things. First, it splits the caller&apos;s requested access mask into two subsets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;limited mask&lt;/strong&gt; -- a fixed set of bits (&lt;code&gt;SYNCHRONIZE&lt;/code&gt;, &lt;code&gt;PROCESS_QUERY_LIMITED_INFORMATION&lt;/code&gt;, and a small handful of others) that the lattice never forbids. The limited mask is subject only to the standard &lt;code&gt;SeAccessCheck&lt;/code&gt; against the target&apos;s DACL.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;full mask&lt;/strong&gt; -- everything else, including &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;, &lt;code&gt;PROCESS_VM_WRITE&lt;/code&gt;, &lt;code&gt;PROCESS_CREATE_THREAD&lt;/code&gt;, &lt;code&gt;PROCESS_DUP_HANDLE&lt;/code&gt;, and &lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt;. The full mask is subject to the lattice rule.&lt;/li&gt;
&lt;/ul&gt;

The subset of `PROCESS_*` access rights that the PPL lattice always allows the standard `SeAccessCheck` to evaluate. Includes `SYNCHRONIZE`, `PROCESS_QUERY_LIMITED_INFORMATION`, `PROCESS_SET_LIMITED_INFORMATION`, and `PROCESS_SUSPEND_RESUME`. `PROCESS_TERMINATE` is included for callers below the Antimalware tier (deny mask `0xFC7FE`), but the kernel widens the deny mask to `0xFC7FF` at the `Antimalware`, `Lsa`, and `WinTcb` rungs -- bit 0, `PROCESS_TERMINATE` -- making those three rungs unkillable except from peers or higher.
&lt;p&gt;Second, it indexes into &lt;code&gt;RtlProtectedAccess[]&lt;/code&gt; using the caller&apos;s signer rung and the target&apos;s type, retrieves the row of permissible access bits, and ANDs the row with the full mask. If the result is non-empty, the access proceeds; if the result is zero, the kernel strips the full-mask bits from the request and returns either the limited subset (if the caller asked for any limited bits) or &lt;code&gt;STATUS_ACCESS_DENIED&lt;/code&gt;. &lt;code&gt;RtlValidProtectionLevel&lt;/code&gt; runs alongside as a sanity check on the encoded byte to catch malformed &lt;code&gt;EPROCESS.Protection&lt;/code&gt; values that would otherwise let the lattice walk off the end of the table.&lt;/p&gt;

sequenceDiagram
    participant App as Caller (any token)
    participant Nt as NtOpenProcess
    participant PsPO as PspProcessOpen
    participant Chk as PspCheckForInvalidAccessByProtection
    participant Rtl as RtlTestProtectedAccess + RtlValidProtectionLevel
    participant Tab as RtlProtectedAccess[] table
    participant SAC as SeAccessCheck
    App-&amp;gt;&amp;gt;Nt: NtOpenProcess(DesiredAccess)
    Nt-&amp;gt;&amp;gt;PsPO: dispatch
    PsPO-&amp;gt;&amp;gt;Chk: protection check
    Chk-&amp;gt;&amp;gt;Rtl: lookup caller / target rungs
    Rtl-&amp;gt;&amp;gt;Tab: index row, retrieve allowed bits
    Tab--&amp;gt;&amp;gt;Rtl: row of allowed access bits
    Rtl--&amp;gt;&amp;gt;Chk: full mask allowed or stripped
    Chk--&amp;gt;&amp;gt;PsPO: residual mask (full or limited)
    PsPO-&amp;gt;&amp;gt;SAC: residual mask vs DACL + token
    SAC--&amp;gt;&amp;gt;Nt: final mask
    Nt--&amp;gt;&amp;gt;App: handle or STATUS_ACCESS_DENIED
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The protection check runs &lt;em&gt;before&lt;/em&gt; &lt;code&gt;SeAccessCheck&lt;/code&gt;. Privileges are evaluated by &lt;code&gt;SeAccessCheck&lt;/code&gt;. The reason &lt;code&gt;SeDebugPrivilege&lt;/code&gt; does not help is structural -- it is not consulted at the moment of denial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Four worked traces make this concrete.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Case (a): admin -&amp;gt; lsass with &lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt;.&lt;/strong&gt; The caller has no &lt;code&gt;EPROCESS.Protection.Type&lt;/code&gt; (it is &lt;code&gt;None&lt;/code&gt;). The target is &lt;code&gt;PPL/Lsa&lt;/code&gt;. The lattice forbids the full mask. The kernel strips every bit of &lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt; except the limited subset. The caller wanted to write memory; the limited subset cannot write memory; the operation effectively fails. This is the Mimikatz scenario.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Case (b): admin -&amp;gt; lsass with &lt;code&gt;PROCESS_QUERY_LIMITED_INFORMATION&lt;/code&gt;.&lt;/strong&gt; Same caller, same target, but the requested mask sits entirely in the limited subset. The lattice does not gate the limited mask. &lt;code&gt;SeAccessCheck&lt;/code&gt; evaluates the DACL on &lt;code&gt;lsass.exe&lt;/code&gt;, finds that administrators are permitted to query basic process information, and the call succeeds. This is why Process Explorer can still enumerate &lt;code&gt;lsass.exe&lt;/code&gt; and show its threads even when LSA protection is enabled.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Case (c): &lt;code&gt;MsMpEng.exe&lt;/code&gt; (PPL/Antimalware, rung 3) -&amp;gt; &lt;code&gt;lsass.exe&lt;/code&gt; (PPL/Lsa, rung 4) with &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;.&lt;/strong&gt; The lattice rule: caller rung 3 &amp;lt; target rung 4, so the full mask is denied. Defender cannot read LSASS memory. Defender does not need to; the cross-rung isolation prevents one Microsoft service from reading another Microsoft service&apos;s secrets even within the same trusted system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Case (d): hypothetical &lt;code&gt;PPL/WinTcb&lt;/code&gt; (rung 6) -&amp;gt; &lt;code&gt;lsass.exe&lt;/code&gt; (PPL/Lsa, rung 4) with &lt;code&gt;PROCESS_VM_READ&lt;/code&gt;.&lt;/strong&gt; The lattice rule: caller rung 6 &amp;gt;= target rung 4, so the full mask is allowed. A process signed at the WinTcb rung can read LSASS memory by design. This is how Service Control Manager and Windows Error Reporting can still interact with protected &lt;code&gt;lsass.exe&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Caller&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Mask&lt;/th&gt;
&lt;th&gt;Lattice rule&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Admin, no Protection&lt;/td&gt;
&lt;td&gt;PPL/Lsa&lt;/td&gt;
&lt;td&gt;PROCESS_ALL_ACCESS&lt;/td&gt;
&lt;td&gt;Caller has no rung&lt;/td&gt;
&lt;td&gt;Full mask stripped (denied)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin, no Protection&lt;/td&gt;
&lt;td&gt;PPL/Lsa&lt;/td&gt;
&lt;td&gt;PROCESS_QUERY_LIMITED_INFORMATION&lt;/td&gt;
&lt;td&gt;Limited mask&lt;/td&gt;
&lt;td&gt;Allowed (DACL permitting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/Antimalware (3)&lt;/td&gt;
&lt;td&gt;PPL/Lsa (4)&lt;/td&gt;
&lt;td&gt;PROCESS_VM_READ&lt;/td&gt;
&lt;td&gt;3 &amp;lt; 4&lt;/td&gt;
&lt;td&gt;Denied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PPL/WinTcb (6)&lt;/td&gt;
&lt;td&gt;PPL/Lsa (4)&lt;/td&gt;
&lt;td&gt;PROCESS_VM_READ&lt;/td&gt;
&lt;td&gt;6 &amp;gt;= 4&lt;/td&gt;
&lt;td&gt;Allowed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The Audit bit revisits the table from a different angle. The bit is annotated &lt;code&gt;Reserved&lt;/code&gt; in itm4n&apos;s public structure definition and named without semantic gloss in Ionescu Part 1; the precise runtime emission shape on an &lt;code&gt;OpenProcess&lt;/code&gt; denial is not enumerated in any of Ionescu Part 1, Forshaw 2018, itm4n&apos;s RunAsPPL writeup, or Microsoft Learn&apos;s RunAsPPL page (whose CodeIntegrity events 3033/3063/3065/3066 are scoped to &lt;code&gt;AuditLevel&lt;/code&gt; under &lt;code&gt;IFEO\LSASS.exe&lt;/code&gt; and to DLL-load failures, not per-process Audit-bit denials) [@ionescu-part1] [@itm4n-runasppl] [@learn-runasppl]. The field name and bit position imply a forensic side-channel; the exact event shape is not in the public record.Two adjacent kernel mechanisms exist in the same neighbourhood but mediate different threat models. &lt;code&gt;PROCESS_TRUST_LABEL_ACE&lt;/code&gt; (a Trust SID ACL entry, introduced in Windows 8.1 alongside PPL) is an ACL-side companion that runs &lt;em&gt;inside&lt;/em&gt; &lt;code&gt;SeAccessCheck&lt;/code&gt; -- it adds a token-style trust label that interacts with the security descriptor in the standard way. Code Integrity Guard (&lt;code&gt;ProcessSignaturePolicy&lt;/code&gt;) is a per-process &lt;em&gt;signed-image&lt;/em&gt; enforcer settable at &lt;code&gt;CreateProcess&lt;/code&gt; time via the &lt;code&gt;PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY&lt;/code&gt; attribute. Neither is part of PPL; both interact with the same problem space.&lt;/p&gt;
&lt;p&gt;The kernel verifies who is asking, what they are asking for, and at what rung the target sits. What the kernel &lt;em&gt;cannot&lt;/em&gt; verify is the behaviour of code that arrives through a signed channel and then executes against attacker-controlled data. That structural seam is the entire premise of the bypass arms race, and it is the next section.&lt;/p&gt;
&lt;h2&gt;8. The Bypass Arms Race -- Forshaw, itm4n, Landau&lt;/h2&gt;
&lt;p&gt;If the kernel only verifies the channel by which code enters a PPL, every bypass should attack the seam between channel and behaviour. Test that prediction against the public record. Since 2018, four named bypass acts have hit major Microsoft research blogs. All four sit in the same structural class.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The kernel verifies the channel. It does not verify the behaviour. Every public PPL bypass since 2018 attacks the seam between what the channel proves (a signature, an EKU, a section identity) and what the code does once mapped.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Act I (2018) -- Forshaw and JScript-into-PPL&lt;/h3&gt;
&lt;p&gt;James Forshaw, then at Google Project Zero, published &quot;Injecting Code into Windows Protected Processes Using COM&quot; in October 2018 [@forshaw-2018-10]. The mechanism: a PPL can be made to instantiate a COM object whose CLSID resolves to &lt;code&gt;scrobj.dll&lt;/code&gt;, the Microsoft-signed Windows Script Component scripting host. Once loaded into the PPL, the script object accepts attacker-supplied source code and executes it inside the protected process. The DLL is signed. The kernel admits it. The kernel cannot reason about the JScript source it then runs.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s fix in Windows 10 1803 (April 2018, deployed broadly through that year) was a hardcoded deny-list in &lt;code&gt;CI.DLL&lt;/code&gt;. Forshaw&apos;s own writeup gives the source verbatim [@forshaw-2018-10]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;UNICODE_STRING g_BlockedDllsForPPL[] = {
    DECLARE_USTR(&quot;scrobj.dll&quot;),
    DECLARE_USTR(&quot;scrrun.dll&quot;),
    DECLARE_USTR(&quot;jscript.dll&quot;),
    DECLARE_USTR(&quot;jscript9.dll&quot;),
    DECLARE_USTR(&quot;vbscript.dll&quot;)
};

NTSTATUS CipMitigatePPLBypassThroughInterpreters(
    PEPROCESS Process, LPBYTE Image, SIZE_T ImageSize)
{
    if (!PsIsProtectedProcess(Process)) return STATUS_SUCCESS;
    // walk g_BlockedDllsForPPL; if any match, return STATUS_DYNAMIC_CODE_BLOCKED
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Five DLLs, hardcoded. Microsoft Learn corroborates the policy on the user-facing side [@learn-am-services]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The following scripting DLLs are forbidden by CodeIntegrity inside a protected process: scrobj.dll, scrrun.dll, jscript.dll, jscript9.dll, and vbscript.dll.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Channel: a Microsoft-signed DLL. Behaviour: arbitrary attacker script. The fix narrows the channel by name-listing the five DLLs known to admit attacker behaviour. The class survives.The mechanism was previewed at Recon Montreal 2018 in the joint Forshaw-Ionescu talk &quot;Unknown Known DLLs and other Code Integrity Trust Violations&quot; (June 15-17, 2018) [@recon-mtl-2018]. Forshaw&apos;s August 2017 &quot;Bypassing VirtualBox Process Hardening&quot; essay [@forshaw-2017-vbox] is the structural precursor -- it makes the same channel-vs-behaviour argument against a different kernel-supported process-hardening regime.&lt;/p&gt;
&lt;h3&gt;Act II (2018-2021) -- DefineDosDevice and PPLdump&lt;/h3&gt;
&lt;p&gt;In his August 2018 post on object-directory exploits [@forshaw-2018-08], Forshaw added a single throwaway sentence that the security community would spend three years productising. itm4n quotes it verbatim in his 2021 SCRT walkthrough [@itm4n-scrt]:&lt;/p&gt;

Abusing the DefineDosDevice API actually has a second use, it&apos;s an Administrator to Protected Process Light (PPL) bypass.
&lt;p&gt;The mechanism, fully worked out by itm4n in April 2021, is structural and uses that same primitive. As an administrator, call &lt;code&gt;DefineDosDevice&lt;/code&gt; to create a symbolic link in &lt;code&gt;\KnownDlls\&lt;/code&gt; (the object-directory subkey that the loader uses for fast known-DLL lookups). The call is dispatched via RPC to &lt;code&gt;csrss.exe&lt;/code&gt;, which runs at PPL/WinTcb (rung 6) and so has the lattice authority to write into protected directories. The administrator gets a &lt;code&gt;\KnownDlls\&lt;/code&gt; entry pointing at an attacker-controlled section. Now start a PPL. The PPL&apos;s loader resolves DLL names through &lt;code&gt;\KnownDlls\&lt;/code&gt; and finds the administrator&apos;s entry. The PPL maps the attacker&apos;s section without re-validating its on-disk signature, because &lt;code&gt;\KnownDlls\&lt;/code&gt; is the kernel&apos;s vouched-for fast path.&lt;/p&gt;
&lt;p&gt;itm4n&apos;s PPLdump tool, published April 2021, automated the attack. The README test matrix lists every Windows version it ran against [@ppldump-repo]. For fifteen months, an administrator could dump any PPL&apos;s memory, including &lt;code&gt;lsass.exe&lt;/code&gt;, despite &lt;code&gt;RunAsPPL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s fix arrived in build 19044.1826 (the July 2022 update to Windows 10 21H2). itm4n&apos;s &quot;End of PPLdump&quot; writeup describes the patch and the BinDiff diff verbatim [@itm4n-end-of-ppldump]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The conclusion is that PPLs now appear to be behaving just like PPs and therefore no longer rely on Known DLLs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The fix patched &lt;code&gt;LdrpInitializeProcess&lt;/code&gt; in NTDLL to skip &lt;code&gt;\KnownDlls\&lt;/code&gt; for PPL processes, behind a Velocity feature flag (&lt;code&gt;Feature_Servicing_2206c_38427506__private_IsEnabled&lt;/code&gt;). PPLdump&apos;s repository README now opens with [@ppldump-repo]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2022-07-24 - As of Windows 10 21H2 10.0.19044.1826 (July 2022 update), the exploit implemented in PPLdump no longer works. A patch in NTDLL now prevents PPLs from loading Known DLLs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;itm4n&apos;s structural finding -- that *PPLs honoured &lt;code&gt;\KnownDlls\&lt;/code&gt; while PPs did not* -- is the most interesting failure in the eight-year run, because the asymmetry sat in plain sight from 2013 to 2022 and nobody had asked &quot;why are PPs and PPLs loading sections differently?&quot; The fix closes one asymmetry. The structural class survives.PPLdump&apos;s substitution chain uses NTFS transactions and Forrest Orr&apos;s &quot;phantom DLL hollowing&quot; technique to materialise the attacker-controlled section on disk in a way the kernel section creator will accept [@forrest-orr-hollow]. Orr&apos;s writeup is the original publication of the hollowing primitive; PPLdump composes it with the &lt;code&gt;\KnownDlls\&lt;/code&gt; redirection trick.&lt;/p&gt;
&lt;h3&gt;Act III (2022-2024) -- Landau&apos;s PPLFault CI TOCTOU&lt;/h3&gt;
&lt;p&gt;Gabriel Landau, then at Elastic, presented &quot;PPLdump Is Dead. Long Live PPLdump!&quot; at Black Hat Asia 2023 [@bh-asia-2023-pdf]. The mechanism is a Time-Of-Check / Time-Of-Use bug at the section-creation layer.&lt;/p&gt;

A class of bug in which a security property is verified at one point in time but the underlying object is mutable between the check and the use. The protected resource passes its check, then changes between check and access, and the operation proceeds against the changed state without re-verification.
&lt;p&gt;The TOCTOU here is subtle. When a PPL calls &lt;code&gt;NtCreateSection&lt;/code&gt; on a Microsoft-signed DLL, the kernel&apos;s memory manager calls &lt;code&gt;MiValidateSectionCreate&lt;/code&gt;, which calls into &lt;code&gt;ci.dll&lt;/code&gt; to verify the file&apos;s Authenticode signature. The check succeeds. The section is created. But the memory manager does not page in the file contents at section-create time; it pages them in lazily, on demand, when threads first touch the mapped pages. If an attacker can keep the section&apos;s backing file &lt;em&gt;unsubstituted&lt;/em&gt; during the signature check and substituted during the lazy page-in, the kernel will execute attacker bytes through a section whose signature it already verified.&lt;/p&gt;
&lt;p&gt;Landau&apos;s exploit uses Windows&apos; CloudFilter API. An attacker holds an exclusive oplock on a Microsoft-signed DLL during the section-create signature check. After the check passes, the attacker&apos;s CloudFilter &lt;code&gt;FetchDataCallback&lt;/code&gt; provides different bytes (the payload) when the kernel pages in the section. The PPL maps and executes the payload. Landau&apos;s Elastic post documents the chain verbatim [@elastic-pplfault]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The internal memory manager function &lt;code&gt;MiValidateSectionCreate&lt;/code&gt; relies on the Code Integrity module &lt;code&gt;ci.dll&lt;/code&gt; to handle the requisite cryptography and PKI policy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Microsoft&apos;s fix shipped in Windows Insider Canary build 25941 on September 1, 2023 [@elastic-pplfault]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On September 1, 2023, Microsoft released a new build of Windows Insider Canary, version 25941 ... Build 25941 includes improvements to the Code Integrity (CI) subsystem that mitigate a long-standing issue that enables attackers to load unsigned code into Protected Process Light (PPL) processes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The fix narrows the immediate channel by extending page-hash validation to PPL-loaded images that reside on &lt;em&gt;remote&lt;/em&gt; (SMB redirector) paths -- the precise surface that PPLFault required to drive its CloudFilter &lt;code&gt;FetchDataCallback&lt;/code&gt; substitution [@elastic-pplfault]. Locally-cached PPL DLL loads continue to rely on the section-create signature check, so the structural seam survives. The GA patch shipped on February 13, 2024 [@pplfault-repo]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2024-02 UPDATE: Microsoft patched PPLFault on 2024-02-13.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Channel: a signed Microsoft DLL whose hash matched at section create. Behaviour: attacker payload mapped via the lazy page-in. The fix narrows the channel by widening the verification surface from &quot;the file at section-create time&quot; to &quot;every page at fault time.&quot; The class survives.&lt;/p&gt;
&lt;h3&gt;Act IV (2022-2024) -- BYOVDLL and itm4n&apos;s KeyIso chain&lt;/h3&gt;
&lt;p&gt;Bring Your Own Vulnerable DLL. Coined by Gabriel Landau on Twitter in October 2022 (itm4n screenshots the original tweet [@itm4n-ghost-part1]; tweet status 1580067594568364032). Productised by itm4n in August 2024 in &quot;Ghost in the PPL Part 1.&quot;&lt;/p&gt;

A bypass class against any signature-gated security mechanism in which the attacker loads a *legitimately signed but historically vulnerable* binary and exploits the known vulnerability inside it. The signature check passes; the vulnerability does the work. The structural property that makes the class hard to fix is that the kernel cannot deny-list legitimately signed older Microsoft DLLs without breaking the deployments that still depend on them.
&lt;p&gt;itm4n&apos;s specific chain targets the CNG Key Isolation service (&quot;KeyIso&quot;), which runs in &lt;code&gt;lsass.exe&lt;/code&gt; and so inherits its PPL/Lsa protection. The chain is precise [@itm4n-ghost-part1]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;As administrator, stop the KeyIso service.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Services\KeyIso\Parameters\ServiceDll&lt;/code&gt; to point at an older &lt;code&gt;keyiso.dll&lt;/code&gt; extracted from Microsoft update KB5023778. This DLL is Microsoft-signed; the kernel admits it.&lt;/li&gt;
&lt;li&gt;Restart the KeyIso service. The older &lt;code&gt;keyiso.dll&lt;/code&gt; loads into LSASS at PPL/Lsa.&lt;/li&gt;
&lt;li&gt;Trigger CVE-2023-36906, an out-of-bounds read information disclosure in the older &lt;code&gt;keyiso.dll&lt;/code&gt;, to leak an address.&lt;/li&gt;
&lt;li&gt;Trigger CVE-2023-28229, one of six use-after-frees in the same DLL, to obtain control of a &lt;code&gt;CALL&lt;/code&gt; target via the &lt;code&gt;RAX&lt;/code&gt; register.&lt;/li&gt;
&lt;li&gt;Execute attacker code at PPL/Lsa.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The CVEs are real and tracked. k0shl&apos;s writeup is the primary root-cause analysis [@k0shl-keyiso]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Microsoft patched vulnerabilities I reported in CNG Key Isolation service, assigned CVE-2023-28229 and CVE-2023-36906, the CVE-2023-28229 included 6 use after free vulenrabilities with similar root cause and the CVE-2023-36906 is a out of bound read information disclosure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;NVD records both [@nvd-2023-28229] [@nvd-2023-36906]. Y3A&apos;s GitHub repository [@y3a-cve-poc] provides a public PoC for CVE-2023-28229 that itm4n&apos;s chain composes.&lt;/p&gt;
&lt;p&gt;Channel: an actually-Microsoft-signed DLL. Behaviour: the memory-safety vulnerability inside it. There is no general fix announced. Microsoft fixed the specific CVEs by shipping a newer &lt;code&gt;keyiso.dll&lt;/code&gt;, but the older DLL remains in circulation (it ships inside every patched cumulative update bundle), and a kernel that has to admit every legitimately signed older Microsoft DLL has no general defense against the next CVE-of-the-month.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; BYOVDLL has no general patch. Microsoft fixes each underlying CVE on the standard cumulative-update cadence. The class persists for as long as the kernel admits older signed Microsoft DLLs into PPLs, which is for as long as legitimately deployed software depends on the older DLLs.&lt;/p&gt;
&lt;/blockquote&gt;

timeline
    title PPL Bypass Arms Race (2018-2024)
    2018-10 : Forshaw JScript-into-PPL : Fix 1803 Apr 2018 : g_BlockedDllsForPPL deny-list
    2021-04 : itm4n PPLdump (KnownDlls) : Fix Jul 2022 build 19044.1826 : LdrpInitializeProcess patch
    2022-09 : Landau PPLFault (TOCTOU) : Fix Feb 2024 13 GA : CI page-hash for PPLs
    2024-08 : itm4n BYOVDLL KeyIso chain : No general fix : CVEs patched piecewise
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Act&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Channel verified&lt;/th&gt;
&lt;th&gt;Behaviour exploited&lt;/th&gt;
&lt;th&gt;Microsoft fix&lt;/th&gt;
&lt;th&gt;Fix date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;2018&lt;/td&gt;
&lt;td&gt;Microsoft-signed &lt;code&gt;scrobj.dll&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JScript source executed by COM object&lt;/td&gt;
&lt;td&gt;&lt;code&gt;g_BlockedDllsForPPL&lt;/code&gt; deny-list of 5 DLLs&lt;/td&gt;
&lt;td&gt;Apr 2018 (1803)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;II&lt;/td&gt;
&lt;td&gt;2021&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\KnownDlls\&lt;/code&gt; symlink (CSRSS-blessed)&lt;/td&gt;
&lt;td&gt;Attacker section mapped without re-validation&lt;/td&gt;
&lt;td&gt;NTDLL &lt;code&gt;LdrpInitializeProcess&lt;/code&gt; patch&lt;/td&gt;
&lt;td&gt;Jul 2022 (19044.1826)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;III&lt;/td&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;Signed DLL passed &lt;code&gt;MiValidateSectionCreate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CloudFilter substitutes bytes on lazy page-in&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/INTEGRITYCHECK&lt;/code&gt; page hashes for PPLs&lt;/td&gt;
&lt;td&gt;Feb 2024 (GA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IV&lt;/td&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;Legitimately-signed older &lt;code&gt;keyiso.dll&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use-after-free + OOB read (CVE-2023-28229, CVE-2023-36906)&lt;/td&gt;
&lt;td&gt;None (CVE-by-CVE)&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

flowchart TD
    A[Admin stops KeyIso service]
    B[Repoint ServiceDll to older keyiso.dll&lt;br /&gt;from KB5023778]
    C[Restart KeyIso service]
    D[Older keyiso.dll loads&lt;br /&gt;into lsass.exe PPL/Lsa]
    E[Trigger CVE-2023-36906&lt;br /&gt;OOB read for info leak]
    F[Trigger CVE-2023-28229&lt;br /&gt;UAF for RAX control]
    G[Code execution at PPL/Lsa]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E --&amp;gt; F --&amp;gt; G

itm4n explicitly attributes the BYOVDLL framing to Landau&apos;s October 2022 tweet, even though itm4n&apos;s KeyIso chain is the first public productisation. The attribution chain matters because it documents how a one-line research observation (Twitter status 1580067594568364032, screenshot preserved in [@itm4n-ghost-part1]) became a working exploit two years later. The pattern repeats in this domain: Forshaw&apos;s one-sentence DefineDosDevice comment to PPLdump (3 years); Landau&apos;s BYOVDLL tweet to itm4n&apos;s KeyIso chain (2 years). The structural class outlives its discoverer.
&lt;p&gt;Four acts, one class. Every public bypass since 2018 has lived in the same narrow shape: code that becomes part of a PPL through a signed channel and executes attacker-influenced data once mapped. Each generation of fix narrows what the channel admits -- name-list five DLLs; ignore &lt;code&gt;\KnownDlls\&lt;/code&gt;; page-hash every section; CVE-patch every vulnerable older DLL. The class survives because the kernel cannot reason about behaviour. By Rice&apos;s theorem it cannot reason about behaviour in general; in practice, it has nowhere even to start.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;lsass.exe&lt;/code&gt; code execution is reachable through BYOVDLL, where are the actual &lt;em&gt;secrets&lt;/em&gt;? Not in &lt;code&gt;lsass.exe&lt;/code&gt;. Not anywhere the kernel can read at all. The next section is the companion boundary.&lt;/p&gt;
&lt;h2&gt;9. The Companion Boundary -- Credential Guard, VBS, and &lt;code&gt;LsaIso.exe&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;itm4n opens his RunAsPPL walkthrough with a warning [@itm4n-runasppl]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I noticed that this protection tends to be confused with Credential Guard, which is completely different.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The confusion is understandable. Both run on Windows. Both protect LSASS. Both are configured by domain administrators. Both yield &quot;ACCESS_DENIED&quot; to Mimikatz when working correctly. They are nonetheless answering different questions, and they stack rather than replace each other.&lt;/p&gt;
&lt;p&gt;PPL stops an &lt;em&gt;administrator&lt;/em&gt; from reading kernel-trusted user-mode memory. It does nothing against a kernel-mode attacker who can simply zero the &lt;code&gt;Protection&lt;/code&gt; byte in the target &lt;code&gt;EPROCESS&lt;/code&gt;. The kernel-mode attacker is the next threat-model rung up, and the kernel-mode attacker is the threat that Credential Guard answers, by moving the credentials themselves out of &lt;code&gt;lsass.exe&lt;/code&gt; entirely.&lt;/p&gt;

A Hyper-V-based isolation regime in which the Windows hypervisor partitions the system into Virtual Trust Levels (VTLs). VTL0 contains the normal Windows kernel and user-mode processes. VTL1 contains the Secure Kernel and a small set of user-mode trustlets. Memory in VTL1 is inaccessible to VTL0, even from VTL0 kernel-mode code.

A user-mode process running inside VTL1. Trustlets are Microsoft-signed at a specific protected-process equivalent rung within VTL1 and serve as the user-mode hosts for VBS-isolated functionality. `LsaIso.exe` is the trustlet that holds the actual credential material on Credential Guard-enabled hosts.
&lt;p&gt;The architecture is, at the highest level, three layers: VTL0 user-mode, VTL0 kernel, and VTL1 (Secure Kernel plus trustlets). On a Credential Guard-enabled host, &lt;code&gt;lsass.exe&lt;/code&gt; still exists in VTL0 user-mode, still protects itself with PPL/Lsa, and still answers authentication requests. But it no longer holds the NTLM hashes, Kerberos TGT keys, or Cred Manager domain credentials. Those secrets live in &lt;code&gt;LsaIso.exe&lt;/code&gt;, a trustlet in VTL1. When LSASS needs to authenticate a credential, it makes a hypercall into VTL1, and &lt;code&gt;LsaIso.exe&lt;/code&gt; performs the cryptographic operation entirely within VTL1 memory, returning only the result. The keys never leave VTL1.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s documentation states the threat model directly [@learn-cg]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Credential Guard prevents credential theft attacks by protecting NTLM password hashes, Kerberos Ticket Granting Tickets (TGTs), and credentials stored by applications as domain credentials.&lt;/p&gt;
&lt;p&gt;Credential Guard uses Virtualization-based security (VBS) to isolate secrets so that only privileged system software can access them.&lt;/p&gt;
&lt;p&gt;Malware running in the operating system with administrative privileges can&apos;t extract secrets that are protected by VBS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The third sentence is the load-bearing one. &lt;em&gt;Malware running with administrative privileges&lt;/em&gt; maps cleanly to a PPL bypass that achieves code execution at PPL/Lsa. Even from inside &lt;code&gt;lsass.exe&lt;/code&gt;, the secrets are not there.&lt;/p&gt;

flowchart TD
    subgraph VTL0[VTL0 normal world]
        Admin[Admin / SYSTEM token]
        Lsass[lsass.exe at PPL/Lsa]
        Kern0[VTL0 kernel]
    end
    subgraph VTL1[VTL1 secure world]
        SK[Secure Kernel]
        Iso[LsaIso.exe trustlet]
        Secrets[NTLM hashes, Kerberos TGT keys]
    end
    Admin -- &quot;PPL barrier (lattice)&quot; --x Lsass
    Lsass -- hypercall --&amp;gt; Iso
    Kern0 -- &quot;VBS barrier (VTL boundary)&quot; --x Iso
    Iso --&amp;gt; Secrets
&lt;p&gt;The two mechanisms stack rather than overlap. PPL prevents an admin from &lt;code&gt;OpenProcess(PROCESS_VM_READ, lsass)&lt;/code&gt; at the user-mode lattice level. Credential Guard prevents a kernel-mode attacker who &lt;em&gt;succeeds&lt;/em&gt; against PPL from finding the keys, because the keys are in VTL1 memory that the VTL0 kernel cannot read at all. itm4n&apos;s &quot;complementary&quot; framing in the RunAsPPL writeup is the right operational summary [@itm4n-runasppl]: deploy both, always both.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; PPL gates user-mode admins out of LSASS code memory. Credential Guard gates everything else (kernel-mode attackers, BYOVDLL execution-at-PPL/Lsa) out of the secrets themselves by moving the secrets to VTL1. Each mechanism answers a layer of the threat model the other does not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;PPL (LSA protection)&lt;/th&gt;
&lt;th&gt;Credential Guard&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Threat model&lt;/td&gt;
&lt;td&gt;Administrator -&amp;gt; user-mode LSASS&lt;/td&gt;
&lt;td&gt;VTL0 kernel + admin -&amp;gt; credential material&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layer&lt;/td&gt;
&lt;td&gt;VTL0 user-mode lattice&lt;/td&gt;
&lt;td&gt;VTL0 / VTL1 VBS boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kernel-mode attacker&lt;/td&gt;
&lt;td&gt;Cannot stop them&lt;/td&gt;
&lt;td&gt;Stops them (VBS-isolated memory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSRC classification&lt;/td&gt;
&lt;td&gt;Defense in depth&lt;/td&gt;
&lt;td&gt;Security boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default-on (consumer)&lt;/td&gt;
&lt;td&gt;Audit mode, Win11 22H2&lt;/td&gt;
&lt;td&gt;n/a (enterprise)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default-on (enterprise)&lt;/td&gt;
&lt;td&gt;Audit mode, Win11 22H2&lt;/td&gt;
&lt;td&gt;Enabled, Win11 22H2 / Win Server 2025 (domain-joined non-DC)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

The architecture of `LsaIso.exe`, its trustlet ID, its IUM EKU, and the hypercall plumbing between LSASS and the trustlet are the subject of a separate article in this series (&quot;VBS Trustlets: What Actually Runs in the Secure Kernel&quot;). The cross-link is deliberate: PPL and Credential Guard are paired in practice, but the architectural depth of VTL1 is its own subject.
&lt;p&gt;Credential Guard&apos;s default-on rollout, recorded in Microsoft Learn [@learn-cg]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Starting in Windows 11, 22H2 and Windows Server 2025, Credential Guard is enabled by default on domain-joined, non-DC systems that meet hardware requirements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two stacked mechanisms; one classified as a security boundary, one not. The next section asks what the classification means.&lt;/p&gt;
&lt;h2&gt;10. Where PPL Isn&apos;t a Security Boundary -- Microsoft&apos;s Servicing Criteria&lt;/h2&gt;
&lt;p&gt;Gabriel Landau&apos;s &quot;Inside Microsoft&apos;s Plan to Kill PPLFault&quot; essay states the classification in one sentence [@elastic-pplfault]:&lt;/p&gt;

Microsoft does not consider PPL to be a security boundary, meaning they won&apos;t prioritize security patches for code-execution vulnerabilities discovered therein, but they have historically addressed some such vulnerabilities on a less-urgent basis.
&lt;p&gt;Microsoft&apos;s &quot;Windows Security Servicing Criteria&quot; defines the term &lt;em&gt;security boundary&lt;/em&gt; directly [@msrc-servicing]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A security boundary provides a logical separation between the code and data of security domains with different levels of trust. For example, the separation between kernel mode and user mode is a classic [...] security boundary.&lt;/p&gt;
&lt;/blockquote&gt;

A logical separation between code and data of security domains with different levels of trust. Microsoft commits to servicing security boundary violations with out-of-band patches when the severity bar is met. The kernel-mode / user-mode separation is the canonical example. Per Microsoft&apos;s published servicing criteria, PPL is *not* on the security-boundary list.

A security feature that raises the cost of an attack without guaranteeing prevention. Microsoft treats defense-in-depth features as servicing targets on the standard cumulative-update cadence, not as out-of-band patch priorities. PPL falls into this category per Microsoft&apos;s published classification.
&lt;p&gt;The relevant excerpts of the criteria page enumerate which surfaces are and are not boundaries. The live MSRC page renders that enumeration table client-side via JavaScript; the raw HTML returned by automated fetchers contains only the React shell. The text of the enumeration is preserved in the Wayback Machine capture at archive date 2023-05-06 [@msrc-criteria-archive], and Landau&apos;s follow-on Elastic post quotes the relevant administrative-process row verbatim [@elastic-byovd-admin]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Administrative processes and users are considered part of the Trusted Computing Base (TCB) for Windows and are therefore not strong[ly] isolated from the kernel boundary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The corresponding row for PPL is the same shape: administrative-process-to-PPL is not isolated as a security boundary. Landau filed VULN-074311 with MSRC in September 2022 disclosing both an admin-to-PPL and a PPL-to-kernel zero-day. The Elastic post records MSRC&apos;s classification of the disclosure verbatim [@elastic-byovd-admin]:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MSRC similarly does not consider admin-to-PPL a security boundary, instead classifying it as a defense-in-depth security feature.&lt;/p&gt;
&lt;/blockquote&gt;

The MSRC servicing-criteria page&apos;s *definition* of &quot;security boundary&quot; is retrievable from raw HTML and verified against the live page. The *enumeration* of which Windows surfaces are or are not boundaries lives in a client-side rendered table and is not present in the raw HTML payload. The verifiable trail for &quot;PPL is excluded from the boundary list&quot; is the Wayback Machine capture combined with Elastic&apos;s verbatim quotation of MSRC&apos;s classification.
&lt;p&gt;The operational consequence is direct. A published PPL bypass does not trigger an out-of-band patch. It is fixed on the next major-release cadence, sometimes faster if Microsoft has internal motivation. The disclosure-to-fix half-lives are public record:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bypass&lt;/th&gt;
&lt;th&gt;Disclosed&lt;/th&gt;
&lt;th&gt;Microsoft fix&lt;/th&gt;
&lt;th&gt;Disclosure-to-fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Forshaw 2018 JScript-into-PPL&lt;/td&gt;
&lt;td&gt;Oct 2018&lt;/td&gt;
&lt;td&gt;Apr 2018 (1803, pre-disclosure)&lt;/td&gt;
&lt;td&gt;~0 months (Microsoft fixed first)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;itm4n 2021 PPLdump (KnownDlls)&lt;/td&gt;
&lt;td&gt;Apr 2021&lt;/td&gt;
&lt;td&gt;Jul 2022 (build 19044.1826)&lt;/td&gt;
&lt;td&gt;~15 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Landau 2023 PPLFault (CI TOCTOU)&lt;/td&gt;
&lt;td&gt;Apr-Sep 2023&lt;/td&gt;
&lt;td&gt;Feb 2024 (GA)&lt;/td&gt;
&lt;td&gt;~5-11 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;itm4n 2024 BYOVDLL (KeyIso chain)&lt;/td&gt;
&lt;td&gt;Aug 2024&lt;/td&gt;
&lt;td&gt;none (open, CVE-by-CVE)&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A correctly classified PPL bypass is fixed on the standard cumulative-update cadence, not out-of-band. The implication for defenders is operational: PPL is exactly as strong as the engineering velocity Microsoft chooses to invest in it. Treat detection (Section 11) and the Credential Guard companion (Section 9) as load-bearing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The reader takeaway is the third Aha moment of the article. PPL is real, kernel-enforced, structurally elegant, and demonstrably effective against the threat it was designed for (administrator-from-user-mode reads of LSASS). It is also explicitly &lt;em&gt;not&lt;/em&gt; a security boundary per Microsoft&apos;s own published servicing policy, and that classification is the most important fact about it. Plan for bypasses. Stack with Credential Guard. Treat detection as primary, not secondary.&lt;/p&gt;
&lt;h2&gt;11. Practical Guide -- Configuring, Verifying, and Monitoring PPL&lt;/h2&gt;
&lt;p&gt;If you are deploying PPL on a corporate fleet, run this checklist. The order is deliberate: audit before enforce, verify before trust the verifier, and detect because no static control survives unmotivated.&lt;/p&gt;
&lt;h3&gt;Deploy&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Enable &lt;code&gt;AuditLevel = 8&lt;/code&gt; under &lt;code&gt;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\LSASS.exe&lt;/code&gt; for two months [@learn-runasppl]. This is a &lt;em&gt;different&lt;/em&gt; registry hive from &lt;code&gt;RunAsPPL&lt;/code&gt; (which lives under &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa&lt;/code&gt;); mixing the two values up is the most common Stage 0 deployment error (see §6). Collect CodeIntegrity events 3065 and 3066 to enumerate every LSASS plug-in that would fail enforcement (smart-card middleware, third-party CSPs, password-filter DLLs). Re-sign or replace the failing modules. Set &lt;code&gt;RunAsPPL = 1&lt;/code&gt; on Secure Boot-capable machines; the kernel automatically stores the policy in a UEFI variable. &lt;code&gt;RunAsPPL = 2&lt;/code&gt; (Win11 22H2+) is the softer option that omits the UEFI variable for environments requiring admin-removable protection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For third-party EDR, confirm the agent daemon runs at &lt;code&gt;PPL/Antimalware&lt;/code&gt; (signer rung 3, byte &lt;code&gt;0x31&lt;/code&gt;). Process Explorer exposes this via View -&amp;gt; Select Columns -&amp;gt; Protection. System Informer (the modern Process Hacker fork that itm4n recommends in his BYOVDLL writeup [@itm4n-ghost-part1]) shows the same field in its process list. If your EDR is &lt;em&gt;not&lt;/em&gt; running at &lt;code&gt;PPL/Antimalware&lt;/code&gt;, it does not have the kernel&apos;s protection against admin tampering even when its vendor claims &quot;protected&quot; in marketing material. Process Explorer&apos;s &quot;Protection&quot; column ships in the canonical Sysinternals distribution [@sysinternals-procexp]; it reads &lt;code&gt;EPROCESS.Protection&lt;/code&gt; via the &lt;code&gt;NtQueryInformationProcess&lt;/code&gt; entry point [@learn-ntqueryinfoproc], although the specific &lt;code&gt;ProcessProtectionInformation&lt;/code&gt; information-class value is not enumerated in the public Learn &lt;code&gt;PROCESSINFOCLASS&lt;/code&gt; table -- the value is community-documented from Windows headers and reverse engineering rather than from a Microsoft Learn API reference.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Verify&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; On a host you suspect of misconfiguration, attach WinDbg to the kernel and run &lt;code&gt;!process 0 7 lsass.exe&lt;/code&gt;. The output includes the &lt;code&gt;_PS_PROTECTION&lt;/code&gt; byte. Decode it with the formula from §3 above: &lt;code&gt;((value &amp;amp; 0xF0) &amp;gt;&amp;gt; 4)&lt;/code&gt; is the signer rung; &lt;code&gt;value &amp;amp; 0x07&lt;/code&gt; is the type; &lt;code&gt;(value &amp;gt;&amp;gt; 3) &amp;amp; 1&lt;/code&gt; is the audit bit. A &lt;code&gt;RunAsPPL = 1&lt;/code&gt; host yields &lt;code&gt;0x41&lt;/code&gt; (PPL + Lsa). The Defender service yields &lt;code&gt;0x31&lt;/code&gt; (PPL + Antimalware). &lt;code&gt;csrss.exe&lt;/code&gt; yields &lt;code&gt;0x61&lt;/code&gt; (PPL + WinTcb). If &lt;code&gt;lsass.exe&lt;/code&gt; shows &lt;code&gt;0x00&lt;/code&gt;, the registry policy did not take effect on this boot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{&lt;code&gt;function decode(b) {   const t = b &amp;amp; 0x07, a = (b &amp;gt;&amp;gt; 3) &amp;amp; 0x01, s = (b &amp;gt;&amp;gt; 4) &amp;amp; 0x0F;   const tn = [&apos;None&apos;, &apos;ProtectedLight&apos;, &apos;Protected&apos;];   const sn = [&apos;None&apos;,&apos;Authenticode&apos;,&apos;CodeGen&apos;,&apos;Antimalware&apos;,               &apos;Lsa&apos;,&apos;Windows&apos;,&apos;WinTcb&apos;,&apos;Max&apos;];   return &apos;0x&apos; + b.toString(16).padStart(2,&apos;0&apos;) + &apos; = &apos; +          (sn[s] || s) + &apos;-&apos; + (tn[t] || t) +          (a ? &apos; (Audit on)&apos; : &apos;&apos;); } // Three benchmark values you should be able to recognise by sight console.log(decode(0x31)); // MsMpEng.exe (Defender at PPL/Antimalware) console.log(decode(0x41)); // lsass.exe under RunAsPPL=1 console.log(decode(0x61)); // csrss.exe (PPL/WinTcb)&lt;/code&gt;}&lt;/p&gt;
&lt;h3&gt;Monitor&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The CodeIntegrity provider emits three event IDs that matter for PPL monitoring [@learn-runasppl]: | Event ID | Provider | What it tells you | |---|---|---| | 3033 | Microsoft-Windows-CodeIntegrity | A DLL load was blocked by CI (PPL or otherwise) | | 3063 | Microsoft-Windows-CodeIntegrity | Enforcement-mode: LSASS plug-in failed the shared-section security requirement (complement of audit-mode event 3065) | | 3065 | Microsoft-Windows-CodeIntegrity | LSASS plug-in failed the shared-section requirement | | 3066 | Microsoft-Windows-CodeIntegrity | LSASS plug-in failed the Microsoft signing level requirement | Sysmon Event 10 (ProcessAccess) captures &lt;code&gt;OpenProcess&lt;/code&gt; denials with the requested access mask and is the cheapest detection for a Mimikatz-shaped attempt against an RunAsPPL-protected &lt;code&gt;lsass.exe&lt;/code&gt;. A burst of 3033 events from a non-Microsoft process targeting &lt;code&gt;lsass.exe&lt;/code&gt; is the canonical signal that a PPL bypass attempt is under way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; PPL prevents admin-from-user-mode reads of LSASS. Credential Guard prevents kernel-mode reads of the credentials themselves (and BYOVDLL-style execution at PPL/Lsa). Deploy both. itm4n&apos;s &quot;complementary&quot; framing in his RunAsPPL writeup [@itm4n-runasppl] is the right operational model. On Win11 22H2 and Windows Server 2025, Credential Guard is default-on for domain-joined non-DC systems with VBS-capable hardware [@learn-cg]; on older fleets, enable it explicitly via Group Policy or the Device Guard / Credential Guard configuration script. Always both -- either alone leaves a layer of the threat model uncovered.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you are an EDR vendor wanting your daemon to run at &lt;code&gt;PPL/Antimalware&lt;/code&gt;, the path is fixed [@learn-mvi] [@learn-am-services]: 1. Hold Microsoft Virus Initiative membership; maintain independent-lab certification (AV-Comparatives, AV-Test, SE Labs, MRG Effitas, SKD Labs, VB 100, West Coast Labs, AVLab Cybersecurity Foundation). 2. Author an ELAM driver with an embedded &lt;code&gt;&amp;lt;ELAM&amp;gt;&lt;/code&gt; resource section enumerating your user-mode binary signing-certificate hashes. 3. Submit the driver through WHQL for Microsoft co-signing. 4. Use Trusted Signing for your user-mode binaries. 5. Verify with Process Explorer that the service launches at &lt;code&gt;PPL/Antimalware&lt;/code&gt; after install.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Practitioners who follow the checklist still need to know the common misconceptions. The next section catalogues them.&lt;/p&gt;
&lt;h2&gt;12. FAQ -- Common Misconceptions&lt;/h2&gt;
&lt;p&gt;Seven questions practitioners ask after their first PPL deployment.&lt;/p&gt;

Yes for full-access termination via `OpenProcess(PROCESS_TERMINATE, ...)`; an admin without a higher signer rung cannot terminate a `PPL/Antimalware` daemon by a direct kill. No for legitimate uninstall: the vendor&apos;s MSI installer (or equivalent) typically signals the daemon to shut itself down through its own service-control path, which is gated by ACL and not by the PPL lattice. Operationally, expect administrators to be able to uninstall your EDR but not to terminate its main process from outside the vendor toolchain.

No. itm4n&apos;s verbatim warning is worth repeating [@itm4n-runasppl]: &quot;I noticed that this protection tends to be confused with Credential Guard, which is completely different.&quot; PPL protects `lsass.exe` *as a process* from admin-from-user-mode reads. Credential Guard moves the *credentials themselves* into VTL1 memory via VBS. PPL is a VTL0 user-mode lattice control. Credential Guard is a VTL0 / VTL1 hypervisor boundary. They stack; see Section 9 for the layering and Section 11 Item 5 for the deployment recommendation.

Because Microsoft has not classified PPL as a security boundary. The Windows Security Servicing Criteria define a security boundary as a logical separation between security domains at different levels of trust, and Microsoft&apos;s published enumeration excludes administrative-process-to-PPL from that list [@msrc-servicing] [@elastic-byovd-admin]. PPL is treated as a defense-in-depth feature. The operational implication is that PPL bypasses are fixed on the next major release cadence rather than out-of-band, with disclosure-to-fix half-lives ranging from approximately five to fifteen months historically (see Section 10 for the data).

Practically no for non-AV applications. The protected-process EKU OIDs are gated by Microsoft&apos;s certificate authorities; only the Antimalware rung admits third-party certificates, and admission is mediated by ELAM driver + Microsoft Virus Initiative membership [@learn-mvi]. Hobbyist tooling cannot opt in. There is no public path for a non-AV third-party application to claim a PPL rung. If your application requires PPL-style anti-tampering, the realistic options are (a) become an MVI member if your application is an AV/EDR, (b) use Process Mitigation Policies such as Code Integrity Guard for code-injection resistance, or (c) deploy your sensitive operations inside a separate Microsoft-signed service.

&quot;Protected service&quot; is informal terminology for a Windows service whose host process runs as a PPL, with the Service Control Manager configured to launch it at a specific signer rung. The deployment plumbing (SCM service configuration, service-DLL packaging, the signing of the host binary) is what makes a service &quot;protected.&quot; The PPL machinery is what makes the host process actually resistant to tampering. The two terms describe the same thing from different angles -- one from the SCM-management view, one from the kernel-access-check view.

Only if the smart-card middleware DLL is not signed at the LSA level (signer rung 4). Most major smart-card vendors have updated their middleware to be Microsoft-signed at the required level, but legacy or in-house middleware frequently fails enforcement. The recommended workflow is to run `AuditLevel = 8` for two months [@learn-runasppl], collect CodeIntegrity 3065 / 3066 events, enumerate the failing modules, re-sign or replace them, and only then switch to `RunAsPPL = 1`. Skipping the audit period is the single most common cause of authentication outages during LSA protection rollouts.

Because the threat model PPL answers is *administrator-from-user-mode*, not *administrator-from-kernel-mode*. PPL is a kernel-enforced gate in the access-check pipeline, but a kernel-mode driver that can write to `EPROCESS.Protection` can zero the byte and disable the gate for any process. The defense against the kernel-mode attacker is a different mechanism: VBS-isolated credentials in VTL1 (Credential Guard), with HVCI / kernel-mode integrity controls preventing arbitrary kernel-mode code from running in the first place. PPL stops one threat; Credential Guard stops the threat one rung up; and the two are intended to be deployed together (Section 9, Section 11 Item 5).
&lt;p&gt;The arc has run from a single Mimikatz error code to a kernel-enforced lattice, a third-party admission path mediated by ELAM and MVI, an arms race shaped by a single structural insight that the kernel verifies the channel and not the behaviour, and a stacked companion boundary that lives in VTL1 because VTL0 has run out of places to hide a key. PPL is not a security boundary. That classification is not a footnote; it is the most important fact about it, because it tells defenders that the mechanism is exactly as strong as the engineering velocity Microsoft chooses to invest. Deploy it. Stack it with Credential Guard. Monitor for the next bypass.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The kernel verifies the channel. It does not verify the behaviour. Every PPL bypass since 2018 has lived in that seam, every fix has narrowed the channel, and the seam survives because behaviour is, by Rice&apos;s theorem, structurally outside what static signature verification can reason about.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;protected-process-light-the-ppl-signer-hierarchy-from-wintcb-to-antimalware&quot; keyTerms={[
  { term: &quot;Protected Process Light (PPL)&quot;, definition: &quot;A kernel-enforced gating model decorating a process with a structured protection level (Type, Audit, Signer) and rejecting OpenProcess requests from callers below the target&apos;s signer rung.&quot; },
  { term: &quot;_PS_PROTECTION byte&quot;, definition: &quot;The EPROCESS field encoding Type (3 bits), Audit (1 bit), Signer (4 bits) in a single UCHAR; read on every NtOpenProcess.&quot; },
  { term: &quot;Signer rung&quot;, definition: &quot;The four-bit Signer field of _PS_PROTECTION naming the trust tier of a protected process; values include Authenticode, Antimalware, Lsa, Windows, and WinTcb.&quot; },
  { term: &quot;RunAsPPL&quot;, definition: &quot;The HKLM\SYSTEM\CurrentControlSet\Control\Lsa registry knob that launches lsass.exe at PPL/Lsa on the next boot; value 1 anchors the policy in a UEFI variable on Secure Boot machines.&quot; },
  { term: &quot;ELAM&quot;, definition: &quot;Early Launch Anti-Malware driver -- a Microsoft-certified kernel driver that enrolls a vendor&apos;s user-mode signing certificates at PPL/Antimalware via an embedded resource section.&quot; },
  { term: &quot;BYOVDLL&quot;, definition: &quot;Bring Your Own Vulnerable DLL -- a bypass class against signature-gated security mechanisms in which the attacker loads a legitimately signed but historically vulnerable binary and exploits the known vulnerability inside it.&quot; },
  { term: &quot;Credential Guard&quot;, definition: &quot;A VBS-based isolation mechanism that moves NTLM hashes, Kerberos TGT keys, and Cred Manager credentials out of lsass.exe and into LsaIso.exe in VTL1.&quot; },
  { term: &quot;Security boundary (MSRC)&quot;, definition: &quot;Per Microsoft&apos;s published servicing criteria, a logical separation between code and data of security domains at different trust levels; PPL is excluded from this list and treated as defense in depth.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows</category><category>protected-process-light</category><category>lsass</category><category>credential-guard</category><category>kernel-security</category><category>edr</category><category>mimikatz</category><category>security-boundary</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>