<?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: kernel-security</title><description>Posts tagged kernel-security.</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/kernel-security/rss.xml" rel="self" type="application/rss+xml"/><item><title>The Same-Privilege Paradox: Twenty-One Years of Windows Kernel Self-Defense</title><link>https://paragmali.com/blog/the-same-privilege-paradox-twenty-one-years-of-windows-kerne/</link><guid isPermaLink="true">https://paragmali.com/blog/the-same-privilege-paradox-twenty-one-years-of-windows-kerne/</guid><description>PatchGuard, KASLR, KDP, and the Win32k Lockdown are four answers to one paradox -- a defense at the attacker&apos;s privilege cannot succeed in principle. The 2005-2026 trajectory is migration out of the kernel.</description><pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate><content:encoded>
Microsoft has spent twenty-one years defending the Windows kernel from itself. PatchGuard, KASLR, KDP, and the Win32k Lockdown are four answers to a single problem -- the **same-privilege paradox**, that a defense at the attacker&apos;s privilege level cannot succeed in principle. The trajectory is migration: from in-kernel obfuscation (PatchGuard, 2005), to address-space tricks (KASLR 2007, KVA Shadow 2018), to hypervisor-anchored isolation (KDP, 2020), and finally to attack-surface deletion (Win32k filter, 2017). Microsoft&apos;s own Security Servicing Criteria say PatchGuard is not a security boundary [@ms-servicing-criteria], and that admission is the load-bearing premise of every modern Windows kernel mitigation.
&lt;h2&gt;1. If the attacker is already in the kernel, what is left to defend?&lt;/h2&gt;
&lt;p&gt;For three years, a Russian-attributed espionage rootkit called Uroburos ran on Microsoft&apos;s most heavily defended kernel -- the 64-bit Windows kernel with PatchGuard active -- and PatchGuard never made a sound [@gdata-uroburos-blog]. The reason is the one the marketing copy will not tell you: PatchGuard is not, and was never designed to be, a security boundary; Microsoft says so in its own Security Servicing Criteria [@ms-servicing-criteria]. The twenty-one-year history of Windows kernel self-defense is the story of why the answer to &quot;the kernel cannot defend itself from itself&quot; turned out to be &quot;stop trying to defend it from inside.&quot;&lt;/p&gt;
&lt;p&gt;That sentence will read like editorial provocation until you see the architecture. Uroburos did not bypass PatchGuard. It side-stepped it. The rootkit shipped a signed-but-vulnerable copy of Oracle&apos;s &lt;code&gt;VBoxDrv.sys&lt;/code&gt;, used the vulnerability to flip the &lt;code&gt;g_CiEnabled&lt;/code&gt; flag that gates Driver Signature Enforcement, loaded its own unsigned kernel driver, and then operated alongside PatchGuard for three years (2011 -- 2014) without ever modifying anything PatchGuard checked [@gdata-uroburos-blog] [@stmxcsr-turla]. The Stage 2 evolution survey calls this the canonical refutation of the most common reader misconception about PatchGuard: not &quot;PatchGuard was broken&quot; but &quot;PatchGuard&apos;s protected-structure list is, by construction, narrower than the kernel-modification surface.&quot;&lt;/p&gt;

A defense that shares its CPU privilege level with the attacker can in principle always be subverted by an attacker at that privilege level, because every code path and data structure the defense relies on is, by construction, mutable by the attacker. The paradox is not a formal impossibility theorem in the cryptographic sense, but it is the de facto design constraint Microsoft has acknowledged in writing through its Security Servicing Criteria [@ms-servicing-criteria].

A Microsoft kernel feature that periodically verifies a fixed list of kernel structures -- the SSDT, IDT, GDT, syscall MSRs, the in-memory `nt` and `hal` images, and select processor control registers -- and bug-checks the system with stop code `CRITICAL_STRUCTURE_CORRUPTION` (0x109) on mismatch. Introduced April 25, 2005 in Windows XP Professional x64 Edition and Windows Server 2003 x64 Edition; never shipped on x86 [@ms-advisory-932596] [@ms-driver-x64-restrictions]. PatchGuard is an *engineering deterrent*, not a security boundary.
&lt;p&gt;This article covers four mitigations across twenty-one years -- April 25, 2005, when PatchGuard shipped with Windows XP Professional x64 Edition and Windows Server 2003 x64 Edition [@ms-advisory-932596], through June 2026, when kCET and the VTL1-anchored stack are the front line. The four mitigations are PatchGuard (KPP), KASLR (and its 2018 successor KVA Shadow), KDP (Kernel Data Protection), and the two-stage Win32k Lockdown that began in 2012 with &lt;code&gt;DisallowWin32kSystemCalls&lt;/code&gt; and resolved in 2017 with &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; [@ms-syscall-disable-policy] [@ms-syscall-filter-policy]. They do not look like they belong together until you notice the direction. Each generation moves the defense one step further away from where the attacker lives: from in-kernel obfuscation, to address-space tricks, to hypervisor-anchored isolation (VTL1), to attack-surface deletion.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Every meaningful Windows kernel mitigation since 2017 has moved the &lt;em&gt;enforcement&lt;/em&gt; to a privilege level the kernel-mode attacker cannot reach -- hypervisor (VTL1), CPU silicon (KTRR on Apple, kCET shadow stack hardware on Intel / AMD), or out of the syscall surface entirely. The reason is the same-privilege paradox: a defense that lives where the attacker lives cannot, in principle, succeed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Four misconceptions are worth retiring before we start. &lt;strong&gt;First&lt;/strong&gt;, &quot;PatchGuard is the load-bearing kernel-rootkit defense&quot;; in fact, Microsoft says it is not a security boundary at all, and Uroburos operated alongside it for three years. &lt;strong&gt;Second&lt;/strong&gt;, &quot;PatchGuard is x64-only&quot;; the documentation is x64-centric, but in 2026 PatchGuard also runs on 64-bit ARM Windows -- the one architectural truth in the framing is that PatchGuard never shipped on 32-bit Windows. &lt;strong&gt;Third&lt;/strong&gt;, &quot;KASLR is dead because entropy is the variable that matters&quot;; the Hund-Willems-Holz 2013 result and Gruss et al. 2017 generalization showed that &lt;em&gt;randomness&lt;/em&gt; was never the load-bearing defense -- structural unreachability is [@doi-hund-2013] [@gruss-kaiser-pdf]. &lt;strong&gt;Fourth&lt;/strong&gt;, &quot;Win32k Lockdown killed half the LPE class&quot;; the lockdown removes roughly the historically-vulnerable syscall surface &lt;em&gt;from sandboxed renderers specifically&lt;/em&gt;, not from the operating system in general [@pz-breaking-chain].&lt;/p&gt;
&lt;p&gt;To see why Microsoft has spent twenty-one years on a problem that, by their own admission, has no in-kernel answer, we have to go back to April 25, 2005 -- and to the architectural break that made the new contract politically possible.&lt;/p&gt;
&lt;h2&gt;2. Why Microsoft built PatchGuard at all (1998 -- 2005)&lt;/h2&gt;
&lt;p&gt;Before April 2005, the Windows kernel was a public hooking surface &lt;em&gt;by design&lt;/em&gt;. McAfee, Symantec, F-Secure, and Trend Micro patched the System Service Descriptor Table (SSDT), hooked the Interrupt Descriptor Table (IDT), and inline-patched &lt;code&gt;nt!Nt*&lt;/code&gt; system-service routines as legitimate engineering practice. The same primitives, applied with malicious intent, became the rootkit canon of the late 1990s and early 2000s: NTRootkit, FU, Hacker Defender. From the operating system&apos;s point of view, the defender and the attacker were architecturally indistinguishable.&lt;/p&gt;

A kernel data structure on Windows containing function pointers to every system service routine (the `Nt*` functions that implement system calls). On 32-bit Windows, anti-virus vendors routinely patched the SSDT to intercept system calls before the kernel processed them. On x64, modifying the SSDT is prohibited and PatchGuard treats it as a `CRITICAL_STRUCTURE_CORRUPTION` event [@ms-driver-x64-restrictions].
&lt;p&gt;The symmetry was awkward enough in normal operation. It became politically untenable in October 2005, when Mark Russinovich discovered that Sony BMG&apos;s XCP DRM software, shipped on tens of millions of audio CDs, installed an actual cloaking rootkit on consumer Windows machines.Russinovich&apos;s October 31, 2005 Sysinternals post &quot;Sony, Rootkits and Digital Rights Management Gone Too Far&quot; turned a niche kernel-internals topic into national news within a week. The lawsuit settlements and CD recall that followed established, in pop-culture terms, the symmetry between &quot;legitimate kernel hooking&quot; and &quot;malware kernel hooking&quot; that the security industry had been arguing about for years. The XCP code was structurally identical to malware -- it hid files whose names began with &lt;code&gt;$sys$&lt;/code&gt;, modified system calls, and resisted removal -- and it shipped under a Sony certificate.&lt;/p&gt;
&lt;p&gt;What Microsoft needed was an architectural break large enough that they could rewrite the kernel contract without having to honor the old one. They got it from AMD. The x64 architecture, productised as AMD64 and adopted by Intel as EM64T, was Microsoft&apos;s once-in-a-decade chance to publish a new contract incompatible with the old. Windows XP Professional x64 Edition and Windows Server 2003 x64 Edition shipped on April 25, 2005 [@ms-advisory-932596]. The new kernel-mode contract had two enforcement layers. &lt;strong&gt;PatchGuard&lt;/strong&gt; was the engineering enforcement -- the code that periodically inspected the kernel&apos;s most sensitive structures and bug-checked the system on mismatch. &lt;strong&gt;Kernel-Mode Code Signing (KMCS)&lt;/strong&gt; was the policy enforcement -- the rule that production x64 kernels would load only Authenticode-signed drivers.&lt;/p&gt;

The policy on 64-bit Windows that the kernel will load only Authenticode-signed kernel drivers in production (test-signing modes exist for development). KMCS shipped with the same April 2005 release as PatchGuard and is its policy counterpart -- KMCS controls what code enters the kernel; PatchGuard checks the kernel structures the loaded code is expected to leave alone [@ms-driver-x64-restrictions].
&lt;p&gt;The combination did exactly what the AV industry feared. Their entire detection methodology was, by the new contract, illegal on x64. McAfee bought a full-page ad in the &lt;em&gt;Financial Times&lt;/em&gt; in October 2006 to call Microsoft&apos;s behaviour anti-competitive. Symantec joined the EC complaint. The verbatim industry framing was delivered by Vincent Weafer, then Symantec&apos;s senior director of security response, in a &lt;em&gt;CRN&lt;/em&gt; report: &lt;em&gt;&quot;Either everybody has access to the kernel or nobody has access to the kernel -- and we believe in the latter&quot;&lt;/em&gt; [@crn-mcafee-symantec]. Microsoft declined to publish a signed bypass API. By the time the dust settled, the AV-vendor hooking pattern on Windows had been industrially ended.&lt;/p&gt;

Either everybody has access to the kernel or nobody has access to the kernel -- and we believe in the latter. -- Vincent Weafer, Symantec, quoted in CRN, September 25, 2006 [@crn-mcafee-symantec].

McAfee and Symantec argued that Vista x64 plus PatchGuard locked third-party security vendors out of the kernel while Microsoft&apos;s own Windows Defender remained free to ship integrations Microsoft had not exposed to anyone else. The EC investigation eventually closed without forcing Microsoft to expose a signed bypass API. The 2024 CrowdStrike Falcon outage -- where a single bad signature update propagated through a kernel driver and bricked an estimated 8.5 million Windows machines worldwide -- is now widely read, retroactively, as vindication of Microsoft&apos;s 2006 position. The argument that &quot;everybody or nobody&quot; has kernel access turned out to have a third answer: &quot;as few people as possible, with as small a kernel footprint as possible, mediated by user-mode brokers.&quot; That is the design move the rest of this article is about.
&lt;p&gt;The historical record has one quirk worth flagging. No primary 2005 PatchGuard launch document is preserved in Microsoft&apos;s current documentation surface; the earliest official primary is Microsoft Security Advisory 932596 from August 2007, which describes Kernel Patch Protection as protecting &quot;code and critical structures in the Windows kernel from modification by unknown code or data&quot; and announces an upcoming PatchGuard update [@ms-advisory-932596]. The technical detail of what PatchGuard checked was reverse-engineered by the offensive security community before Microsoft documented it.&lt;/p&gt;

gantt
    title Windows kernel self-defense, 2005-2026
    dateFormat YYYY-MM
    section Same-privilege (CPL=0)
    PatchGuard v1            :2005-04, 2008-02
    PatchGuard v2-v3         :2006-11, 2010-10
    PatchGuard v7-v8         :2012-08, 2026-06
    KASLR (8-bit entropy)    :2007-01, 2018-01
    section CPU mediated
    KVA Shadow               :2018-01, 2026-06
    kCET / shadow stack      :2022-09, 2026-06
    section VTL1 anchored
    HVCI                     :2015-07, 2026-06
    kCFG with VBS bitmap     :2017-04, 2026-06
    KDP static plus dynamic  :2020-05, 2026-06
    section Surface deletion
    DisallowWin32kSystemCalls:2012-08, 2017-10
    Win32kSystemCallFilter   :2017-10, 2026-06
&lt;p&gt;So the contract was published, the kernel was no longer a public hooking surface, and Microsoft shipped a feature called PatchGuard that ran inside the kernel and checked the kernel&apos;s most sensitive structures. The question Skywing and skape would publish nine months later was the question everybody in offensive security had been waiting for: &lt;em&gt;how do you defend a kernel from inside the kernel?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;3. PatchGuard v1 and v2: obfuscation as defense (2005 -- 2008)&lt;/h2&gt;
&lt;p&gt;PatchGuard v1 was an engineering answer to a political problem. It worked exactly the way a defense works if you do not state out loud that the attacker is in the same address space: a periodic timer fired, a checksum was recomputed, a mismatch caused the machine to bug-check with stop code &lt;code&gt;CRITICAL_STRUCTURE_CORRUPTION&lt;/code&gt; (0x109), and the assumption was that the cost of figuring out which timer, which checksum, and which DPC handler was high enough to deter casual rootkit authors. And for nine months, that was the story.&lt;/p&gt;

The Windows bug-check stop code raised by PatchGuard when one of its periodic integrity checks detects an unexpected modification to a protected kernel structure. The bug-check call goes through `KeBugCheckEx`, which on later PatchGuard generations is itself a protected structure -- swallowing the bug-check from a hooked `KeBugCheckEx` was one of the four bypass classes Skywing and skape catalogued in 2005 [@uninformed-v3-archive].
&lt;p&gt;What does PatchGuard actually check? The protected-structure list has grown across generations, but the core, as Microsoft documents it for driver authors, has been remarkably stable [@ms-driver-x64-restrictions]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The SSDT and &lt;code&gt;KeServiceDescriptorTable[Shadow]&lt;/code&gt; (the function-pointer tables that dispatch system calls)&lt;/li&gt;
&lt;li&gt;The Interrupt Descriptor Table (IDT), read from the CPU via &lt;code&gt;IDTR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The Global Descriptor Table (GDT), read from the CPU via &lt;code&gt;GDTR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The syscall-related model-specific registers: &lt;code&gt;IA32_LSTAR&lt;/code&gt;, &lt;code&gt;IA32_STAR&lt;/code&gt;, &lt;code&gt;IA32_CSTAR&lt;/code&gt;, and the &lt;code&gt;IA32_SYSENTER_*&lt;/code&gt; family&lt;/li&gt;
&lt;li&gt;The in-memory &lt;code&gt;nt&lt;/code&gt; and &lt;code&gt;hal&lt;/code&gt; kernel images (so you cannot inline-patch &lt;code&gt;nt!NtCreateFile&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KdpStub&lt;/code&gt;, &lt;code&gt;KeBugCheckCallbackHead&lt;/code&gt;, and other kernel call-back tables&lt;/li&gt;
&lt;li&gt;Select processor control registers and debug registers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mechanism: a context block built by &lt;code&gt;nt!KiInitializePatchGuard&lt;/code&gt; at boot, scattered across allocations, XOR-encrypted; a DPC-driven verifier routine that fires at randomized intervals; a per-fire recomputation of expected checksums; a &lt;code&gt;KeBugCheckEx(0x109, ...)&lt;/code&gt; call on any mismatch. The load-bearing property of the design -- the one that drives the rest of the story -- is that &lt;em&gt;the defense lives at CPL=0, alongside the attacker&lt;/em&gt;. The verifier, the keys, the schedule, the bug-check routine itself: all of it lives in the same address space as the rootkit it is meant to detect.&lt;/p&gt;

flowchart TD
    A[Timer fires at random interval] --&amp;gt; B[DPC routine dispatched]
    B --&amp;gt; C[Decrypt scattered context fragment]
    C --&amp;gt; D[Hash protected structures]
    D --&amp;gt; E{Hash matches expected}
    E -- yes --&amp;gt; F[Reschedule next check]
    E -- no --&amp;gt; G[Call KeBugCheckEx 0x109]
    G --&amp;gt; H[System bug-check CRITICAL_STRUCTURE_CORRUPTION]
    F --&amp;gt; A
&lt;p&gt;In December 2005, eight months after PatchGuard shipped, Skywing and skape published &quot;Bypassing PatchGuard on Windows x64&quot; in &lt;em&gt;Uninformed&lt;/em&gt; Volume 3 [@uninformed-v3-archive]. The paper enumerated four architectural bypass classes that would, with minor variations, survive every PatchGuard generation Microsoft has shipped since:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Patch the verifier timer.&lt;/strong&gt; If you control the DPC queue, you can prevent the check from ever firing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hook the verification callback.&lt;/strong&gt; Replace the function pointer the DPC routine is dispatched through.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replace the DPC routine.&lt;/strong&gt; Rewrite the bytes of &lt;code&gt;nt!KiPatchGuardCheckRoutine&lt;/code&gt; itself, before it executes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Swallow the bug-check.&lt;/strong&gt; Hook &lt;code&gt;KeBugCheckEx&lt;/code&gt; so that the eventual mismatch call returns to the attacker&apos;s handler instead of crashing the system.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;code&gt;KiInitializePatchGuard&lt;/code&gt; initialization routine itself uses the &quot;scattered initialization&quot; tradition Microsoft inherited from Windows 2000 -- the context block is not allocated as a single contiguous structure but assembled from fragments at randomized offsets, each XOR-keyed against a derived value the verifier alone reconstructs at check time. The fragments are referenced through call-graph paths designed to be inaccessible to a static reader. This is exactly the &lt;em&gt;engineering cost&lt;/em&gt; layer that Skywing&apos;s 2005 paper would later identify as raising the cost of bypass without affecting any structural bypass class.&lt;/p&gt;
&lt;p&gt;The thesis the &lt;em&gt;Uninformed&lt;/em&gt; paper stated in its abstract was the framing Microsoft would not formally adopt in writing for another twelve years: &lt;em&gt;any&lt;/em&gt; defense at the same privilege as the attacker can be subverted in principle, because the attacker can do anything the defense can do -- including reading the obfuscation key and rewriting the check. The argument is structural, not empirical. Skywing&apos;s contribution was not &quot;we broke PatchGuard&quot;; it was &quot;PatchGuard&apos;s class of defense has a fixed structural ceiling, and the ceiling is below &apos;security boundary.&apos;&quot;&lt;/p&gt;

The biographical pattern that ran through this story is unusual and worth naming explicitly. Skape (Matt Miller) later joined Microsoft and became the lead on multiple mitigation features. Skywing (Ken Johnson) later wrote the bylined MSRC blog post that introduced KVA Shadow in 2018 [@ms-kva-shadow-blog]. Andrea Allievi, who reverse-engineered PatchGuard 8.1 at NoSuchCon 2014 [@allievi-nsc2014], later co-authored *Windows Internals 7e Part 2* and the 2020 KDP launch blog [@ms-kdp-blog]. The pattern is not random: the offensive-research community that proved the same-privilege paradox was the same community Microsoft eventually hired to design the cross-privilege answer.
&lt;p&gt;Microsoft did exactly what you would expect a serious engineering organisation to do when an obfuscation layer is partially peeled back: they added another. PatchGuard v2 shipped in 2006 servicing updates and was inherited by Vista x64 in November 2006. It introduced an XOR-encrypted-and-scattered context, decoy DPC routines, a generalised anti-hook framework that flagged modifications to additional kernel function tables, and randomized timer phase. In January 2007 Skywing published &quot;Subverting PatchGuard Version 2&quot; in &lt;em&gt;Uninformed&lt;/em&gt; Volume 6, walking through the v2 hardening in detail and demonstrating that the same four bypass classes survived [@uninformed-v6-archive]. The engineering cost was raised; the structural ceiling was not.&lt;/p&gt;
&lt;p&gt;It is worth seeing the integrity check as a teaching primitive. The real implementation is hardened with anti-disassembly and anti-debugging tricks that we will not reproduce; the underlying &lt;em&gt;control loop&lt;/em&gt; is plain.&lt;/p&gt;
&lt;p&gt;{`
// Conceptual demonstration only -- the real PatchGuard is far more obfuscated
const protectedStructures = {
  SSDT: &apos;eb2f4c1abe007f29d6c910a9c66e0b21&apos;,
  IDT:  &apos;7c4b48a39b22d5f0a1e4ecb0d80b1c2a&apos;,
  GDT:  &apos;0d1f3a72b9aa6d8a14e88f9d22cc66ab&apos;,
  KeBugCheckEx: &apos;6677aabbccdd0011223344556677ff88&apos;,
};
const expected = {...protectedStructures};&lt;/p&gt;
&lt;p&gt;function hashStructure(name) {
  // In real KPP this is a derived hash over current memory contents
  return protectedStructures[name];
}&lt;/p&gt;
&lt;p&gt;function patchguardCheck() {
  for (const name of Object.keys(expected)) {
    if (hashStructure(name) !== expected[name]) {
      // KeBugCheckEx(CRITICAL_STRUCTURE_CORRUPTION, ...)
      console.log(&apos;BUGCHECK 0x109 on&apos;, name);
      return;
    }
  }
  console.log(&apos;All structures intact -- reschedule&apos;);
}&lt;/p&gt;
&lt;p&gt;// Simulate one tick of the verifier
patchguardCheck();&lt;/p&gt;
&lt;p&gt;// Simulate an attacker modifying SSDT
protectedStructures.SSDT = &apos;ffffffffffffffffffffffffffffffff&apos;;
patchguardCheck();
`}&lt;/p&gt;
&lt;p&gt;The toy is honest about the shape: a verifier walks a fixed list, computes a hash, compares against a stored expected value, calls a bug-check on mismatch. Everything Skywing&apos;s bypass classes targeted -- the verifier&apos;s schedule, the verifier&apos;s code, the expected-hash store, the bug-check primitive -- is sitting in the address space the attacker also writes.&lt;/p&gt;
&lt;p&gt;By January 2007, the pattern was set. Microsoft adds an obfuscation layer; Skywing peels it back; Microsoft adds another. Both sides were right. Microsoft was right that the engineering cost mattered: the AV-vendor hooking pattern was being industrially ended, signed third-party kernel drivers were a much narrower entry point than the old free-for-all, and casual rootkit authors were locked out of the bypass class. Skywing was right that engineering cost is not a security boundary. The next decade would prove both.&lt;/p&gt;
&lt;h2&gt;4. The evolution, generation by generation (2008 -- 2016)&lt;/h2&gt;
&lt;p&gt;Twelve years of cat-and-mouse ran on two parallel tracks. PatchGuard added DPC-based checks in v3 (Vista SP1 / Server 2008, February 2008) [@uninformed-v8-archive], HAL function-table verification and stack-context randomisation in Windows 7 -- 8 (2009 -- 2012), and a context-block ring in Windows 8.1 (2013) -- which Andrea Allievi reverse-engineered at NoSuchCon 2014, again finding four independent bypass paths [@allievi-nsc2014]. Meanwhile, two quieter developments laid the groundwork for what was coming: KASLR shipped on Vista x64 in 2007 [@russinovich-vista-part3], and Jurczyk and Coldwind&apos;s Bochspwn project in 2013 falsified the industry&apos;s assumption that win32k LPE bugs were a tail of accidents [@j00ru-bochspwn-blog].&lt;/p&gt;
&lt;h3&gt;The PatchGuard generation ladder&lt;/h3&gt;
&lt;p&gt;Each generation tightened the engineering cost without changing the structural ceiling. The table below summarises the evolution; the right-most column lists the canonical reverse-engineering primary, which in every generation came from outside Microsoft.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generation&lt;/th&gt;
&lt;th&gt;Year, OS first shipped&lt;/th&gt;
&lt;th&gt;Key delta&lt;/th&gt;
&lt;th&gt;Canonical reverse-engineering primary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;v1&lt;/td&gt;
&lt;td&gt;April 2005, XP x64 / Server 2003 SP1 x64&lt;/td&gt;
&lt;td&gt;Baseline -- single context block, fixed protected-structure list, single DPC&lt;/td&gt;
&lt;td&gt;Skywing &amp;amp; skape, &lt;em&gt;Uninformed&lt;/em&gt; v3, Dec 2005 [@uninformed-v3-archive]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2&lt;/td&gt;
&lt;td&gt;2006 servicing, inherited by Vista x64 Nov 2006&lt;/td&gt;
&lt;td&gt;XOR-encrypted scattered context, decoy DPCs, anti-hook framework&lt;/td&gt;
&lt;td&gt;Skywing, &lt;em&gt;Uninformed&lt;/em&gt; v6, Jan 2007 [@uninformed-v6-archive]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v3&lt;/td&gt;
&lt;td&gt;Vista SP1 / Server 2008, Feb 2008&lt;/td&gt;
&lt;td&gt;Multiple concurrent contexts, randomised timer phase, &lt;code&gt;KeBugCheckEx&lt;/code&gt; self-protection&lt;/td&gt;
&lt;td&gt;Skywing, &lt;em&gt;Uninformed&lt;/em&gt; v8, Sep 2007 [@uninformed-v8-archive]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v7 (Windows 7)&lt;/td&gt;
&lt;td&gt;2009 -- 2010&lt;/td&gt;
&lt;td&gt;HAL function-table verification, stack-context randomisation&lt;/td&gt;
&lt;td&gt;Community RE; no single canonical paper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v8 (Windows 8)&lt;/td&gt;
&lt;td&gt;2012&lt;/td&gt;
&lt;td&gt;&lt;code&gt;KeServiceDescriptorTableShadow&lt;/code&gt; added (now covers win32k syscall table), expanded MSR list&lt;/td&gt;
&lt;td&gt;Community RE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v8.1&lt;/td&gt;
&lt;td&gt;2013 (Windows 8.1)&lt;/td&gt;
&lt;td&gt;Single context block replaced by &lt;strong&gt;context-block ring&lt;/strong&gt;; atomic patching of every block required; 247 protected structures (vs ~26 on Vista x64)&lt;/td&gt;
&lt;td&gt;Andrea Allievi, NoSuchCon 2014 [@allievi-nsc2014]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Allievi&apos;s 2014 talk is the clearest single picture of what hardening looked like by the Windows 8.1 era. The single context block had become a singly-linked list (SLIST) of context blocks. The cryptographic self-integrity check now ran across the SLIST. The protected-structure set had grown from roughly twenty-six on Vista x64 to &lt;strong&gt;two hundred and forty-seven&lt;/strong&gt; by Windows 8.1, including &lt;code&gt;HalPrivateDispatchTable&lt;/code&gt; and &lt;code&gt;HalpInterruptController&lt;/code&gt; [@allievi-nsc2014]. And the four 2006 bypass classes still worked. The engineering cost of bypassing PatchGuard had risen by an order of magnitude; the architectural class of bypass had not changed.&lt;/p&gt;
&lt;h3&gt;KASLR on Vista, February -- April 2007&lt;/h3&gt;
&lt;p&gt;In parallel with the PatchGuard generation ladder, Microsoft shipped a different style of defense on the same kernel. Mark Russinovich&apos;s three-part &lt;em&gt;Inside the Windows Vista Kernel&lt;/em&gt; series in TechNet Magazine documented the new mitigation in April 2007 [@russinovich-vista-part3]: the kernel image base, instead of being constant, was selected at boot from a small space of possible offsets.&lt;/p&gt;

Randomising the kernel image base across boots, so that an attacker with a stale or guessed kernel address cannot use it as an absolute reference. On Vista x64 the implementation had roughly eight bits of entropy (256 possible kernel base addresses), selected at boot time by `winload.exe` [@russinovich-vista-part3]. The mitigation is *probabilistic* by construction: it raises the cost of an unprivileged information-leak, but cannot survive a deterministic side-channel attacker.
&lt;p&gt;The Vista bootloader, &lt;code&gt;winload.exe&lt;/code&gt;, was the component that picked the kernel image base at boot. The choice of selecting the offset early -- before the kernel proper executes -- was deliberate; KASLR after the kernel is mapped is harder to do because every kernel pointer recorded so far becomes invalid. The Vista bootloader was also the component PatchGuard&apos;s protected list depended on: an attacker with bootloader code execution simply chose their own offset.&lt;/p&gt;
&lt;p&gt;The probabilistic framing held until 2013. Hund, Willems, and Holz published &quot;Practical Timing Side Channel Attacks Against Kernel Space ASLR&quot; at IEEE S&amp;amp;P 2013 [@doi-hund-2013]. Their technique exploited the shared TLB and cache state between user mode and kernel mode on every x86 / x64 CPU then shipping: an unprivileged user-mode timer could measure differential cache behaviour when accessing addresses near where the kernel mapped its image, and recover the kernel base in seconds. Eight bits of entropy collapse fast under a side-channel that gives you one bit per probe. Gruss et al. generalised the argument in 2017 with a paper whose title was the thesis: &lt;em&gt;&quot;KASLR is Dead: Long Live KASLR&quot;&lt;/em&gt; [@gruss-kaiser-pdf]. The structural answer would have to be something other than entropy.&lt;/p&gt;
&lt;h3&gt;The 2012 Windows 8 attempt at attack-surface deletion&lt;/h3&gt;
&lt;p&gt;While KASLR&apos;s structural limits were being demonstrated in academia, Microsoft shipped a different style of mitigation in Windows 8: &lt;code&gt;DisallowWin32kSystemCalls&lt;/code&gt;, a process-level option enabling the kernel to refuse &lt;em&gt;every&lt;/em&gt; win32k system call from a process that opted in [@ms-syscall-disable-policy]. The semantics are all-or-nothing: a process either can call into &lt;code&gt;win32k.sys&lt;/code&gt; or it cannot. Useful for non-UI broker processes (where the answer is &quot;never&quot;). Structurally inadequate for browser renderers, which need to draw windows, render fonts, and dispatch input through a constrained-but-non-empty subset of the win32k surface. The mitigation languished for five years, waiting for the per-syscall version that arrived in 2017.&lt;/p&gt;
&lt;h3&gt;The Bochspwn empirical surprise&lt;/h3&gt;
&lt;p&gt;In 2013, Mateusz Jurczyk and Gynvael Coldwind presented Bochspwn at SyScan and at Black Hat USA [@j00ru-bochspwn-blog] [@j00ru-bhusa-pdf]. The methodology was a Bochs x86 emulator instrumented to trace every memory access made by the kernel during syscall handling. The instrumentation found classes of bugs -- specifically &lt;em&gt;double-fetch&lt;/em&gt; bugs, where the kernel reads the same user-controlled memory twice without re-validating between reads -- by tagging each user-pointer dereference and looking for repeats.&lt;/p&gt;

A double-fetch happens when kernel code reads a value from user-mode memory, validates it, and later reads the *same* address again expecting the value to be unchanged. A racing user-mode thread can flip the value between the two reads, defeating the validation. Detecting double-fetches statically is hard; detecting them by static analysis on a closed-source kernel is harder still. Bochspwn solved the detection problem at the emulator level: instrument the entire kernel under Bochs, log every memory read of every page table mapped writable from user mode, and post-process the trace for &quot;same address, same kernel function, two reads, no intervening synchronisation.&quot; The result: dozens of exploitable kernel race conditions across multiple Windows versions, the *majority* in `win32k.sys` [@j00ru-bochspwn-blog]. The win32k bug class was systemic, not accidental.
&lt;p&gt;Jurczyk&apos;s empirical finding mattered because it pre-dated the design of the eventual lockdown by four years. The community knew, by mid-2013, that &lt;em&gt;win32k.sys was a bug class, not a bug tail&lt;/em&gt;. Microsoft&apos;s eventual answer -- per-process filtering of the win32k syscall surface -- had a clean empirical motivation by the time it shipped.&lt;/p&gt;
&lt;p&gt;The pre-Bochspwn high-profile example was already in the literature: Bruce Dang and Peter Ferrie&apos;s December 2010 talk at the 27th Chaos Communication Congress (&quot;Adventures in Analyzing Stuxnet&quot;) had named CVE-2010-2743, a &lt;code&gt;win32k.sys&lt;/code&gt; &lt;code&gt;NtUserLoadKeyboardLayoutEx&lt;/code&gt; LPE that Stuxnet used to escalate from user to kernel on Windows XP [@nvd-cve-2010-2743]. Stuxnet placed one of the most consequential kernel-level malware operations on record on top of a single win32k vulnerability. Bochspwn explained why: the surface was structurally vulnerable, not accidentally so.&lt;/p&gt;
&lt;h3&gt;The intellectual surprise of this act -- Uroburos coexisted with PatchGuard&lt;/h3&gt;
&lt;p&gt;The cleanest demonstration that the same-privilege paradox is empirical, not theoretical, came in February 2014. G Data SecurityLabs published its analysis of Uroburos, a Russian-attributed espionage rootkit that had been operating in production for an estimated three years [@gdata-uroburos-blog]. Uroburos did not bypass PatchGuard. It loaded a copy of Oracle&apos;s &lt;code&gt;VBoxDrv.sys&lt;/code&gt; (a signed third-party driver shipped as part of VirtualBox), used a privilege-escalation vulnerability in that driver to flip the &lt;code&gt;g_CiEnabled&lt;/code&gt; flag (the gate for Driver Signature Enforcement), loaded its own unsigned rootkit driver, and then operated for three years in production &lt;em&gt;without ever modifying anything PatchGuard checked&lt;/em&gt; [@stmxcsr-turla].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The most-repeated misreading of PatchGuard&apos;s track record is &quot;Uroburos was a PatchGuard bypass.&quot; It was not. Uroburos was a Driver Signature Enforcement (DSE) bypass that operated &lt;em&gt;alongside&lt;/em&gt; PatchGuard for three years (2011 -- 2014) without modifying any PatchGuard-protected structure [@gdata-uroburos-blog] [@stmxcsr-turla]. The lesson is structural: PatchGuard&apos;s protected-structure list is, by construction, narrower than the kernel-modification surface, and a disciplined attacker simply stays outside the list. The corollary -- that no in-kernel integrity monitor can be wider than its protected-structure list, and any list narrower than &quot;all kernel memory&quot; leaves gaps -- is the empirical anchor for the same-privilege paradox.&lt;/p&gt;
&lt;/blockquote&gt;

The policy on 64-bit Windows that the kernel will load only Authenticode-signed drivers in production. DSE is gated by an in-memory flag (`nt!g_CiEnabled` historically, `nt!g_CiOptions` on later builds). An attacker with arbitrary kernel write can flip the flag and load unsigned drivers -- which is precisely how the BYOVD attack pattern works [@gdata-uroburos-blog] [@hfiref0x-upgdsed].
&lt;p&gt;Three insights converged from this act. From the side-channel KASLR literature: some defenses cannot succeed at CPL=0 because the &lt;em&gt;attack&lt;/em&gt; is below the operating system. From Allievi 2014 and Uroburos 2011 -- 2014: same-privilege obfuscation is permanently bounded by engineering cost, no matter how much engineering cost you pay. From Bochspwn: win32k is not a bug tail but a bug class -- the only structural answer is to delete the surface rather than defend it. The 2017 calendar year was about to land all three answers at once.&lt;/p&gt;
&lt;h2&gt;5. 2017&apos;s triple inflection&lt;/h2&gt;
&lt;p&gt;In a single calendar year, three mutually independent breakthroughs reshaped kernel self-defense. June 2017: CyberArk&apos;s Kasif Dekel published GhostHook, an Intel-PT-based PatchGuard bypass that forced Microsoft&apos;s first public statement that PatchGuard is not a security boundary [@cyberark-ghosthook]. July 2017: Gruss et al. published &quot;KASLR is Dead: Long Live KASLR&quot; at ESSoS, proposing kernel page-table isolation as the structural answer [@gruss-kaiser-pdf]. October 2017: Windows 10 1709 shipped &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt;, the per-process, per-syscall allow-list designed for the Chrome and Edge renderer sandboxes [@ms-syscall-filter-policy]. Three teams, three mitigations, three facets of the same paradox.&lt;/p&gt;
&lt;h3&gt;Win32kSystemCallFilter (October 17, 2017)&lt;/h3&gt;
&lt;p&gt;The Windows 8 mitigation &lt;code&gt;DisallowWin32kSystemCalls&lt;/code&gt; had been the right idea applied as a meat-axe: an opted-in process loses access to &lt;em&gt;every&lt;/em&gt; win32k system call. Windows 10 1709 introduced the surgical version. &lt;code&gt;PROCESS_MITIGATION_SYSTEM_CALL_FILTER_POLICY&lt;/code&gt; registers a per-process bitmap of system-defined &lt;code&gt;FilterId&lt;/code&gt; values that the process is allowed to call; everything outside the bitmap is denied [@ms-syscall-filter-policy]. The filter is applied via &lt;code&gt;UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, ...)&lt;/code&gt; at &lt;code&gt;CreateProcess&lt;/code&gt; time -- not at runtime.&lt;/p&gt;

A Windows 10 1709+ process-mitigation policy (`PROCESS_MITIGATION_SYSTEM_CALL_FILTER_POLICY`, header `ntddk.h`) that registers a per-process bitmap of allowed system-defined `FilterId` values for win32k system calls. Calls outside the bitmap terminate the calling process. Used by the Chromium sandbox to constrain the win32k surface available to a renderer process [@ms-syscall-filter-policy] [@chromium-sandbox-doc].
&lt;p&gt;The &quot;at CreateProcess time, not at runtime&quot; detail is load-bearing. James Forshaw and Ivan Fratric&apos;s November 2016 Project Zero post &quot;Breaking the Chain&quot; documented how Edge&apos;s window-broker architecture, which applied syscall restrictions to a child process &lt;em&gt;after&lt;/em&gt; it had started, was subject to a window-of-opportunity race between the child&apos;s earliest syscall and the broker&apos;s policy application [@pz-breaking-chain]. If the policy is not in place by the time the first attacker-controlled syscall fires, the policy has not happened. The lesson the Windows 10 1709 design banked: mitigations belong on the &lt;code&gt;CreateProcess&lt;/code&gt; boundary, not on a later thread.&lt;/p&gt;

sequenceDiagram
    participant R as Renderer process (VTL0 user)
    participant SD as Syscall dispatcher (kernel)
    participant W as win32k handler
    participant EP as EPROCESS filter bitmap
    R-&amp;gt;&amp;gt;SD: NtUser/NtGdi syscall with FilterId N
    SD-&amp;gt;&amp;gt;EP: Consult per-process filter bitmap
    EP--&amp;gt;&amp;gt;SD: bit N set or unset
    alt FilterId allowed
        SD-&amp;gt;&amp;gt;W: Dispatch to win32k handler
        W--&amp;gt;&amp;gt;R: Return result
    else FilterId denied
        SD-&amp;gt;&amp;gt;R: Terminate process via fast-fail
    end
&lt;p&gt;The Forshaw / Fratric Edge race is a textbook case of why &quot;apply at runtime&quot; is a security anti-pattern for process mitigations. The Microsoft Edge of late 2016 used a sandbox model in which a renderer process started with limited restrictions and then upgraded itself to the full lockdown profile after initialisation. Forshaw and Fratric showed that an attacker who landed code execution before the upgrade completed -- a window of milliseconds -- could simply not upgrade. The lesson generalises beyond Edge: every per-process mitigation in modern Windows is applied at process creation time precisely so there is no window the attacker can race [@pz-breaking-chain].&lt;/p&gt;
&lt;p&gt;The cleanest way to see the two-mitigation contrast is side by side:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;&lt;code&gt;DisallowWin32kSystemCalls&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Win32kSystemCallFilter&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Chromium&apos;s actual choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;First shipped&lt;/td&gt;
&lt;td&gt;Windows 8, 2012 [@ms-syscall-disable-policy]&lt;/td&gt;
&lt;td&gt;Windows 10 1709, October 2017 [@ms-syscall-filter-policy]&lt;/td&gt;
&lt;td&gt;Both, in different process types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Granularity&lt;/td&gt;
&lt;td&gt;All-or-nothing&lt;/td&gt;
&lt;td&gt;Per-syscall allow-list&lt;/td&gt;
&lt;td&gt;Blanket-disable for non-UI; per-syscall for renderer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mitigation policy struct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PROCESS_MITIGATION_SYSTEM_CALL_FILTER_POLICY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Composes both with LPAC privilege reduction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use case&lt;/td&gt;
&lt;td&gt;Non-UI broker processes (GPU broker, network process)&lt;/td&gt;
&lt;td&gt;Renderer processes that draw windows&lt;/td&gt;
&lt;td&gt;The renderer needs a constrained-but-non-zero win32k subset [@chromium-sandbox-doc]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The Chromium sandbox composes the two mitigations with one more: the &lt;strong&gt;Less Privileged AppContainer&lt;/strong&gt; (LPAC). LPAC removes ambient access to user data, the network, and most named-object namespaces; &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; removes the syscall surface; &lt;code&gt;DisallowWin32kSystemCalls&lt;/code&gt; applies to processes that need no UI at all. Defense in depth at the surface level rather than the structural level.&lt;/p&gt;

A Windows AppContainer variant introduced in Windows 10 that further restricts the ambient capabilities available to the contained process -- no access to user files, no access to most named objects, restricted ability to enumerate the system. Combined with `Win32kSystemCallFilter`, LPAC gives the Chromium renderer a process model in which both *what the renderer can ask the kernel to do* and *what the renderer can see in user mode* are deliberately narrow [@chromium-sandbox-doc].
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; is the first mitigation in the 21-year arc that &lt;em&gt;deletes&lt;/em&gt; attack surface rather than defending it. PatchGuard and KASLR are kernel defenses: they live inside the kernel and protect kernel state. The win32k filter is a process-mitigation policy enforced by the kernel&apos;s system-call dispatcher at the syscall boundary. The protection is realised by &lt;em&gt;not letting the kernel be called&lt;/em&gt; rather than by checking the kernel&apos;s state afterwards. Once you see this shape, the rest of the modern Windows mitigation stack -- KDP, kCFG-with-VBS-bitmap, kCET -- becomes legible as variations on the same move: put the enforcement outside the attacker&apos;s reach.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;KAISER and the page-table split&lt;/h3&gt;
&lt;p&gt;In July 2017, Gruss et al. presented &quot;KASLR is Dead: Long Live KASLR&quot; at ESSoS [@gruss-kaiser-pdf]. The acronym was &lt;strong&gt;KAISER&lt;/strong&gt; -- Kernel Address Isolation to have Side-channels Efficiently Removed. The architecture is simple to describe, hard to engineer, and devastating to a side-channel attacker.&lt;/p&gt;
&lt;p&gt;A modern x64 kernel runs in the same virtual address space as the calling user process, distinguished by privilege bits in page-table entries. A syscall does not change the page tables; it only changes the privilege level. The TLB is therefore shared between user and kernel mappings, and side-channel attacks like Hund 2013 work by timing the resulting cache and TLB behaviour. KAISER&apos;s answer was to give each process &lt;em&gt;two&lt;/em&gt; sets of page tables: a &quot;user&quot; CR3 in which the kernel address space is &lt;em&gt;not mapped&lt;/em&gt;, and a &quot;kernel&quot; CR3 in which the full virtual address space is mapped. The syscall entry path switches from user CR3 to kernel CR3; the sysret path switches back. The kernel address space is not just unknown to a user-mode attacker -- it is structurally unreachable.&lt;/p&gt;

A design proposed by Gruss et al. (KAISER, ESSoS 2017) [@gruss-kaiser-pdf] in which each process has two page-table hierarchies: a user CR3 that does not map the kernel and a kernel CR3 that maps both. CR3 is switched on every syscall entry and exit. The kernel is no longer just *hard to find* (the KASLR posture); it is *unreachable* from user CR3 (the structural posture). Linux shipped KAISER as KPTI in early 2018; Microsoft shipped a re-engineered variant as KVA Shadow [@ms-kva-shadow-blog].

sequenceDiagram
    participant U as User-mode thread
    participant CPU as CPU CR3
    participant K as Kernel
    U-&amp;gt;&amp;gt;CPU: syscall (SYSCALL instruction)
    CPU-&amp;gt;&amp;gt;CPU: Switch CR3 from user to kernel
    CPU-&amp;gt;&amp;gt;K: Kernel now mapped, enter system service
    K-&amp;gt;&amp;gt;K: Handle request
    K-&amp;gt;&amp;gt;CPU: SYSRET
    CPU-&amp;gt;&amp;gt;CPU: Switch CR3 back to user
    CPU-&amp;gt;&amp;gt;U: Return to user mode, kernel unmapped
&lt;p&gt;The Gruss paper landed six months before anyone knew why it mattered. Then, on January 3, 2018, Jann Horn published &quot;Reading privileged memory with a side-channel&quot; on Project Zero [@pz-meltdown-post], the same day the academic teams (Lipp et al., independently) published the Meltdown disclosure [@usenix-lipp-meltdown]. Meltdown -- CVE-2017-5754, &quot;rogue data cache load&quot; -- exploited transient out-of-order execution on Intel CPUs to read kernel memory from user mode. The only structural fix was to ensure the kernel pages were not present in the user-mode page table. KAISER&apos;s design, drafted as a generic side-channel countermeasure, was suddenly Meltdown&apos;s required mitigation.&lt;/p&gt;
&lt;h3&gt;GhostHook and the formal admission&lt;/h3&gt;
&lt;p&gt;In June 2017, Kasif Dekel published GhostHook [@cyberark-ghosthook]. The mechanism is elegant. Intel Processor Trace (Intel PT) is a CPU feature for low-overhead recording of control flow, designed for performance analysis and debugging. The trace is written to a Table of Physical Addresses (ToPA), and when a configured ToPA region fills, the CPU raises a performance-monitoring interrupt (PMI). The OS&apos;s PMI handler is a function pointer. PMI handlers run in kernel mode, with full kernel privilege. GhostHook configured Intel PT with a tiny ToPA covering an address near &lt;code&gt;IA32_LSTAR&lt;/code&gt; (the syscall entry MSR), arranged for the buffer to fill immediately, and registered an attacker-controlled PMI handler. Every kernel transition fired the PMI; the attacker&apos;s handler ran first. PatchGuard does not enumerate Intel PT. By design.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s response, as reported in the CyberArk write-up, was the formal end of an eleven-year ambiguity. PatchGuard is &quot;considered an in-depth security feature&quot; but not a security boundary; the GhostHook bypass would &quot;be considered for a future version of Windows&quot; but did not warrant an out-of-band fix [@cyberark-ghosthook]. The Microsoft position aligns with the Security Servicing Criteria: admin-to-kernel is not a security boundary, and an attacker who has already reached kernel mode (the precondition for installing a GhostHook-style PMI handler) is outside the scope of what PatchGuard exists to prevent [@ms-servicing-criteria].&lt;/p&gt;

While the technique was found to bypass PatchGuard, Microsoft has graciously agreed to consider [the issue] for a future version of Windows. As such, no immediate risk exists for customers. -- Microsoft response to GhostHook, June 2017 [@cyberark-ghosthook].
&lt;p&gt;The three breakthroughs of 2017 were structurally aligned. &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; deleted the most-vulnerable syscall surface from sandboxed renderers. KAISER&apos;s page-table split made KASLR&apos;s probabilistic defense obsolete and structurally unreachable. GhostHook forced the public admission that the same-privilege class of defense has a ceiling Microsoft already knew about. And then, on the morning of January 3, 2018, the academic paper of six months earlier became an emergency engineering deliverable.&lt;/p&gt;
&lt;h2&gt;6. State of the art: KDP, KVA Shadow, kCFG, kCET, and the Secure Kernel shift (2018 -- 2026)&lt;/h2&gt;
&lt;p&gt;January 3, 2018: Meltdown&apos;s public disclosure forces every major operating system to ship page-table isolation within weeks [@pz-meltdown-post]. Microsoft&apos;s response, &lt;strong&gt;KVA Shadow&lt;/strong&gt;, ships in the Windows 10 1709 cumulative security update the same day. The engineering write-up is bylined to Ken Johnson of the Microsoft Security Response Center [@ms-kva-shadow-blog]. The same Ken Johnson who, twelve years earlier, co-authored &lt;em&gt;Bypassing PatchGuard on Windows x64&lt;/em&gt; under the name Skywing [@uninformed-v3-archive]. The offensive-research outsider had become the bylined Microsoft defender. The same loop was about to close on the architectural question: &lt;em&gt;where, exactly, does the defense live?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Ken Johnson / Skywing trajectory -- offensive Uninformed paper in 2005, the bylined MSRC blog post in 2018, twelve years later -- is the cleanest single illustration of the offensive-research-to-Microsoft pattern. He is engineering credit attributed to Ken Johnson on the MSRC byline; the offensive identity is widely known but not asserted by Microsoft. Either reading of the byline is valid; the structural point is that the same person whose 2005 paper identified the architectural ceiling of CPL=0 obfuscation later shipped the cross-privilege answer for Meltdown [@uninformed-v3-archive] [@ms-kva-shadow-blog].&lt;/p&gt;
&lt;h3&gt;KVA Shadow: the productisation of KAISER&lt;/h3&gt;
&lt;p&gt;KVA Shadow is the Windows productisation of KAISER. Two CR3-loadable page tables per process: a user-mode shadow that does not map most of the kernel, and a kernel-mode page table that does. CR3 is switched on every syscall entry and exit. The kernel address space is unmapped from user CR3 [@ms-kva-shadow-blog]. The structural Meltdown fix is exact: a Meltdown-class transient read of a kernel address from user mode now hits an unmapped page-table entry and raises a fault before any cached side-channel evidence is produced.&lt;/p&gt;
&lt;p&gt;Two things to be precise about. First, KVA Shadow addresses &lt;strong&gt;Variant 3&lt;/strong&gt; (Meltdown, CVE-2017-5754) only. Spectre Variant 1 (CVE-2017-5753), Variant 2 (CVE-2017-5715), and Variant 4 (Speculative Store Bypass) require their own mitigations (microcode updates, retpoline, IBRS / IBPB, SSBD); KVA Shadow does nothing for them [@usenix-lipp-meltdown]. Second, the performance cost of the CR3-switch on every syscall is real -- Fortinet&apos;s analysis of the KVA Shadow build measured significant slowdowns for syscall-heavy workloads, mitigated on newer CPUs by Process-Context Identifiers (PCID) that keep TLB entries valid across CR3 switches [@fortinet-kva-shadow].&lt;/p&gt;
&lt;h3&gt;HVCI: the VTL1 enabler&lt;/h3&gt;
&lt;p&gt;Hypervisor-Protected Code Integrity (HVCI) is not, strictly, a kernel defense -- it is the foundation everything else in the modern stack stands on. HVCI uses Virtualization-Based Security (VBS) to run a small Secure Kernel in Virtual Trust Level 1 (VTL1), one privilege level above the NT kernel in VTL0. The Secure Kernel manages the Second-Level Address Translation (SLAT) page tables -- Intel EPT or AMD NPT -- that mediate physical memory access for the NT kernel. With HVCI on, kernel pages are managed W^X (writable XOR executable): a kernel-mode driver attempting to make a writable page executable triggers a SLAT fault that VTL1 catches.&lt;/p&gt;

A Windows architecture in which the hypervisor partitions the system into two Virtual Trust Levels. VTL0 hosts the normal NT kernel, drivers, and user-mode processes. VTL1 hosts a Secure Kernel and a small set of trustlets that enforce policy on VTL0. Cross-VTL transitions are mediated by the hypervisor; a VTL0 kernel-mode attacker cannot reach VTL1, even with arbitrary kernel write. VBS is the architectural primitive that makes HVCI, KDP, and kCFG-with-VBS-bitmap possible [@ms-kdp-blog].
&lt;p&gt;For this article HVCI is the cross-cutting dependency: it is what makes KDP and the VBS-protected kCFG bitmap work. Once you have a hypervisor enforcing SLAT on the NT kernel, every defense you want to anchor &lt;em&gt;outside&lt;/em&gt; the NT kernel has a home.&lt;/p&gt;
&lt;h3&gt;KDP: static and dynamic kernel data protection&lt;/h3&gt;
&lt;p&gt;Microsoft announced Kernel Data Protection on July 8, 2020, with Windows 10 version 2004 [@ms-kdp-blog]. Two flavours.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static KDP&lt;/strong&gt; uses the &lt;code&gt;MmProtectDriverSection&lt;/code&gt; API, called from &lt;code&gt;DriverEntry&lt;/code&gt;, to mark a section of the driver&apos;s image as read-only for the rest of the kernel&apos;s lifetime. The intended use is for tables of policy data the driver expects never to modify after initialisation: function-pointer arrays, configuration constants, signed policy blobs. Once &lt;code&gt;MmProtectDriverSection&lt;/code&gt; returns, the section&apos;s pages are tagged read-only in the VTL1-managed SLAT; a VTL0 kernel-mode attempt to write them takes a hardware page fault that VTL0 has no way to relax.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic KDP&lt;/strong&gt; is for runtime-allocated state. The canonical API is &lt;code&gt;ExAllocatePool3&lt;/code&gt;, called with a &lt;code&gt;POOL_EXTENDED_PARAMETER&lt;/code&gt; array containing a &lt;code&gt;POOL_EXTENDED_PARAMS_SECURE_POOL&lt;/code&gt; extended parameter [@ms-kdp-blog]. The flags &lt;code&gt;SECURE_POOL_FLAGS_FREEABLE&lt;/code&gt; (1) and &lt;code&gt;SECURE_POOL_FLAG_MODIFIABLE&lt;/code&gt; (2) control whether the allocation can later be freed and whether further protected modifications are permitted. The secure-pool extension routes the allocation through the Secure Kernel; the resulting memory is verified by VTL1 and protected by SLAT.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; KDP does &lt;em&gt;not&lt;/em&gt; automatically protect &quot;all kernel memory.&quot; It protects exactly the memory a driver author opts in to protect via &lt;code&gt;MmProtectDriverSection&lt;/code&gt; (static) or &lt;code&gt;ExAllocatePool3&lt;/code&gt; with the secure-pool extension (dynamic) [@ms-kdp-blog]. Memory allocated through the normal &lt;code&gt;ExAllocatePool2&lt;/code&gt; path is &lt;em&gt;not&lt;/em&gt; KDP-protected. A defender architecting around KDP must explicitly opt the data they care about into the secure pool; the protection is targeted, not blanket.&lt;/p&gt;
&lt;/blockquote&gt;

A Microsoft kernel-memory protection introduced with Windows 10 version 2004 (July 2020) that allows drivers to mark sections of kernel memory as read-only and have the protection enforced by the Secure Kernel in VTL1 via the SLAT page tables. Static KDP uses `MmProtectDriverSection`; Dynamic KDP uses `ExAllocatePool3` with a `POOL_EXTENDED_PARAMS_SECURE_POOL` extended parameter passed via `POOL_EXTENDED_PARAMETER`. The enforcement lives at a privilege level the VTL0 attacker cannot reach [@ms-kdp-blog].
&lt;p&gt;The Microsoft launch blog makes the architectural point in one sentence: &lt;em&gt;&quot;the memory managed by KDP is always verified by the secure kernel (VTL1) and protected using SLAT tables by the hypervisor&quot;&lt;/em&gt; [@ms-kdp-blog]. This is the first kernel self-defense mitigation in the Windows lineage whose enforcement is &lt;em&gt;structurally&lt;/em&gt; outside the NT kernel. A VTL0 attacker with arbitrary kernel write &lt;em&gt;cannot&lt;/em&gt; relax the SLAT entry that protects a KDP-tagged page, because the SLAT entry is managed by VTL1, and VTL1 is not in VTL0&apos;s address space.&lt;/p&gt;

flowchart TD
    A[VTL0 NT kernel plus attacker driver] --&amp;gt;|attempt write to KDP-protected page| B[CPU memory access]
    B --&amp;gt; C[SLAT page table consulted]
    C --&amp;gt; D{SLAT entry writable for VTL0}
    D -- no, RO by VTL1 --&amp;gt; E[Hardware EPT or NPT fault]
    D -- yes --&amp;gt; F[Write succeeds]
    E --&amp;gt; G[Secure Kernel in VTL1 receives fault]
    G --&amp;gt; H[VTL0 attacker has no path to relax SLAT entry]
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The canonical pre-boot PatchGuard bypass, EfiGuard, is a UEFI bootkit that patches the loaded kernel image to disable PatchGuard and DSE before the kernel runs [@mattiwatti-efiguard]. It works precisely because PatchGuard, DSE, and the kernel image all live in VTL0 -- a pre-boot agent has the same architectural reach. But once the system boots into a VBS-enabled configuration, the SLAT enforcement lives in VTL1, and the launching firmware does &lt;em&gt;not&lt;/em&gt; have VTL1&apos;s privileges. The same attacker that defeats PatchGuard at the kernel level cannot defeat HVCI from the same vantage. This is the cleanest cross-mitigation demonstration that the architectural-layer choice -- &quot;which privilege level does the defense live at?&quot; -- is the load-bearing variable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;kCFG: forward-edge integrity&lt;/h3&gt;
&lt;p&gt;Control Flow Guard (CFG) is Microsoft&apos;s compiler-assisted forward-edge CFI. Every indirect call is replaced by a check against a bitmap of valid call targets; an invalid target raises a fast-fail [@ms-cfg]. The kernel variant -- &lt;strong&gt;kCFG&lt;/strong&gt; -- is enabled by &lt;code&gt;/guard:cf&lt;/code&gt; and protects indirect calls in &lt;code&gt;ntoskrnl&lt;/code&gt; and CFG-compiled drivers. With HVCI on, the CFG bitmap is stored in VTL1-protected memory; a VTL0 attacker who can write arbitrary kernel pages still cannot tamper with the bitmap. kCFG defeats jump-oriented and call-oriented programming (JOP / COP) against the forward edge. It does nothing for the backward edge.&lt;/p&gt;
&lt;h3&gt;kCET: backward-edge integrity in hardware&lt;/h3&gt;
&lt;p&gt;Kernel-mode hardware-enforced stack protection (informally &lt;strong&gt;kCET&lt;/strong&gt;, formally documented as &quot;Kernel Mode Hardware-enforced Stack Protection&quot;) closes the backward edge using the Intel CET and AMD Shadow Stack hardware features [@ms-kernel-mode-hsp]. A CPU-maintained shadow stack records every &lt;code&gt;CALL&lt;/code&gt; return address; every &lt;code&gt;RET&lt;/code&gt; validates the popped address against the shadow stack and fast-fails on mismatch. The shadow-stack pages are marked Shadow Stack in the kernel-mode PTE, which the CPU enforces directly; with VBS on, the Secure Kernel additionally locks the shadow-stack mappings against VTL0 write.&lt;/p&gt;
&lt;p&gt;kCET requires Intel 11th-generation Tiger Lake or later, or AMD Zen 3 or later, plus VBS and HVCI [@ms-kernel-mode-hsp]. It is off-by-default on Windows Server 2025 because enabling it system-wide requires every loaded driver to be compiled with the &lt;code&gt;/CETCOMPAT&lt;/code&gt; flag; a single non-&lt;code&gt;/CETCOMPAT&lt;/code&gt; driver disables kCET for the entire system at load time. As of June 2026, the rollout is gated on driver vendor adoption.&lt;/p&gt;
&lt;p&gt;An adjacent technique worth knowing about by name is &lt;strong&gt;eXtended Flow Guard (XFG)&lt;/strong&gt;. XFG augmented kCFG&apos;s bitmap-membership check with a per-function type-derived 64-bit hash compared at the call site -- a defense that detects not just &quot;is this target valid?&quot; but &quot;is this target the &lt;em&gt;right&lt;/em&gt; target for this call&apos;s signature?&quot; XFG was prototyped in MSVC and partially shipped on Windows 10 Insider builds, but the instrumentation never reached full inbox-kernel coverage and the feature is no longer Microsoft&apos;s strategic investment direction. The shipping equivalent on 2026 hardware is kCET for the backward edge plus kCFG for the forward edge.&lt;/p&gt;
&lt;p&gt;Connor McGarr&apos;s Black Hat USA 2025 deck, &quot;Out of Control: KCFG and KCET,&quot; documents the 2026 frontier of kCET bypasses -- an &lt;code&gt;iretq&lt;/code&gt;-frame corruption combined with a write-what-where primitive can pivot around the shadow stack [@mcgarr-bh25-blackhat] [@mcgarr-km-shadow] [@mcgarr-github]. The bypass requires the attacker to already control a kernel-mode write primitive and several CFG-clean targets, which is exactly the precondition KDP, kCFG, and HVCI are designed to make hard.&lt;/p&gt;
&lt;h3&gt;ARM64 Pointer Authentication&lt;/h3&gt;
&lt;p&gt;The recurring framing of PatchGuard as &quot;x64-only&quot; is documentation-accurate but deployment-incomplete. In 2026, PatchGuard, kCFG, and Pointer Authentication Codes (PAC) ship on 64-bit ARM Windows as well as x64. PAC is an ARMv8.3-A feature in which a tag computed over a pointer value and a per-process key is stored in the unused high bits of the pointer; the CPU validates the tag on dereference. PAC closes a different class of pointer-corruption attacks than kCFG/kCET. The structural point is that the kernel self-defense investment is fully cross-architecture, not x64-only.&lt;/p&gt;
&lt;h3&gt;The Microsoft Vulnerable Driver Blocklist&lt;/h3&gt;
&lt;p&gt;The reactive answer to BYOVD is the &lt;strong&gt;Microsoft Recommended Driver Block Rules&lt;/strong&gt; -- a list of known-vulnerable signed third-party drivers that Windows refuses to load when App Control for Business (formerly WDAC) is enabled [@ms-driver-block-rules]. The list is default-on with Memory Integrity, Smart App Control, and S-mode since Windows 11 22H2 and is updated through Windows Update. Verification on a modern system: &lt;code&gt;CiTool --list-policies&lt;/code&gt; and look for a policy whose friendly name is &lt;code&gt;Microsoft Windows Driver Policy&lt;/code&gt; and &lt;code&gt;Is Currently Enforced: true&lt;/code&gt;. The blocklist is the structural answer to the Uroburos pattern -- Microsoft cannot prevent any signed third-party driver from having a write-primitive bug, but they can refuse to load specific drivers known to have shipped such bugs.&lt;/p&gt;

The attack pattern in which an attacker, having reached administrator privilege, installs a *legitimate* signed third-party kernel driver known to contain a privilege-escalation vulnerability, then exploits that vulnerability to obtain arbitrary kernel-mode primitives. The Uroburos VBoxDrv abuse [@gdata-uroburos-blog] is the canonical 2011 example; the Microsoft Recommended Driver Block Rules are the 2024+ reactive answer [@ms-driver-block-rules].
&lt;h3&gt;Synthesis&lt;/h3&gt;
&lt;p&gt;By 2026, the Windows kernel self-defense stack is no longer a single mitigation; it is a &lt;em&gt;stack&lt;/em&gt; organised by where the defense actually runs. The 21-year trajectory now resolves into a single thesis: every generation has been a partial answer to the same-privilege paradox, and Microsoft&apos;s strategy has progressively migrated the defense out of the kernel -- first into instruction-level obfuscation, then into address-space tricks, then into VBS-anchored isolation, and finally into attack-surface deletion. Before we name that thesis formally, it is worth asking: what did the rest of the industry do?&lt;/p&gt;
&lt;h2&gt;7. What the rest of the industry did differently&lt;/h2&gt;
&lt;p&gt;The Microsoft answer to the same-privilege paradox -- twenty-one years of compounding investment in same-privilege deterrents while progressively shifting enforcement to VTL1 -- is not the only answer. Apple and the Linux mainline community took architecturally opposite paths, each correct for a different platform constraint.&lt;/p&gt;
&lt;h3&gt;Apple: push the defense into silicon&lt;/h3&gt;
&lt;p&gt;Apple&apos;s answer was to put enforcement &lt;em&gt;below&lt;/em&gt; the kernel, into hardware Apple controls end-to-end. On Apple Silicon, the Kernel Text Read-only Region (KTRR) is hardware-enforced via the AMCC (Apple Memory Cache Controller). At boot, after the kernel is mapped and before user code runs, the kernel text region is locked read-only at the memory-controller level. Once locked, no software running at &lt;em&gt;any&lt;/em&gt; privilege level can modify it -- not the kernel itself, not a kernel extension, not a hypothetical EL2 hypervisor [@siguza-ktrr].&lt;/p&gt;

Apple Silicon&apos;s hardware-enforced read-only kernel text region. After boot, the kernel image is locked via the AMCC memory controller; no software at any privilege level can write to the protected region for the lifetime of that boot [@siguza-ktrr]. Apple&apos;s architectural answer to the same-privilege paradox: push the defense *below* the kernel, into hardware Apple controls.
&lt;p&gt;The corollary is that Apple&apos;s hardware control allows them to make a software move Microsoft cannot. Apple deprecated third-party Kernel Extensions (KEXTs) in favour of user-mode DriverKit and Endpoint Security, structurally removing the BYOVD class from the platform.Apple&apos;s deprecation of third-party KEXTs began in macOS Catalina (2019) with a deprecation warning, escalated to &quot;system extensions&quot; requiring user approval and reduced kernel-mode footprint, and reached a near-complete migration target on Apple Silicon. The architectural cost is that legitimate device-driver vendors and EDR products had to rebuild their stacks on top of user-mode brokers and Apple-curated APIs; the architectural benefit is that a 2024-style CrowdStrike Falcon kernel-driver outage is structurally not possible on Apple Silicon, because the EDR product runs in user mode against an Endpoint Security framework that mediates the kernel for it.&lt;/p&gt;
&lt;h3&gt;Linux mainline: privilege reduction, not integrity monitoring&lt;/h3&gt;
&lt;p&gt;The mainline Linux community&apos;s strategy is structurally the opposite of Microsoft&apos;s: do not invest in same-privilege deterrents at all; invest in privilege reduction and surface isolation instead. LKRG (Linux Kernel Runtime Guard, maintained by Openwall) is the closest functional analogue to PatchGuard [@openwall-lkrg-page] [@openwall-lkrg-github]. Its own documentation describes it as &quot;bypassable by design&quot; -- an openly-acknowledged same-privilege paradox.LKRG&apos;s frank framing is unusual in the security tools space. The project explicitly tells operators that LKRG is a hardening layer that raises the engineering cost of common kernel rootkit techniques, not a security boundary, and that a determined kernel-mode attacker can defeat it. This is the same architectural truth Skywing made in 2005 and that Microsoft published in the Servicing Criteria a decade later, stated upfront in a project README.&lt;/p&gt;
&lt;p&gt;Beyond LKRG, the mainline mechanisms have a recurring structural shape. Each row of the table below is structurally a &lt;em&gt;privilege-reduction&lt;/em&gt; or &lt;em&gt;surface-removal&lt;/em&gt; mechanism rather than a same-privilege integrity check.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Linux mechanism&lt;/th&gt;
&lt;th&gt;Status (as of June 2026)&lt;/th&gt;
&lt;th&gt;What it protects&lt;/th&gt;
&lt;th&gt;Windows analogue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Lockdown LSM&lt;/td&gt;
&lt;td&gt;Mainline since 5.4 (2019)&lt;/td&gt;
&lt;td&gt;Restricts root&apos;s ability to modify the running kernel&lt;/td&gt;
&lt;td&gt;Driver Signature Enforcement plus HVCI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FG-KASLR&lt;/td&gt;
&lt;td&gt;Out-of-tree&lt;/td&gt;
&lt;td&gt;Per-function rather than per-image randomisation&lt;/td&gt;
&lt;td&gt;No direct analogue; closest is kASLR base randomisation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clang KCFI (&lt;code&gt;-fsanitize=kcfi&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Mainline since 6.1 (Dec 2022)&lt;/td&gt;
&lt;td&gt;Forward-edge CFI for the Linux kernel&lt;/td&gt;
&lt;td&gt;kCFG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shadow Call Stack (ARM64)&lt;/td&gt;
&lt;td&gt;Mainline since 5.8 (2020)&lt;/td&gt;
&lt;td&gt;Backward-edge integrity on ARM64&lt;/td&gt;
&lt;td&gt;kCET (on x64 / AMD), SCS on ARM64 Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seccomp-bpf&lt;/td&gt;
&lt;td&gt;Mainline since 3.5 (2012)&lt;/td&gt;
&lt;td&gt;Caller-defined per-syscall filter for any process&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; (system-defined IDs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eBPF kernel-mode restrictions&lt;/td&gt;
&lt;td&gt;Mainline since 5.8 (2020)&lt;/td&gt;
&lt;td&gt;Limits unprivileged users from loading eBPF programs that touch kernel state&lt;/td&gt;
&lt;td&gt;No direct Windows analogue&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The shared design move across all six is &lt;strong&gt;structural privilege reduction rather than same-privilege integrity monitoring&lt;/strong&gt;. seccomp-bpf is particularly instructive as a counterpoint to &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt;. The Linux design is &lt;em&gt;caller-defined&lt;/em&gt;: any process can register a BPF program that filters its own syscalls. The Windows design is &lt;em&gt;system-defined&lt;/em&gt;: a process registers an opaque bitmap of &lt;code&gt;FilterId&lt;/code&gt; values whose semantics are decided by the kernel. The two are not interchangeable, but they answer the same architectural question -- &quot;how do you let a process tell the kernel which syscalls it does not want?&quot; -- with the same fundamental move: per-process surface deletion at the syscall boundary.&lt;/p&gt;
&lt;h3&gt;Hypervisor-anchored alternatives at the application level&lt;/h3&gt;
&lt;p&gt;The third philosophy applies the &quot;live at a different privilege than the attacker&quot; answer at the &lt;em&gt;application&lt;/em&gt; level rather than the kernel level. Bromium / HP Sure Click and Windows Defender Application Guard open every tab or document in its own micro-VM. The hypervisor is the protection boundary; the kernel inside the VM may be fully compromised without affecting the host. This is structurally the same move Microsoft makes with VBS / VTL1, applied one level up the stack.&lt;/p&gt;
&lt;h3&gt;Three philosophies, one shared admission&lt;/h3&gt;
&lt;p&gt;Three platforms, three philosophies, one shared admission: every architecture eventually had to admit that a defense at the same privilege as the attacker cannot succeed in principle. Apple put the defense in silicon. Linux invested in surface reduction instead of integrity monitoring. Microsoft built a same-privilege deterrent first, then migrated the load-bearing pieces of it to VTL1. The interesting disagreement is not whether the paradox exists -- it is where, exactly, to put the defense instead. That is a question with no single right answer, and to see why, we have to state the paradox formally.&lt;/p&gt;
&lt;h2&gt;8. The same-privilege paradox, formally&lt;/h2&gt;
&lt;p&gt;Now we can state the paradox in a sentence: &lt;em&gt;a defense that shares its CPU privilege level with the attacker can in principle always be subverted by an attacker at that privilege level, because every code path and data structure the defense relies on is, by construction, mutable by the attacker.&lt;/em&gt; It is not a formal impossibility theorem in the cryptographic sense -- there is no FLP-style no-go proof for kernel self-defense -- but it is the de facto design constraint Microsoft has acknowledged in writing.&lt;/p&gt;
&lt;h3&gt;Microsoft&apos;s formal admission&lt;/h3&gt;
&lt;p&gt;The Microsoft Security Servicing Criteria for Windows defines a &quot;security boundary&quot; as &lt;em&gt;&quot;a logical separation between the code and data of security domains with different levels of trust&quot;&lt;/em&gt;, with kernel-mode versus user-mode as the canonical example [@ms-servicing-criteria]. The document then enumerates which transitions Microsoft treats as security boundaries (kernel / user, hypervisor / kernel, VTL1 / VTL0, virtual machine / host, network), and explicitly &lt;em&gt;does not&lt;/em&gt; enumerate admin-to-kernel or kernel-to-kernel as boundaries. The exclusion is the cleanest possible architectural admission of the paradox: no defense at CPL=0 in the attacker&apos;s kernel can be a security boundary, no matter how cleverly engineered. PatchGuard, by Microsoft&apos;s own classification, is not a boundary and never has been.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The same-privilege paradox is, formally, the observation that the &lt;strong&gt;reference monitor&lt;/strong&gt; of a security policy must be tamper-resistant from the principals it monitors, and that &quot;tamper-resistant from a co-resident kernel-mode attacker&quot; is structurally unachievable in a single-address-space single-privilege design. Every modern Windows kernel mitigation either &lt;em&gt;raises the cost&lt;/em&gt; of tampering (the engineering-deterrent class: PatchGuard, KASLR, kASLR variants) or &lt;em&gt;moves the monitor outside CPL=0&lt;/em&gt; (the structural class: KDP, kCFG-with-VBS-bitmap, kCET, the entire VTL1-anchored stack). Only the second class can claim a security boundary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The KASLR-specific bound&lt;/h3&gt;
&lt;p&gt;The cleanest mathematical version of the paradox lives in the KASLR side-channel literature. Suppose an x64 system has $n$ bits of entropy in its kernel base address; the probabilistic floor on guessing it from one shot is $2^{-n}$. The Hund-Willems-Holz 2013 result is that a co-resident user-mode attacker with access to a shared TLB or cache state can extract bits of the kernel base at a rate of one bit per probe, recovering the address in $O(n)$ probes -- a polynomial-time defeat of the probabilistic defense [@doi-hund-2013]. Increasing $n$ does not change the asymptotic; it only changes the constant. Gruss et al. 2017 generalised the argument across micro-architectural side channels and concluded that any operating system implementing user / kernel address-space sharing on a CPU with shared TLB / cache state must leak the kernel base address to an unprivileged user-mode timing observer [@gruss-kaiser-pdf]. The structural fix is not to add entropy: it is to remove the sharing. KVA Shadow / KPTI is the structural answer.&lt;/p&gt;
&lt;p&gt;The shape of the bound is general. Wherever a defense&apos;s correctness reduces to &lt;em&gt;the attacker not knowing X&lt;/em&gt;, and &lt;em&gt;X&lt;/em&gt; leaks across a shared micro-architectural channel, the defense is asymptotically defeated.&lt;/p&gt;
&lt;h3&gt;The proper formal anchor: Anderson 1972&lt;/h3&gt;
&lt;p&gt;The right formal anchor for the same-privilege paradox is the reference-monitor concept introduced in Anderson&apos;s 1972 &lt;em&gt;Computer Security Technology Planning Study&lt;/em&gt; for the US Air Force [@csrc-anderson-1972]. Anderson&apos;s &quot;reference monitor&quot; must satisfy three properties:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Always invoked.&lt;/strong&gt; Every reference of a subject to an object is mediated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tamper-resistant.&lt;/strong&gt; The reference monitor cannot be modified by the subjects it monitors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small enough to be analysed.&lt;/strong&gt; The Trusted Computing Base (TCB) is small enough to be verified.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PatchGuard fails property 2 by construction: it lives in the same address space as the subjects it monitors, and any subject with kernel-mode write can modify the verifier code, the verifier schedule, the expected-hash store, or the bug-check primitive. KDP, by contrast, satisfies property 2 because its enforcement lives in VTL1 and a VTL0 subject cannot reach VTL1.&lt;/p&gt;

A recurring confusion in the kernel-security literature is to anchor same-privilege-paradox arguments in the Bell-LaPadula or Biba multi-level security models (1973 / 1977). Those models formalise *information flow* across security domains -- which subjects may read or write which objects given their lattice levels. They are silent on the question of whether the policy *enforcement mechanism itself* can be tamper-resistant against a co-resident attacker. That is Anderson&apos;s reference-monitor property, formalised in the 1972 USAF report [@csrc-anderson-1972]. Bell-LaPadula assumes a tamper-resistant reference monitor as a precondition; Anderson&apos;s report is the document that *names* the precondition. For the same-privilege paradox, Anderson is the load-bearing anchor.
&lt;p&gt;The existence proof for what a minimal verifiable TCB looks like is seL4 (Klein et al., SOSP 2009): a roughly 8,700-line microkernel formally verified down to its C implementation against a high-level specification of access control. seL4 is the constructive counterpoint to the Microsoft-style mitigation stack: instead of adding integrity monitors to a large kernel, build a small kernel small enough to verify and put everything else in user-space servers. Windows&apos; VBS / VTL1 architecture is a partial gesture in the same direction -- the Secure Kernel is far smaller than the NT kernel and hosts only policy-enforcement trustlets -- but it is not a from-scratch redesign.&lt;/p&gt;
&lt;h3&gt;Upper and lower bounds, mitigation by mitigation&lt;/h3&gt;
&lt;p&gt;The 21-year story now lays out cleanly as a table of bounds.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;th&gt;Upper bound achieved&lt;/th&gt;
&lt;th&gt;Lower bound that remains&lt;/th&gt;
&lt;th&gt;Structural reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;PatchGuard&lt;/td&gt;
&lt;td&gt;Engineering-deterrent class; raises cost of casual kernel hooking&lt;/td&gt;
&lt;td&gt;Zero structural lower bound; same-privilege bypass class always exists [@uninformed-v3-archive] [@cyberark-ghosthook]&lt;/td&gt;
&lt;td&gt;Verifier lives at attacker&apos;s privilege&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KASLR (entropy alone)&lt;/td&gt;
&lt;td&gt;Probabilistic floor against blind-guess attacker&lt;/td&gt;
&lt;td&gt;Zero structural lower bound against side-channel attacker [@doi-hund-2013]&lt;/td&gt;
&lt;td&gt;TLB / cache shared between user and kernel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KVA Shadow / KPTI&lt;/td&gt;
&lt;td&gt;Structural Meltdown fix (Variant 3)&lt;/td&gt;
&lt;td&gt;Spectre Variants 1, 2, 4 require separate mitigations [@usenix-lipp-meltdown]&lt;/td&gt;
&lt;td&gt;Address-space split addresses only the user-to-kernel transient read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HVCI&lt;/td&gt;
&lt;td&gt;Structural W^X for kernel pages, enforced by VTL1&lt;/td&gt;
&lt;td&gt;VBS-coverage gap on systems that cannot run VBS [@ms-kdp-blog]&lt;/td&gt;
&lt;td&gt;Hypervisor is the protection boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KDP (static and dynamic)&lt;/td&gt;
&lt;td&gt;Structural read-only-after-init for explicitly-tagged kernel data&lt;/td&gt;
&lt;td&gt;Protects only what is explicitly opted in [@ms-kdp-blog]&lt;/td&gt;
&lt;td&gt;VTL1 enforces SLAT page tables outside VTL0 reach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kCFG (with HVCI)&lt;/td&gt;
&lt;td&gt;Structural forward-edge CFI; bitmap in VTL1-protected memory&lt;/td&gt;
&lt;td&gt;Backward edge unprotected; same-call-target overwrite via type confusion possible without XFG [@ms-cfg]&lt;/td&gt;
&lt;td&gt;Bitmap stored outside VTL0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kCET&lt;/td&gt;
&lt;td&gt;Structural backward-edge CFI in CPU hardware&lt;/td&gt;
&lt;td&gt;Off-by-default on Server 2025; gated on driver &lt;code&gt;/CETCOMPAT&lt;/code&gt; [@ms-kernel-mode-hsp] [@mcgarr-bh25-blackhat]&lt;/td&gt;
&lt;td&gt;Shadow stack hardware enforced in silicon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Win32kSystemCallFilter&lt;/td&gt;
&lt;td&gt;Structural surface deletion for sandboxed renderers&lt;/td&gt;
&lt;td&gt;Full lockdown not viable for UI-bearing processes [@ms-syscall-filter-policy]&lt;/td&gt;
&lt;td&gt;Per-process bitmap consulted by syscall dispatcher&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The gap between the same-privilege upper bound (PatchGuard, KASLR-alone -- structurally zero) and the cross-privilege upper bound (HVCI, KDP, kCET -- structurally meaningful) is exactly the gap Microsoft has spent twenty-one years migrating across. With the paradox stated formally, the rest of the article is a single question: where in the privilege hierarchy does the next problem live, and how is Microsoft positioned to answer it?&lt;/p&gt;
&lt;h2&gt;9. Open problems on the June 2026 frontier&lt;/h2&gt;
&lt;p&gt;The same-privilege paradox is in 2026 closer to architecturally resolved than at any prior point in Windows history -- the VTL1-anchored stack of HVCI / KDP / kCFG / kCET makes the cross-privilege answer real. But every structural mitigation has a practical residual, and five of them are large enough to be the article&apos;s frontier.&lt;/p&gt;
&lt;h3&gt;BYOVD: the dominant 2026 attacker path&lt;/h3&gt;
&lt;p&gt;Bring-Your-Own-Vulnerable-Driver is the dominant practical defeat of every structural mitigation in the 2026 stack. Uroburos&apos;s 2011 pattern is essentially what current attackers do: locate a signed third-party driver with a kernel-write primitive (an IOCTL that allows arbitrary physical memory read or write, or arbitrary MSR manipulation), install it through a legitimate driver-load path, exploit the primitive to obtain arbitrary kernel write, then flip the policy flags or hook the structures Microsoft thought were protected. Elastic Security Labs&apos; 2024 survey of in-the-wild Windows kernel LPE 0-days confirms that BYOVD remains a recurring subsystem of incidents [@elastic-lpe-survey], and the Project Zero &quot;0day In the Wild&quot; tracker continues to record Windows kernel-mode CVEs across DWM, win32k, and ALPC subsystems [@pz-0days-tracker]. Every structural mitigation collapses the moment an attacker reaches arbitrary kernel write through a legitimately-loaded driver: KDP-protected pages can be ignored if the attacker can install a new driver that simply does not allocate from the secure pool; kCFG can be bypassed by writing to memory that was not opted in; kCET can be bypassed via McGarr-style &lt;code&gt;iretq&lt;/code&gt; corruption [@mcgarr-km-shadow]; PatchGuard can be hooked from a coexisting driver.&lt;/p&gt;
&lt;p&gt;The Microsoft Recommended Driver Block List [@ms-driver-block-rules] is the reactive answer. The structural problem -- that signed third-party drivers with kernel-write primitives exist &lt;em&gt;at all&lt;/em&gt;, and that the third-party driver supply chain cannot be removed for compatibility reasons -- is unresolved.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A defender architecting around the 2026 Windows kernel mitigation stack must assume BYOVD as the dominant practical bypass. The structural mitigations -- KDP, kCFG, kCET, HVCI -- are sound against an attacker who is &lt;em&gt;constrained&lt;/em&gt; to operate within the inbox kernel. They are not sound against an attacker who can load any of the recurring vulnerable signed drivers the Microsoft Recommended Driver Block List exists to catalogue [@ms-driver-block-rules] [@elastic-lpe-survey]. Verify that the block list is enforced (&lt;code&gt;CiTool --list-policies&lt;/code&gt;), watch CodeIntegrity Event ID 3099, and treat BYOVD as the threat model that drives mitigation selection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;The VBS coverage gap&lt;/h3&gt;
&lt;p&gt;Every VTL1-anchored mitigation collapses on systems that cannot run VBS. Older silicon (pre-2015 Intel without VT-x / VT-d / EPT, AMD parts predating AMD-V / NPT), enterprise-imaged corporate fleets that disabled VBS for compatibility, ARM64 devices below a baseline, and any system without UEFI Secure Boot all fall back to the same-privilege defenses we just classified as structurally bounded. The defender&apos;s threat model is the worst case in the fleet, not the average case in the Microsoft launch announcement.&lt;/p&gt;
&lt;h3&gt;Win32k Lockdown coverage in UI-bearing processes&lt;/h3&gt;
&lt;p&gt;Office, browsers&apos; GPU and UI processes, and any application that draws windows cannot use the full &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; lockdown. Their allow-lists must cover composition, font rendering, and a substantial fraction of the GDI surface -- which is exactly the surface from which historical LPE bugs emerged. The 2016 &lt;code&gt;win32kbase.sys&lt;/code&gt; / &lt;code&gt;win32kfull.sys&lt;/code&gt; typeisolation refactor (Windows 10 v1607, build 14393) split &lt;code&gt;win32k.sys&lt;/code&gt; to make the surface more attributable, but per-app auto-tuning of the allow-list from observed-call traces remains an open product-engineering problem [@j00ru-syscalls-table]. Until UI-bearing processes can use a tight allow-list rather than a permissive one, the win32k surface remains the systemic LPE foothold Bochspwn identified in 2013 [@j00ru-bochspwn-blog].&lt;/p&gt;
&lt;h3&gt;Hypervisor escapes as the structural counter&lt;/h3&gt;
&lt;p&gt;Every VTL1-anchored mitigation assumes VTL1 is uncompromised. Hyper-V CVEs show that the hypervisor TCB hosts its own vulnerability surface. CVE-2024-38080 (Hyper-V SLAT vulnerability) is a 2024 example with Akamai write-up [@akamai-hyperv-cve]. Joanna Rutkowska&apos;s 2006 Blue Pill demonstration at Black Hat USA, &lt;em&gt;Subverting Vista Kernel for Fun and Profit&lt;/em&gt;, was the seminal academic primary for the hypervisor-rootkit class and remains the canonical &quot;Hyperjacking&quot; reference [@blackhat-rutkowska-bluepill]. Every step the Windows mitigation stack takes toward putting more enforcement in VTL1 raises the criticality of VTL1&apos;s own correctness. The Hyper-V code base is small relative to &lt;code&gt;ntoskrnl&lt;/code&gt; but is not zero, and the post-2018 trend of finding side-channel and architectural bugs in CPU hardware applies to VTL1 as much as it does to VTL0.&lt;/p&gt;
&lt;h3&gt;kCET deployment completion&lt;/h3&gt;
&lt;p&gt;kCET is shipping but off-by-default on Windows Server 2025, gated on driver &lt;code&gt;/CETCOMPAT&lt;/code&gt; compatibility [@ms-kernel-mode-hsp]. Until kCET is on-by-default across the inbox kernel and all loaded drivers, the backward-edge ROP class against the Windows kernel remains exploitable in practice. McGarr&apos;s 2025 Black Hat USA deck documents both the structural-bypass frontier and the operational gating problem [@mcgarr-bh25-blackhat] [@mcgarr-github] [@mcgarr-km-shadow].&lt;/p&gt;

On July 19, 2024, a faulty kernel-mode signature update from CrowdStrike Falcon triggered a Windows page fault in a CrowdStrike driver, crashing an estimated 8.5 million Windows endpoints worldwide and disrupting airline operations, hospital systems, payment processing, and emergency-services dispatch for hours to days. The post-incident discussion produced one architectural takeaway widely shared across the kernel-security community: a single signed third-party kernel driver, even one shipped by a defender, can take the operating system down -- and there is no in-kernel protection against it that does not also break legitimate EDR vendors. Microsoft&apos;s 2006 position that the right answer is &quot;as few third-party kernel drivers as possible, with as much functionality as possible mediated by user-mode brokers&quot; got eighteen years of pushback before being retroactively vindicated. The 2024-2026 product direction -- Microsoft&apos;s announcement of the Windows Endpoint Security Platform, a user-mode EDR API that lets vendors build without kernel drivers -- is the inheritor of that position.
&lt;h3&gt;Historical anchoring: the win32k LPE share&lt;/h3&gt;
&lt;p&gt;The &quot;win32k killed half of LPE&quot; framing in the article&apos;s subtitle deserves time-scoping. Pre-lockdown, win32k was the dominant Windows kernel LPE subsystem -- Stuxnet 2010 (CVE-2010-2743) is the historical anchor [@nvd-cve-2010-2743], Bochspwn 2013 documented the systemic shape [@j00ru-bochspwn-blog] [@j00ru-bhusa-pdf], Forshaw 2016 reports that the Chrome M54 lockdown &quot;blocked the sandbox escape of an exploit chain being used in the wild&quot; [@pz-breaking-chain], and Elastic Security Labs&apos; 2024 in-the-wild survey continues to name win32k among the recurring subsystems [@elastic-lpe-survey]. The Project Zero 0day tracker also confirms that win32k remains in the post-lockdown attacker mix [@pz-0days-tracker]. The lockdown removed roughly half the historically-vulnerable syscall surface &lt;em&gt;from sandboxed renderers specifically&lt;/em&gt;; both the fraction and the scope are time- and context-bounded, and a precise percentage cannot be cited to the Project Zero tracker because the tracker does not publish per-subsystem aggregates.&lt;/p&gt;

flowchart TD
    subgraph SD[&quot;Surface deletion (kernel system-call boundary)&quot;]
        SDF[&quot;Win32kSystemCallFilter per-process bitmap&quot;]
        SDD[&quot;DisallowWin32kSystemCalls all-or-nothing&quot;]
    end
    subgraph V1[&quot;VTL1 (Secure Kernel anchored)&quot;]
        V1H[&quot;HVCI (W^X SLAT for kernel pages)&quot;]
        V1K[&quot;KDP static and dynamic via SLAT RO&quot;]
        V1C[&quot;kCFG bitmap in VTL1-protected memory&quot;]
    end
    subgraph CPU[&quot;CPU mediated (hardware enforced)&quot;]
        CPUS[&quot;kCET shadow stack on Intel CET / AMD&quot;]
        CPUK[&quot;KVA Shadow CR3 switch&quot;]
    end
    subgraph V0[&quot;VTL0 same-privilege (CPL=0)&quot;]
        V0P[&quot;PatchGuard integrity checks&quot;]
        V0K[&quot;KASLR base-address randomisation&quot;]
    end
    SD --&amp;gt; V1
    V1 --&amp;gt; CPU
    CPU --&amp;gt; V0
&lt;p&gt;BYOVD is in 2026 what same-privilege bypass was in 2007 -- the dominant practical defeat of a mitigation stack whose individual pieces are each structurally sound. The next twenty-one years of Windows kernel self-defense will be substantially the story of what Microsoft does about it.&lt;/p&gt;
&lt;h2&gt;10. What a Windows defender or driver developer actually does today&lt;/h2&gt;
&lt;p&gt;The article&apos;s intellectual payoff has been made; the practical payoff is the rest of this section. Five concrete decision questions, in roughly the order a working practitioner would reason through them.&lt;/p&gt;
&lt;h3&gt;1. Is the system Secured-core or Windows 11 22H2+ with Memory Integrity on?&lt;/h3&gt;
&lt;p&gt;If yes, HVCI, KDP, kCFG, and the Microsoft Recommended Driver Block Rules are baseline [@ms-kdp-blog] [@ms-driver-block-rules]. Layer kCET if all loaded drivers are &lt;code&gt;/CETCOMPAT&lt;/code&gt; and the CPU is Intel 11th-gen Tiger Lake or later or AMD Zen 3 or later [@ms-kernel-mode-hsp]. The baseline gets you the structural mitigations the same-privilege paradox argues are required; everything else is layered on top.&lt;/p&gt;
&lt;h3&gt;2. Is the workload a sandboxed renderer or sandboxable child process?&lt;/h3&gt;
&lt;p&gt;Apply &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; (Windows 10 1709+) via &lt;code&gt;UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, ...)&lt;/code&gt; at &lt;code&gt;CreateProcess&lt;/code&gt; time, not at runtime [@ms-syscall-filter-policy]. The Forshaw / Fratric race-the-mitigation Edge demonstration is the empirical reason -- if the filter is applied after the child process has started, an attacker who races the policy application can simply not be filtered [@pz-breaking-chain]. The Chromium sandbox is the canonical consumer reference for what this composition looks like in a production browser [@chromium-sandbox-doc].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every per-process mitigation in modern Windows -- &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt;, &lt;code&gt;DisallowWin32kSystemCalls&lt;/code&gt;, ACG, CIG, Strict CIG, user-mode shadow stack, CFG -- belongs on the &lt;code&gt;CreateProcess&lt;/code&gt; boundary. The Forshaw / Fratric Project Zero finding on Edge&apos;s window-broker race [@pz-breaking-chain] is the empirical proof that mitigations applied to a running process leave a race window. The Windows API path is &lt;code&gt;STARTUPINFOEXW&lt;/code&gt; with a &lt;code&gt;PPROC_THREAD_ATTRIBUTE_LIST&lt;/code&gt; containing &lt;code&gt;PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY&lt;/code&gt;; the policy enums to set are documented in &lt;code&gt;ntddk.h&lt;/code&gt; for the filter [@ms-syscall-filter-policy] and in &lt;code&gt;winnt.h&lt;/code&gt; for the disable [@ms-syscall-disable-policy].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3. Is the workload UI-bearing?&lt;/h3&gt;
&lt;p&gt;Full lockdown is out of reach for processes that draw windows, render fonts, or dispatch input. The practical answer is the &lt;em&gt;adjacent&lt;/em&gt; mitigation set: Arbitrary Code Guard (ACG), Code Integrity Guard (CIG), Strict CIG, user-mode shadow stack, and CFG, plus PatchGuard, HVCI, and kCFG at the system level. The composition raises the cost of remote exploitation without requiring the renderer-style syscall-surface deletion.&lt;/p&gt;

For a sandboxed renderer-class process on Windows 11 22H2+:&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Win32kSystemCallFilter&lt;/code&gt;&lt;/strong&gt; -- &lt;code&gt;PROCESS_MITIGATION_SYSTEM_CALL_FILTER_POLICY&lt;/code&gt; with the bitmap permitting only the &lt;code&gt;FilterId&lt;/code&gt; values the renderer needs [@ms-syscall-filter-policy].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACG (Arbitrary Code Guard)&lt;/strong&gt; -- forbid dynamic code generation in the process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CIG / Strict CIG (Code Integrity Guard)&lt;/strong&gt; -- forbid loading non-Microsoft-signed DLLs (CIG), or non-Microsoft-signed-and-not-store-signed DLLs (Strict CIG).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User-mode shadow stack and CFG&lt;/strong&gt; -- backward and forward edge CFI in user mode.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All four are applied via &lt;code&gt;UpdateProcThreadAttribute(PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, ...)&lt;/code&gt; at &lt;code&gt;CreateProcess&lt;/code&gt; time, in the same call. The Chromium renderer is the canonical reference deployment [@chromium-sandbox-doc].
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;4. Are you a driver author?&lt;/h3&gt;
&lt;p&gt;Three things to do, in order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mark RO-after-init data via Static KDP.&lt;/strong&gt; Call &lt;code&gt;MmProtectDriverSection&lt;/code&gt; from &lt;code&gt;DriverEntry&lt;/code&gt; on any image section that should be read-only for the rest of the driver&apos;s lifetime [@ms-kdp-blog].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Allocate runtime-protected state via Dynamic KDP.&lt;/strong&gt; Call &lt;code&gt;ExAllocatePool3&lt;/code&gt; with a &lt;code&gt;POOL_EXTENDED_PARAMETER&lt;/code&gt; array containing a &lt;code&gt;POOL_EXTENDED_PARAMS_SECURE_POOL&lt;/code&gt; extended parameter. Set &lt;code&gt;SECURE_POOL_FLAGS_FREEABLE&lt;/code&gt; if the allocation needs to be freeable; set &lt;code&gt;SECURE_POOL_FLAG_MODIFIABLE&lt;/code&gt; only if the allocation must be modifiable under further protected control [@ms-kdp-blog].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compile with &lt;code&gt;/guard:cf&lt;/code&gt; and &lt;code&gt;/CETCOMPAT&lt;/code&gt;.&lt;/strong&gt; The first enables CFG instrumentation across the driver image; the second tells the loader the driver is compatible with kernel-mode shadow stack [@ms-cfg] [@ms-kernel-mode-hsp].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The driver-side KDP pattern is short enough to show in full:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// DriverEntry-time static KDP: mark a .rdata-like section as read-only
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject,
                     _In_ PUNICODE_STRING RegistryPath) {
    NTSTATUS status = MmProtectDriverSection(
        &amp;amp;g_PolicyTable,        // address of the section to protect
        sizeof(g_PolicyTable), // size in bytes
        0);                    // reserved
    if (!NT_SUCCESS(status)) return status;
    // ... rest of driver init
    return STATUS_SUCCESS;
}

// Runtime dynamic KDP allocation: a secure pool buffer
POOL_EXTENDED_PARAMETER params[2] = {0};
params[0].Type = PoolExtendedParameterSecurePool;
params[0].SecurePoolParams = &amp;amp;(POOL_EXTENDED_PARAMS_SECURE_POOL){
    .SecurePoolFlags = SECURE_POOL_FLAGS_FREEABLE,
    .SecurePoolBuffer = NULL,
    .Cookie = 0xC0FFEEDEADBEEFULL,
    .NoFill = FALSE,
};
params[1].Type = PoolExtendedParameterInvalidType;

PVOID secureBuffer = ExAllocatePool3(
    POOL_FLAG_NON_PAGED,    // pool flags
    bufferSize,             // size
    &apos;KDPx&apos;,                 // pool tag
    params,                 // extended parameters
    1);                     // count of extended parameters
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. Are you a defender on an existing fleet?&lt;/h3&gt;
&lt;p&gt;Verify that the Recommended Driver Block Rules are active via &lt;code&gt;CiTool --list-policies&lt;/code&gt;. Look for a policy whose &lt;code&gt;Friendly Name&lt;/code&gt; is &lt;code&gt;Microsoft Windows Driver Policy&lt;/code&gt; and &lt;code&gt;Is Currently Enforced&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; [@ms-driver-block-rules]. Watch Event ID 3099 in the CodeIntegrity Operational log for block events. For verifying the broader VBS / HVCI state, the canonical PowerShell query is &lt;code&gt;Get-CimInstance Win32_DeviceGuard&lt;/code&gt; followed by selecting &lt;code&gt;VirtualizationBasedSecurityStatus&lt;/code&gt;, &lt;code&gt;SecurityServicesRunning&lt;/code&gt;, and &lt;code&gt;AvailableSecurityProperties&lt;/code&gt;. For KVA Shadow specifically, &lt;code&gt;Get-SpeculationControlSettings&lt;/code&gt; reports the state. For per-process mitigation policy, &lt;code&gt;Get-ProcessMitigation -System&lt;/code&gt; for the system policy and &lt;code&gt;Get-ProcessMitigation -Name &amp;lt;name&amp;gt;&lt;/code&gt; for a specific process; the Chromium internal page &lt;code&gt;chrome://sandbox&lt;/code&gt; shows the per-process filter state from inside the browser.&lt;/p&gt;
&lt;p&gt;A reader who wants to play with the field-decoding logic can do it in a browser. The Python below mirrors what the PowerShell pipeline does -- enumerate the bits, decode by name. The real Windows API surface is bigger, but the decoding shape is the same.&lt;/p&gt;
&lt;p&gt;{`&lt;/p&gt;
Conceptual decoder for Win32_DeviceGuard fields
Real PowerShell: Get-CimInstance Win32_DeviceGuard | Select VirtualizationBasedSecurityStatus,
SecurityServicesRunning, AvailableSecurityProperties
&lt;p&gt;VBS_STATUS = {
    0: &quot;VBS not enabled&quot;,
    1: &quot;VBS enabled but not running&quot;,
    2: &quot;VBS enabled and running&quot;,
}&lt;/p&gt;
&lt;p&gt;SECURITY_SERVICES = {
    0: &quot;None&quot;,
    1: &quot;Credential Guard&quot;,
    2: &quot;HVCI&quot;,
    3: &quot;System Guard Secure Launch&quot;,
    4: &quot;SMM Firmware Measurement&quot;,
    7: &quot;Kernel Mode Hardware-enforced Stack Protection (kCET)&quot;,
    8: &quot;Hypervisor-Protected Code Integrity (HVCI legacy)&quot;,
}&lt;/p&gt;
&lt;p&gt;AVAILABLE_PROPERTIES = {
    1: &quot;Base virtualization support&quot;,
    2: &quot;Secure boot&quot;,
    3: &quot;DMA protection&quot;,
    4: &quot;Secure memory overwrite&quot;,
    5: &quot;UEFI code readonly&quot;,
    6: &quot;SMM security mitigations&quot;,
    7: &quot;Mode-based execute control for HVCI&quot;,
    8: &quot;APIC virtualization&quot;,
}&lt;/p&gt;
&lt;p&gt;def decode(field_name, value, table):
    if isinstance(value, list):
        names = [table.get(v, f&quot;unknown({v})&quot;) for v in value]
        print(f&quot;  {field_name}: {names}&quot;)
    else:
        print(f&quot;  {field_name}: {table.get(value, f&apos;unknown({value})&apos;)}&quot;)&lt;/p&gt;
Simulated CIM response from a Secured-core PC
&lt;p&gt;sample = {
    &quot;VirtualizationBasedSecurityStatus&quot;: 2,
    &quot;SecurityServicesRunning&quot;: [1, 2, 7],
    &quot;AvailableSecurityProperties&quot;: [1, 2, 3, 5, 7],
}&lt;/p&gt;
&lt;p&gt;print(&quot;Win32_DeviceGuard decoded:&quot;)
decode(&quot;VirtualizationBasedSecurityStatus&quot;,
       sample[&quot;VirtualizationBasedSecurityStatus&quot;], VBS_STATUS)
decode(&quot;SecurityServicesRunning&quot;,
       sample[&quot;SecurityServicesRunning&quot;], SECURITY_SERVICES)
decode(&quot;AvailableSecurityProperties&quot;,
       sample[&quot;AvailableSecurityProperties&quot;], AVAILABLE_PROPERTIES)
`}&lt;/p&gt;
&lt;h3&gt;Common pitfalls&lt;/h3&gt;
&lt;p&gt;A short reference list of mistakes that recur in real-world reviews:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Apply mitigations at &lt;code&gt;CreateProcess&lt;/code&gt;, not at runtime.&lt;/strong&gt; The Forshaw / Fratric race is the cited example [@pz-breaking-chain].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not assume &lt;code&gt;DisallowWin32kSystemCalls&lt;/code&gt; is the modern lockdown.&lt;/strong&gt; It is the Windows 8 ancestor of &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; and is structurally distinct -- different mitigation enum, different policy struct [@ms-syscall-disable-policy] [@ms-syscall-filter-policy].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not use &lt;code&gt;MmAllocateNodePagesForMdlEx&lt;/code&gt; for Dynamic KDP.&lt;/strong&gt; The canonical API is &lt;code&gt;ExAllocatePool3&lt;/code&gt; with the secure-pool extended parameter; the NUMA-MDL API is a different API for a different purpose [@ms-kdp-blog].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kCET disables system-wide on a non-&lt;code&gt;/CETCOMPAT&lt;/code&gt; driver.&lt;/strong&gt; A single non-compat driver in the inbox set turns it off [@ms-kernel-mode-hsp].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PatchGuard is not a security boundary.&lt;/strong&gt; Do not architect a defense whose security argument rests on it; Microsoft&apos;s own Servicing Criteria say so [@ms-servicing-criteria].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these decisions makes the kernel a security boundary; together they make the kernel as hard to defeat as today&apos;s stack allows. The remaining questions are FAQs.&lt;/p&gt;
&lt;h2&gt;11. Frequently asked questions&lt;/h2&gt;

No. Microsoft&apos;s own *Security Servicing Criteria for Windows* explicitly does not enumerate admin-to-kernel or kernel-to-kernel as a security boundary; PatchGuard is an *engineering deterrent*, not a security boundary [@ms-servicing-criteria]. The most empirically grounded refutation is Uroburos&apos;s 2011 -- 2014 operational coexistence with PatchGuard on production Windows systems [@gdata-uroburos-blog]. PatchGuard raises the cost of a class of attacks; it does not eliminate any class of attacks.

No. PatchGuard shipped on April 25, 2005, with Windows XP Professional x64 Edition and Windows Server 2003 x64 Edition [@ms-advisory-932596]. Vista x64 (November 2006) inherited PatchGuard v2 from the 2005 release; the x86 editions of Vista never received PatchGuard. The &quot;Vista first&quot; misreading conflates PatchGuard&apos;s first widely-publicised release with its first shipping release.

No. Uroburos was a Driver Signature Enforcement (DSE) bypass that coexisted with PatchGuard for three years (2011 -- 2014) without modifying any PatchGuard-protected structure. It loaded a signed-but-vulnerable copy of Oracle&apos;s `VBoxDrv.sys`, used the vulnerability to flip the `g_CiEnabled` DSE-gating flag, loaded its own unsigned rootkit driver, then operated alongside PatchGuard [@gdata-uroburos-blog] [@stmxcsr-turla]. The canonical PatchGuard *bypass* is GhostHook (Kasif Dekel, CyberArk, June 2017), which uses an Intel-PT-buffer-fill PMI to redirect execution without touching any structure PatchGuard enumerates [@cyberark-ghosthook].

No. They are distinct `SetProcessMitigationPolicy` enums with distinct semantics. `DisallowWin32kSystemCalls` shipped in Windows 8 (2012) as a `PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY` and is all-or-nothing [@ms-syscall-disable-policy]. `Win32kSystemCallFilter` shipped in Windows 10 1709 (October 2017) as a `PROCESS_MITIGATION_SYSTEM_CALL_FILTER_POLICY` and is a per-syscall allow-list driven by a bitmap of system-defined `FilterId` values [@ms-syscall-filter-policy]. Chromium uses *both* in different process types -- the blanket-disable for processes that need no UI, the per-syscall filter for the renderer [@chromium-sandbox-doc].

Microsoft&apos;s documentation still calls it an x64 feature [@ms-driver-x64-restrictions], but in deployment it is also enforced on 64-bit ARM Windows in 2026. It has never shipped on x86 -- the precise framing is &quot;64-bit Windows only, both x64 and ARM64.&quot; The &quot;x64 only&quot; framing is documentation-accurate but deployment-incomplete.

Mostly no. KDP is a VBS-backed (Secure Kernel / VTL1) mitigation that *protects* kernel memory but is *enforced* outside the kernel. The Microsoft launch blog states the architecture directly: &quot;the memory managed by KDP is always verified by the secure kernel (VTL1) and protected using SLAT tables by the hypervisor&quot; [@ms-kdp-blog]. KDP is the canonical example of the same-privilege paradox resolved by structural means: the enforcement lives at a privilege level the VTL0 attacker cannot reach.

Title hyperbole, time-scoped. Pre-lockdown, win32k was the dominant Windows kernel LPE subsystem -- Stuxnet 2010 used a `win32k.sys` keyboard-layout LPE [@nvd-cve-2010-2743]; Bochspwn 2013 documented the systemic shape [@j00ru-bochspwn-blog]; Forshaw reports that Chrome&apos;s M54 win32k lockdown &quot;blocked the sandbox escape of an exploit chain being used in the wild&quot; [@pz-breaking-chain]. Elastic Security Labs&apos; 2024 in-the-wild survey continues to name win32k among the recurring subsystems [@elastic-lpe-survey]. The lockdown removed roughly half the historically-vulnerable syscall surface *from sandboxed renderers specifically* -- both the fraction and the scope are time- and context-bounded.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; PatchGuard, KASLR, KDP, &lt;code&gt;Win32kSystemCallFilter&lt;/code&gt; -- four answers, twenty-one years, one paradox. The arc resolves: every meaningful kernel defense in modern Windows ultimately lives at a privilege level the attacker does not have, because the alternative -- defending the kernel from inside the kernel -- is the one thing the architecture cannot do.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;kernel-self-defense-in-windows-patchguard-kaslr-kdp-and-the-win32k-lockdown-that&quot; keyTerms={[
  { term: &quot;Same-Privilege Paradox&quot;, definition: &quot;A defense at the attacker&apos;s privilege level cannot in principle succeed; the de facto design constraint Microsoft has acknowledged in writing through the Security Servicing Criteria.&quot; },
  { term: &quot;PatchGuard (KPP)&quot;, definition: &quot;Microsoft kernel feature that periodically verifies a fixed list of kernel structures and bug-checks the system on mismatch with stop code 0x109; not a security boundary.&quot; },
  { term: &quot;SSDT&quot;, definition: &quot;System Service Descriptor Table; the kernel function-pointer table that dispatches system calls. Pre-PatchGuard, the canonical AV hooking surface; post-PatchGuard, a protected structure.&quot; },
  { term: &quot;KMCS&quot;, definition: &quot;Kernel-Mode Code Signing; the 64-bit Windows policy that the kernel will load only Authenticode-signed drivers in production.&quot; },
  { term: &quot;CRITICAL_STRUCTURE_CORRUPTION (0x109)&quot;, definition: &quot;The bug-check stop code PatchGuard raises on detecting an unexpected modification to a protected kernel structure.&quot; },
  { term: &quot;KASLR&quot;, definition: &quot;Kernel Address Space Layout Randomisation; probabilistic defense by randomising kernel base address; defeated by side-channel attackers on systems with shared TLB/cache state.&quot; },
  { term: &quot;DSE&quot;, definition: &quot;Driver Signature Enforcement; the policy gate that loads only signed drivers in production. The g_CiEnabled flag is the in-memory gate; flipping it is the canonical BYOVD operation.&quot; },
  { term: &quot;Win32kSystemCallFilter&quot;, definition: &quot;Windows 10 1709+ process-mitigation policy registering a per-process allow-list of win32k system calls; the canonical &apos;attack-surface deletion&apos; mitigation.&quot; },
  { term: &quot;KAISER / KPTI&quot;, definition: &quot;Kernel Page-Table Isolation; the two-CR3 page-table architecture that makes the kernel address space unreachable from user CR3; Linux shipped KPTI in 2018, Microsoft shipped KVA Shadow.&quot; },
  { term: &quot;LPAC&quot;, definition: &quot;Less Privileged AppContainer; a Windows process model that further restricts ambient capabilities. Used by the Chromium renderer in composition with Win32kSystemCallFilter.&quot; },
  { term: &quot;KDP&quot;, definition: &quot;Kernel Data Protection; static via MmProtectDriverSection, dynamic via ExAllocatePool3 with POOL_EXTENDED_PARAMS_SECURE_POOL. Enforced by the Secure Kernel (VTL1) via SLAT.&quot; },
  { term: &quot;VBS / VTL1&quot;, definition: &quot;Virtualization-Based Security; the hypervisor-partitioned architecture in which a Secure Kernel runs at Virtual Trust Level 1, above the NT kernel in VTL0.&quot; },
  { term: &quot;BYOVD&quot;, definition: &quot;Bring-Your-Own-Vulnerable-Driver; the dominant 2026 attacker pattern of installing a signed third-party driver with a kernel-write primitive to obtain arbitrary kernel-mode access.&quot; },
  { term: &quot;KTRR&quot;, definition: &quot;Kernel Text Read-only Region; Apple Silicon&apos;s hardware-enforced read-only kernel text region, locked at boot at the AMCC memory-controller level.&quot; },
  { term: &quot;Reference Monitor (Anderson 1972)&quot;, definition: &quot;The formal anchor for the same-privilege paradox: a security policy must be enforced by a monitor that is always invoked, tamper-resistant from its subjects, and small enough to be analysed.&quot; },
  { term: &quot;HVCI&quot;, definition: &quot;Hypervisor-Protected Code Integrity; the VTL1-anchored W^X enforcement for kernel pages that underpins KDP, kCFG, and kCET.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-internals</category><category>kernel-security</category><category>patchguard</category><category>kaslr</category><category>kdp</category><category>win32k-lockdown</category><category>vbs-hvci</category><category>same-privilege-paradox</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><item><title>When SYSTEM Isn&apos;t Enough: The Windows Secure Kernel and the End of Total Kernel Trust</title><link>https://paragmali.com/blog/the-windows-secure-kernel/</link><guid isPermaLink="true">https://paragmali.com/blog/the-windows-secure-kernel/</guid><description>How Windows built a hardware-isolated kernel above Ring 0 using Hyper-V, protecting credentials and code integrity even after full NT kernel compromise.</description><pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate><content:encoded>
**The Windows Secure Kernel (securekernel.exe) is a minimal kernel running in a hardware-isolated environment (VTL1) above the main NT kernel, enforced by the Hyper-V hypervisor.** It protects credentials, code integrity, and application secrets even when an attacker has full control of the standard kernel. Born from the failure of software-only defenses like PatchGuard, it represents the biggest architectural shift in Windows security since the original NT reference monitor. It is not invulnerable -- rollback attacks and side-channel vulnerabilities remain open problems -- but it fundamentally changed what &quot;kernel compromise&quot; means on Windows.
&lt;h2&gt;When SYSTEM Isn&apos;t Enough&lt;/h2&gt;
&lt;p&gt;An attacker has achieved the holy grail: SYSTEM-level access on a domain-joined Windows machine. They load Mimikatz, point it at LSASS, and reach for the domain admin&apos;s Kerberos ticket. The command runs. The output comes back empty. The credentials are there -- the machine uses them every second -- but they&apos;re locked behind a wall that even full kernel access cannot breach.&lt;/p&gt;
&lt;p&gt;Welcome to the world of the Windows Secure Kernel.&lt;/p&gt;
&lt;p&gt;For decades, Windows security rested on a single hard boundary: user mode versus kernel mode. If you crossed that line -- if you achieved Ring 0 execution -- the system was yours. Every credential, every security policy, every secret was accessible. Tools like Benjamin Delpy&apos;s Mimikatz turned this architectural reality into a practical catastrophe, making Pass-the-Hash and Pass-the-Ticket attacks trivially easy across enterprise networks [@mimikatz-github].&lt;/p&gt;
&lt;p&gt;But on a modern Windows 11 machine with Virtualization-Based Security (VBS) enabled, the rules have changed. A new trust boundary exists -- one enforced not by the kernel, but by the hypervisor running &lt;em&gt;above&lt;/em&gt; the kernel. Even SYSTEM-level access in the traditional kernel cannot reach across this boundary [@ms-vbs].&lt;/p&gt;
&lt;p&gt;If kernel mode gives you everything, what could possibly be &lt;em&gt;above&lt;/em&gt; kernel mode? The answer requires a 30-year journey through Windows security.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The All-or-Nothing Kernel: How Windows NT Was Built&lt;/h2&gt;
&lt;p&gt;In 1988, Dave Cutler began designing Windows NT with a security model influenced by military security research -- especially the reference monitor concept, distinct from Bell-LaPadula&apos;s mandatory-access-control model. State-of-the-art for its era. It also contained a fatal assumption.&lt;/p&gt;

The core component of the Windows NT security architecture that mediates all access to securable objects (files, registry keys, processes) by checking Access Control Lists (ACLs) against the caller&apos;s security token. The SRM runs in kernel mode and enforces discretionary access control for every system operation.
&lt;p&gt;The NT kernel drew a hard line between Ring 3 (user mode) and Ring 0 (kernel mode) [@custer-inside-nt]. User-mode processes could not directly access kernel memory. The Security Reference Monitor mediated all access to system objects. For the early 1990s, this was a significant advance over DOS and Windows 9x, where applications and the OS shared the same memory space with no isolation at all.Dave Cutler previously designed VMS at Digital Equipment Corporation (DEC). Many NT design principles -- including the SRM, the object manager, and the layered architecture -- trace directly back to VMS. The letters &quot;WNT&quot; are famously one character ahead of &quot;VMS&quot; in the alphabet.&lt;/p&gt;
&lt;p&gt;But the NT model contained a fatal assumption: &lt;strong&gt;all kernel-mode code is equally trusted&lt;/strong&gt;. Once a driver or exploit gained Ring 0 access, it shared the same address space and privilege level as the kernel itself. It could read and write any memory, modify the System Service Dispatch Table (SSDT), manipulate the Interrupt Descriptor Table (IDT), or unlink processes from the EPROCESS active process list.&lt;/p&gt;
&lt;p&gt;This was the golden age of kernel-mode rootkits. Jamie Butler&apos;s FU rootkit (2004) used Direct Kernel Object Manipulation (DKOM) to unlink processes from the active process list, making malicious processes invisible to Task Manager, antivirus tools, and every other system utility [@hoglund-rootkits]. SSDT hooking allowed rootkits to intercept and redirect any system call, providing total control over OS behavior.&lt;/p&gt;
&lt;p&gt;Mark Russinovich and Bryce Cogswell built the Sysinternals tools to make these kernel internals visible to defenders [@sysinternals-story]. Process Explorer, Filemon, and Regmon became essential diagnostic instruments. But visibility is not protection. Defenders could see the problem; they could not stop it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The NT kernel drew one hard line -- user mode versus kernel mode. When attackers crossed that line, there was nothing left to protect. Every security mechanism, every credential, every policy lived in the same flat address space. Microsoft needed to draw a new line.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Software Guards for a Hardware Problem: PatchGuard and Friends&lt;/h2&gt;
&lt;p&gt;What do you do when the prisoners are as powerful as the guards? You send in more guards at the same level. That was Microsoft&apos;s first strategy -- and its fundamental flaw.&lt;/p&gt;

A software-only kernel integrity monitor introduced in 2005 for 64-bit Windows. PatchGuard periodically checks critical kernel structures (SSDT, IDT, GDT, processor MSRs) for unauthorized modifications and forces a Blue Screen of Death (CRITICAL_STRUCTURE_CORRUPTION) if tampering is detected.
&lt;p&gt;PatchGuard arrived in Windows XP x64 and Windows Server 2003 SP1 in 2005 [@wp-patchguard]. It used obfuscated, randomized integrity checks to detect unauthorized modifications to kernel structures. If it caught tampering, it triggered a BSOD. On the surface, this seemed like a strong defense.PatchGuard&apos;s internal implementation uses extensive obfuscation: randomized check intervals, encrypted context blocks, and self-protecting code that resists static analysis. Microsoft never published its internal design, treating security through obscurity as a deliberate delaying tactic against attackers.&lt;/p&gt;
&lt;p&gt;Mandatory kernel-mode code signing followed with Windows Vista x64 in 2007, requiring all kernel drivers to carry a valid &lt;a href=&quot;https://paragmali.com/blog/windows-app-identity-33-year-reinvention/&quot; rel=&quot;noopener&quot;&gt;Authenticode&lt;/a&gt; signature [@ms-kmcs]. Data Execution Prevention (DEP) marked memory pages as non-executable [@ms-dep]. Address Space Layout Randomization (ASLR) randomized the memory layout of loaded modules [@ms-mitigations]. Supervisor Mode Execution Prevention (SMEP) blocked kernel code from executing user-mode memory pages [@ms-mitigations].&lt;/p&gt;
&lt;p&gt;Each mitigation raised the cost of attack. Together, they made kernel exploitation significantly harder. But each one had a fatal weakness.&lt;/p&gt;

An attack technique where adversaries install a legitimately signed but vulnerable third-party driver, then exploit the driver&apos;s vulnerability to gain arbitrary kernel-mode code execution. Because the driver carries a valid signature, it bypasses kernel-mode code signing enforcement.
&lt;p&gt;&lt;strong&gt;PatchGuard runs at Ring 0 -- the same privilege level as the attackers it monitors.&lt;/strong&gt; In 2019, the InfinityHook project demonstrated how to hook kernel callbacks via the Event Tracing for Windows (ETW) subsystem without patching any kernel structures that PatchGuard checks [@infinityhook-github]. PatchGuard never noticed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kernel-mode code signing stops unsigned drivers but not signed-and-vulnerable ones.&lt;/strong&gt; The BYOVD technique became a staple of advanced persistent threat (APT) groups: install a legitimately signed driver with a known vulnerability, exploit that vulnerability, and gain arbitrary kernel execution while all code signing checks pass [@ms-vuln-drivers].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DEP is bypassed by Return-Oriented Programming (ROP).&lt;/strong&gt; Instead of injecting new code, attackers chain existing executable code snippets (&quot;gadgets&quot;) to achieve arbitrary computation [@wp-rop]. &lt;strong&gt;ASLR has limited entropy&lt;/strong&gt; on 32-bit systems and is defeated by information leaks that reveal randomized base addresses [@wp-aslr].&lt;/p&gt;

Benjamin Delpy released Mimikatz in 2011, and the security world was never the same. What began as a proof-of-concept for extracting plaintext passwords from LSASS memory became the single most-used credential theft tool in real-world attacks. Red teams used it. Nation-state actors used it. Ransomware gangs used it. The tool&apos;s existence -- more than any theoretical argument -- forced Microsoft to confront the fact that LSASS credentials in a flat kernel address space were indefensible. Credential Guard was Mimikatz&apos;s direct response [@mimikatz-github].

PatchGuard was a guard who could be knocked out by the very prisoners it watched. A defense sharing its privilege level with the attacker can always, given sufficient motivation, be subverted.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; No software-only defense can protect against an attacker at the same privilege level. This is not a fixable bug -- it is a structural limitation. PatchGuard delays attacks; it cannot prevent them. Microsoft needed something that kernel-mode code could not even reach.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Building the Foundation: Secure Boot and the Trust Chain&lt;/h2&gt;
&lt;p&gt;If you cannot trust the kernel at runtime, can you at least trust that it started clean? UEFI Secure Boot bet on that premise.&lt;/p&gt;
&lt;p&gt;Windows 8 (October 2012) mandated Secure Boot for certified hardware, establishing a cryptographic chain of trust from firmware through bootloader to OS kernel [@ms-secure-boot]. Only components signed by trusted authorities could execute during the boot process. Measured Boot extended this by hashing each boot component into TPM Platform Configuration Registers (PCRs), creating a verifiable boot log that remote attestation services could check [@ms-trusted-boot].&lt;/p&gt;
&lt;p&gt;This was a real advance. Bootkits like TDL4/Alureon, which operated below the OS and were invisible to all software-based defenses, were effectively blocked [@ms-alureon]. The boot chain was now cryptographically verified.&lt;/p&gt;
&lt;p&gt;But Secure Boot had a critical gap: it protected the boot process, not runtime. Once Windows loaded and started executing, a kernel exploit could compromise the system just as before. PatchGuard was still the only runtime defense, and we have already seen its limitations.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In 2023, ESET researchers confirmed BlackLotus -- the first publicly known UEFI bootkit that bypassed Secure Boot on fully updated Windows systems. It exploited CVE-2022-21894, using a legitimately signed but vulnerable Windows boot manager to load malicious code before the OS [@blacklotus-eset]. The attack demonstrated that even boot-time trust chains can be undermined via BYOVD-style techniques applied to the boot stack itself.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Secure Boot ensured the system started clean but could not keep it clean. Microsoft needed runtime isolation -- and the key technology was already sitting on millions of machines, unused for this purpose: the hypervisor.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Breakthrough: Virtual Trust Levels and the Secure Kernel&lt;/h2&gt;
&lt;p&gt;The insight that changed everything was deceptively simple: if Ring 0 attackers can compromise anything at Ring 0, create a Ring -1. The hypervisor was already there.&lt;/p&gt;
&lt;p&gt;Intel VT-x and AMD-V hardware virtualization extensions, shipping since 2005-2006, gave the hypervisor a privilege level above the OS kernel [@x86-virtualization]. Microsoft&apos;s Hyper-V already used this capability for virtual machines. The breakthrough was recognizing that the same hardware could create a security boundary &lt;em&gt;within a single OS instance&lt;/em&gt; -- not a separate VM, but a hardware-isolated execution context that the kernel could not reach.&lt;/p&gt;

A hardware-enforced execution environment created by the Hyper-V hypervisor using Second Level Address Translation (SLAT). VTL0 is the Normal World where the standard NT kernel, drivers, and applications run. VTL1 is the Secure World where securekernel.exe and security-critical trustlets execute. VTL1 memory is physically inaccessible to all VTL0 code, including the NT kernel.

A hardware feature (Intel Extended Page Tables / AMD Nested Page Tables) that provides a second layer of virtual-to-physical address translation managed by the hypervisor. SLAT enables the hypervisor to control which physical memory pages each VTL can access, making VTL1 memory invisible to VTL0 without any software-level enforcement that could be bypassed.
&lt;p&gt;In May 2015, Brad Anderson announced Virtualization-Based Security, Device Guard, and Credential Guard at Microsoft Ignite [@anderson-ignite-2015]. The initial Windows 10 release, version 1507 (July 2015), shipped with VBS, creating two Virtual Trust Levels: VTL0 (Normal World) and VTL1 (Secure World) [@ms-vbs].&lt;/p&gt;

flowchart TB
    subgraph VTL1[&quot;VTL1 -- Secure World&quot;]
        SK[&quot;securekernel.exe\n(Secure Kernel)&quot;]
        IUM[&quot;Isolated User Mode&quot;]
        LSAISO[&quot;lsaiso.exe\n(Credential Guard)&quot;]
        ENCLAVE[&quot;VBS Enclaves&quot;]
        IUM --- LSAISO
        IUM --- ENCLAVE
    end
    subgraph VTL0[&quot;VTL0 -- Normal World&quot;]
        NT[&quot;ntoskrnl.exe\n(NT Kernel)&quot;]
        DRIVERS[&quot;Kernel Drivers&quot;]
        LSASS[&quot;lsass.exe\n(LSASS broker)&quot;]
        APPS[&quot;User Applications&quot;]
    end
    subgraph HV[&quot;Hyper-V Hypervisor&quot;]
        SLAT[&quot;SLAT Enforcement\n(Intel EPT / AMD NPT)&quot;]
    end
    HV --&amp;gt;|&quot;enforces memory isolation&quot;| VTL1
    HV --&amp;gt;|&quot;enforces memory isolation&quot;| VTL0
    VTL0 -.-&amp;gt;|&quot;Secure Service Calls\n(controlled boundary)&quot;| VTL1
&lt;p&gt;Here is how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;At boot, the Hyper-V hypervisor initializes and creates both VTLs.&lt;/li&gt;
&lt;li&gt;The standard NT kernel (ntoskrnl.exe), all drivers, and user-mode applications run in VTL0.&lt;/li&gt;
&lt;li&gt;securekernel.exe loads in VTL1 kernel mode. It is a minimal, purpose-built kernel that handles only security-critical functions [@ionescu-bh2015].&lt;/li&gt;
&lt;li&gt;The hypervisor uses SLAT to make VTL1 memory physically inaccessible to VTL0. No amount of Ring 0 code in VTL0 can read or write VTL1 pages.&lt;/li&gt;
&lt;li&gt;Communication between VTL0 and VTL1 occurs only via Secure Service Calls (SSCs) -- controlled hypercalls that cross the VTL boundary under strict validation [@ms-vbs].&lt;/li&gt;
&lt;/ol&gt;

A process running in VTL1 Isolated User Mode (IUM), protected from all VTL0 access by hypervisor-enforced memory isolation. The canonical example is lsaiso.exe, the Credential Guard trustlet that holds NTLM hashes and Kerberos tickets in VTL1 where even a fully compromised NT kernel cannot reach them.
&lt;p&gt;securekernel.exe is deliberately minimal. While ntoskrnl.exe is a large general-purpose kernel, securekernel.exe is a much smaller, purpose-built VTL1 kernel whose exact size varies by Windows build. A smaller codebase means a smaller attack surface -- every line of code in VTL1 is a potential entry point for attackers, so Microsoft keeps it as small as possible.&lt;/p&gt;
&lt;p&gt;Alex Ionescu&apos;s 2015 Black Hat presentation was the first major public technical teardown of the Secure Kernel Mode (SKM) and Isolated User Mode (IUM) architecture [@ionescu-bh2015]. Rafal Wojtczuk (Bromium) followed in 2016 with the first independent security audit of VBS, mapping the trust boundaries and identifying the secure call interface as the primary attack surface [@wojtczuk-bh2016].&lt;/p&gt;
&lt;p&gt;What can an attacker with full SYSTEM access in VTL0 &lt;em&gt;not&lt;/em&gt; do?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read credentials protected by Credential Guard&lt;/li&gt;
&lt;li&gt;Load unsigned kernel drivers when HVCI is enabled&lt;/li&gt;
&lt;li&gt;Access VTL1 memory or modify Secure Kernel data structures&lt;/li&gt;
&lt;li&gt;Disable VBS without rebooting (and with Secure Boot + UEFI lock, not easily even then)&lt;/li&gt;
&lt;/ul&gt;

For the first time, an attacker with full NT kernel compromise could not access secrets protected in VTL1. This fundamentally changed the Windows threat model.
&lt;p&gt;For the first time, full NT kernel compromise was no longer game over. But what, exactly, does this new architecture protect?&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Pillars: What the Secure Kernel Protects&lt;/h2&gt;
&lt;p&gt;The Secure Kernel is not a product -- it is a platform. Five distinct security features stand on its shoulders, each protecting a different class of asset.&lt;/p&gt;
&lt;h3&gt;Credential Guard&lt;/h3&gt;
&lt;p&gt;When Credential Guard is enabled, NTLM password hashes and Kerberos Ticket-Granting Tickets (TGTs) are stored exclusively in lsaiso.exe -- a trustlet running in VTL1 [@ms-credential-guard]. The VTL0 lsass.exe process acts as a broker: authentication requests from VTL0 are forwarded to lsaiso.exe via secure RPC over the VTL boundary. lsaiso.exe performs cryptographic operations (challenge signing, ticket generation) within VTL1 and returns only the result -- never the raw secret.&lt;/p&gt;

sequenceDiagram
    participant App as Application (VTL0)
    participant LSASS as lsass.exe (VTL0)
    participant HV as Hypervisor
    participant LSAISO as lsaiso.exe (VTL1)
    App-&amp;gt;&amp;gt;LSASS: Authentication request
    LSASS-&amp;gt;&amp;gt;HV: Secure Service Call
    HV-&amp;gt;&amp;gt;LSAISO: Forward to VTL1
    LSAISO-&amp;gt;&amp;gt;LSAISO: Sign challenge with stored credential
    LSAISO-&amp;gt;&amp;gt;HV: Return signed response (NOT the raw secret)
    HV-&amp;gt;&amp;gt;LSASS: Forward to VTL0
    LSASS-&amp;gt;&amp;gt;App: Authentication result
    Note over LSASS: Even SYSTEM access here cannot read VTL1 memory
&lt;p&gt;Even a Mimikatz-wielding attacker with SYSTEM access in VTL0 gets nothing -- the raw credentials never exist in VTL0 memory. Credential Guard is enabled by default on domain-joined, non-DC Windows 11 22H2+ systems that meet VBS hardware requirements [@ms-credential-guard].&lt;/p&gt;
&lt;h3&gt;HVCI / Memory Integrity&lt;/h3&gt;

A VBS feature (also called &quot;Memory Integrity&quot;) that enforces kernel-mode code integrity from VTL1. HVCI ensures only signed code executes in the kernel and enforces W^X (Write XOR Execute) policy on all kernel memory pages via SLAT. No kernel memory page can be both writable and executable simultaneously.

A memory protection policy enforcing that a page can be either writable or executable, but never both simultaneously. HVCI enforces W^X across all kernel memory via SLAT page permissions controlled from VTL1, preventing attackers from injecting and executing arbitrary code in the kernel.
&lt;p&gt;HVCI moves code integrity enforcement from VTL0 into VTL1 [@ms-hvci]. Before any kernel-mode driver loads, its signature is verified by VTL1 code integrity services. HVCI enforces W^X on kernel memory pages using SLAT: page table modifications that would create a writable-and-executable page are trapped by the hypervisor and denied. Even if an attacker achieves kernel execution in VTL0, they cannot load unsigned drivers or make arbitrary kernel memory executable.On newer CPUs, Intel Mode-Based Execution Control (MBEC, Kaby Lake / 7th Gen+) and AMD Guest Mode Execute Trap (GMET, Zen 2+) provide hardware-accelerated W^X enforcement. Older CPUs rely on software emulation (&quot;Restricted User Mode&quot;), which increases overhead.&lt;/p&gt;

flowchart TD
    A[&quot;Driver load request\n(VTL0)&quot;] --&amp;gt; B[&quot;Signature check\n(VTL1 code integrity)&quot;]
    B --&amp;gt;|&quot;Valid signature&quot;| C[&quot;Set page permissions:\nExecutable + Read-Only&quot;]
    B --&amp;gt;|&quot;Invalid signature&quot;| D[&quot;BLOCKED\nDriver cannot load&quot;]
    E[&quot;Attacker tries to\nmake page W+X&quot;] --&amp;gt; F{&quot;SLAT check\n(Hypervisor)&quot;}
    F --&amp;gt;|&quot;Violation: W+X&quot;| G[&quot;DENIED\nPage remains Read-Only&quot;]
    F --&amp;gt;|&quot;Valid: W XOR X&quot;| H[&quot;Allowed&quot;]
&lt;h3&gt;VBS Enclaves&lt;/h3&gt;

An isolated memory region backed by VTL1 that allows third-party applications to protect secrets from even admin-level OS compromise. The host application in VTL0 communicates with the enclave via the CallEnclave API. Enclave memory is invisible to all VTL0 code, including the NT kernel. Available since Windows 11 24H2.
&lt;p&gt;Starting with Windows 11 24H2, third-party developers can create their own VTL1-protected enclaves -- isolated memory regions for protecting application-level secrets like encryption keys and authentication tokens [@pulapaka-vbs-enclaves]. Unlike Intel SGX, VBS Enclaves require no specialized hardware beyond a VBS-capable CPU [@ms-vbs-enclaves]. Developers define enclave interfaces using EDL (Enclave Description Language) files and build with the VBS Enclave Tooling SDK [@vbs-enclave-tooling].&lt;/p&gt;

The development model works like this: you create an enclave DLL, sign it with a Trusted Signing certificate, and load it via the host application. The enclave runs in VTL1 user mode with access to a limited API surface -- no general Windows API access. All inputs from the VTL0 host must be validated and copied into VTL1 before use. Microsoft&apos;s developer guide covers the details [@ms-vbs-enclaves-dev], and the VBS Enclave Tooling package provides Rust crate support and Visual Studio 2022+ integration [@vbs-enclave-tooling].
&lt;h3&gt;System Guard Runtime Attestation&lt;/h3&gt;
&lt;p&gt;System Guard extends the trust chain from boot into runtime [@ms-system-guard]. A trustlet running in VTL1 periodically measures the integrity of critical system components -- boot state, kernel integrity, driver signatures -- and signs these measurements using a hardware-backed TPM key. Because the measurement code runs in VTL1, it is protected from tampering by compromised VTL0 code [@ms-system-guard-hw]. Remote attestation services (such as &lt;a href=&quot;https://paragmali.com/blog/the-defenders-dilemma-microsoft-antivirus/&quot; rel=&quot;noopener&quot;&gt;Microsoft Defender for Endpoint&lt;/a&gt;) can verify these signed reports to confirm device health -- enabling zero-trust conditional access decisions.&lt;/p&gt;
&lt;h3&gt;Secured-core PCs&lt;/h3&gt;
&lt;p&gt;Secured-core PCs integrate hardware, firmware, and VBS into a single security platform requirement [@ms-secured-core]. Certified hardware must include a 64-bit CPU with SLAT, IOMMU for DMA protection, &lt;a href=&quot;https://paragmali.com/blog/the-tpm-in-windows-one-primitive-twenty-five-years-and-the-c/&quot; rel=&quot;noopener&quot;&gt;TPM 2.0&lt;/a&gt;, UEFI with Secure Boot, SMM protection, DRTM support, and VBS/HVCI enabled and firmware-locked. Major OEMs -- Dell, HP, Lenovo, Microsoft Surface -- ship Secured-core PCs for enterprise and government customers.&lt;/p&gt;
&lt;p&gt;VBS also enables additional isolation features beyond these core pillars. Windows Defender Application Guard (WDAG) uses Hyper-V containers to isolate untrusted browser sessions and Office documents, preventing web-based exploits from reaching the host OS. Hyper-V container isolation provides similar protection for containerized workloads.&lt;/p&gt;
&lt;h3&gt;Decision Guide&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Recommended Approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Protect domain credentials from Pass-the-Hash/Ticket&lt;/td&gt;
&lt;td&gt;Enable Credential Guard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prevent unsigned kernel driver loading&lt;/td&gt;
&lt;td&gt;Enable HVCI / Memory Integrity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protect application-level secrets from admin attacks&lt;/td&gt;
&lt;td&gt;Develop a VBS Enclave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify device integrity for zero-trust&lt;/td&gt;
&lt;td&gt;Enable System Guard Runtime Attestation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum baseline security for new hardware&lt;/td&gt;
&lt;td&gt;Require Secured-core PC certification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The Secure Kernel now protects credentials, code integrity, application secrets, and device health. It is deployed across many millions of Windows 11 and Windows Server machines via VBS-by-default. But is it unbreakable?&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;How Others Solve This Problem: Competing Approaches&lt;/h2&gt;
&lt;p&gt;Windows is not alone in this challenge. Intel, AMD, and ARM each built their own answer to the same question: how do you protect secrets from a compromised OS? Each made different trade-offs.&lt;/p&gt;
&lt;h3&gt;Intel SGX&lt;/h3&gt;
&lt;p&gt;Intel Software Guard Extensions provided hardware enclaves at the CPU level without requiring a hypervisor [@wp-sgx]. Application code and data inside an SGX enclave were encrypted in memory and isolated from the OS, hypervisor, and other applications. The idea was compelling: trust nothing but the CPU itself.&lt;/p&gt;
&lt;p&gt;Then side-channel attacks proved the CPU itself was not trustworthy. The Foreshadow attack (2018) exploited L1 Terminal Fault to extract data directly from SGX enclaves via CPU cache side channels [@foreshadow]. Intel deprecated SGX across 11th Gen client CPUs, including Tiger Lake mobile and Rocket Lake desktop, and continued that direction with 12th Gen Alder Lake [@wp-sgx].&lt;/p&gt;
&lt;h3&gt;AMD SEV-SNP&lt;/h3&gt;
&lt;p&gt;AMD Secure Encrypted Virtualization with Secure Nested Paging (SEV-SNP) encrypts VM memory with per-VM keys and enforces page ownership via a Reverse Map Table (RMP) -- a hardware table that records which VM owns each physical page [@amd-sev]. Even the hypervisor cannot read or remap guest memory without the guest&apos;s consent. This is a fundamentally different trust model from VBS: SEV-SNP &lt;em&gt;distrusts&lt;/em&gt; the hypervisor, while VBS &lt;em&gt;trusts&lt;/em&gt; it. SEV-SNP protects VMs in multi-tenant cloud environments (like Azure Confidential VMs) but does not provide intra-OS isolation within a single machine the way VBS does. The two are complementary, not competing.&lt;/p&gt;
&lt;h3&gt;Intel TDX&lt;/h3&gt;
&lt;p&gt;Intel Trust Domain Extensions create hardware-isolated Trust Domains for VMs, excluding the hypervisor from the trusted computing base [@intel-tdx]. The TDX Module runs in a special CPU mode called Secure Arbitration Mode (SEAM) and mediates all interactions between the hypervisor and Trust Domains -- the hypervisor can schedule TD VMs but cannot read their memory or registers. Like SEV-SNP, TDX targets cloud confidential computing rather than intra-OS protection. It complements VBS rather than replacing it.&lt;/p&gt;
&lt;h3&gt;ARM TrustZone&lt;/h3&gt;
&lt;p&gt;ARM TrustZone partitions the CPU into a Secure World and a Normal World using a hardware security state bit, predating VBS by a decade (2004 vs. 2015) [@arm-trustzone]. World transitions happen through a Secure Monitor Call (SMC) instruction, handled by firmware or a trusted OS like OP-TEE. The concept is similar to VBS -- two execution worlds with hardware isolation -- but the mechanism differs. TrustZone has a smaller attack surface (no hypervisor in the path) but is less flexible: it typically supports only two worlds with coarser granularity. TrustZone dominates mobile and embedded devices; Windows on ARM still uses the hypervisor-based VBS model for VTL0/VTL1 separation, the same architecture as VBS on x64.ARM TrustZone predates VBS by over a decade. The concept of hardware-enforced dual execution worlds was well established in the mobile/embedded world long before Microsoft applied the idea to desktop Windows. The insight was not the dual-world concept itself, but using the x86 hypervisor to implement it.&lt;/p&gt;
&lt;h3&gt;Linux&lt;/h3&gt;
&lt;p&gt;No production equivalent of VBS exists in mainline Linux. Linux relies on Mandatory Access Control (SELinux/AppArmor), container isolation (namespaces/cgroups), and VM-level isolation via SEV-SNP or TDX for cloud workloads. Google&apos;s pKVM (Protected KVM) in Android and ChromeOS is the closest parallel -- it uses the hypervisor to isolate a secure VM from the host kernel, similar in spirit to VTL1. Research projects have proposed similar intra-OS isolation for desktop Linux, but none has reached mainline. Linux&apos;s security philosophy favors defense-in-depth via many smaller mechanisms rather than a single architectural boundary.&lt;/p&gt;
&lt;h3&gt;Cross-Platform Comparison&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Windows VBS&lt;/th&gt;
&lt;th&gt;Intel SGX&lt;/th&gt;
&lt;th&gt;AMD SEV-SNP&lt;/th&gt;
&lt;th&gt;Intel TDX&lt;/th&gt;
&lt;th&gt;ARM TrustZone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Isolation granularity&lt;/td&gt;
&lt;td&gt;OS-level (VTL split)&lt;/td&gt;
&lt;td&gt;Process-level enclaves&lt;/td&gt;
&lt;td&gt;VM-level&lt;/td&gt;
&lt;td&gt;VM-level&lt;/td&gt;
&lt;td&gt;2 worlds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trusts the hypervisor?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;N/A (no hypervisor)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory encryption&lt;/td&gt;
&lt;td&gt;No (isolation only)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (full VM)&lt;/td&gt;
&lt;td&gt;Yes (full VM)&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary use case&lt;/td&gt;
&lt;td&gt;Desktop/server OS&lt;/td&gt;
&lt;td&gt;Legacy high-assurance&lt;/td&gt;
&lt;td&gt;Cloud confidential VMs&lt;/td&gt;
&lt;td&gt;Cloud confidential VMs&lt;/td&gt;
&lt;td&gt;Mobile/IoT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status (2025)&lt;/td&gt;
&lt;td&gt;Active, expanding&lt;/td&gt;
&lt;td&gt;Deprecated on consumer&lt;/td&gt;
&lt;td&gt;GA on major clouds&lt;/td&gt;
&lt;td&gt;Rolling out&lt;/td&gt;
&lt;td&gt;Widely deployed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known weakness&lt;/td&gt;
&lt;td&gt;Rollback, side-channels&lt;/td&gt;
&lt;td&gt;Foreshadow, deprecated&lt;/td&gt;
&lt;td&gt;Physical attacks&lt;/td&gt;
&lt;td&gt;Early deployment&lt;/td&gt;
&lt;td&gt;Firmware attacks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Every platform bets on a different trust anchor. VBS trusts the hypervisor. SEV-SNP trusts only the CPU and its encryption keys. SGX trusted the CPU itself -- until side-channel attacks proved that wrong. The uncomfortable question follows: what &lt;em&gt;cannot&lt;/em&gt; VBS protect against?&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Limits: What VBS Cannot Protect Against&lt;/h2&gt;
&lt;p&gt;Every security boundary has an edge. VBS&apos;s edge is more nuanced than most defenders realize.&lt;/p&gt;
&lt;h3&gt;Attacking the Secure Kernel Directly&lt;/h3&gt;
&lt;p&gt;In August 2020, Saar Amar and Daniel King of Microsoft&apos;s own MSRC stood on the Black Hat stage and demonstrated something the community had feared: direct exploitation of securekernel.exe itself [@amar-bh2020]. Using a custom fuzzer called Hyperseed, they found the first five vulnerabilities in the secure call interface within two weeks; combined with continued manual auditing, they ultimately disclosed ten vulnerabilities [@amar-publications]. Memory corruption bugs in pool management and interface validation allowed VTL0 code to achieve code execution inside VTL1 -- breaking the isolation entirely.&lt;/p&gt;
&lt;p&gt;All vulnerabilities were patched before disclosure. Microsoft has since added mitigations: improved KASLR, Control Flow Guard (CFG) in VTL1, and stricter input validation. But the attack proved that VTL1 is not invulnerable -- the secure call interface is a real attack surface, and any bug there defeats all VBS guarantees.&lt;/p&gt;
&lt;h3&gt;Pass-the-Challenge: The Protocol-Level Bypass&lt;/h3&gt;
&lt;p&gt;Oliver Lyak&apos;s &quot;Pass-the-Challenge&quot; research revealed a subtle limitation of Credential Guard [@lyak-pass-the-challenge]. Credential Guard prevents credential &lt;em&gt;extraction&lt;/em&gt; -- but it cannot prevent credential &lt;em&gt;use&lt;/em&gt;. An attacker with SYSTEM access can relay NTLM authentication challenges through lsaiso.exe, using the machine as an &quot;NTLM oracle.&quot; The raw hash never leaves VTL1, but the attacker can still sign challenges on demand.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Credential Guard perfectly isolates secrets in VTL1, but the VTL0 broker (lsass.exe) necessarily provides an interface for using those secrets. Pass-the-Challenge exploits that interface -- not to extract secrets, but to relay them. This is a fundamental design tension: the more useful the isolation boundary, the more attack surface the boundary&apos;s API exposes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Side-Channel Attacks&lt;/h3&gt;
&lt;p&gt;Spectre and Meltdown demonstrated that speculative execution creates information leakage channels across any software-enforced boundary [@spectre-paper]. VTL0 and VTL1 share the same physical CPU, including caches, branch predictors, and TLBs. Microsoft has deployed microcode updates and software mitigations (IBRS, STIBP, retpolines) [@ms-spectre-advisory], but these reduce the risk rather than eliminating it. Complete elimination requires fundamentally different CPU designs that do not share microarchitectural state across trust boundaries.&lt;/p&gt;
&lt;h3&gt;The Formal Verification Gap&lt;/h3&gt;

The seL4 microkernel is formally verified -- mathematically proven correct for approximately 8,700 lines of C code [@sel4-whitepaper]. This means its isolation guarantees are not empirical (&quot;we tested it and found no bugs&quot;) but mathematical (&quot;we proved it cannot have certain classes of bugs&quot;). Hyper-V is orders of magnitude larger and more complex. Formally verifying it with current techniques is infeasible. The gap between &quot;extensively tested&quot; and &quot;mathematically proven&quot; is significant: Hyper-V&apos;s isolation is empirically strong, not provably correct. A single hypervisor bug could allow VTL escape.
&lt;h3&gt;Microsoft&apos;s Own Boundary&lt;/h3&gt;
&lt;p&gt;Microsoft explicitly states in its Security Servicing Criteria that an administrator with physical access is &lt;em&gt;not&lt;/em&gt; a security boundary [@ms-servicing-criteria]. VBS defends against remote kernel exploitation and privilege escalation, but not against an administrator who can modify firmware, attach hardware debuggers, or perform DMA or evil-maid-style physical attacks; Microsoft&apos;s VBS guidance separately calls out IOMMU-backed DMA protection as a distinct hardware requirement [@ms-vbs].&lt;/p&gt;
&lt;p&gt;This boundary declaration has practical consequences: it is why CVE-2024-21302 (Windows Downdate) required an opt-in fix rather than an automatic security update -- the attack requires admin privileges.&lt;/p&gt;
&lt;p&gt;VBS is the strongest runtime isolation Windows has ever had. But it is empirically strong, not mathematically proven. And one attack discovered in 2024 threatened to undo it entirely.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Arms Race: Rollback Attacks and the Ongoing Battle&lt;/h2&gt;
&lt;p&gt;In August 2024, Alon Leviev of SafeBreach Labs stood on the Black Hat stage and demonstrated something terrifying: he could silently roll back a &quot;fully patched&quot; Windows system to a state where all VBS protections were vulnerable -- using Windows Update itself.&lt;/p&gt;

I found several vulnerabilities that let me develop Windows Downdate -- a tool to take over the Windows Update process to craft fully undetectable downgrades. -- Alon Leviev, SafeBreach Labs
&lt;p&gt;The Windows Downdate attack (CVE-2024-21302) works by hijacking the Windows Update mechanism to replace current versions of securekernel.exe, ci.dll, and other VBS components with older, vulnerable versions [@leviev-downdate]. The system continues to report itself as &quot;fully patched&quot; while running code with known, exploitable vulnerabilities [@cve-2024-21302]. The attack requires administrator privileges -- which, as we noted, Microsoft does not consider a security boundary.&lt;/p&gt;

sequenceDiagram
    participant Attacker as Attacker (Admin in VTL0)
    participant WU as Windows Update
    participant FS as File System
    participant Boot as Next Boot
    Attacker-&amp;gt;&amp;gt;WU: Hijack update process
    WU-&amp;gt;&amp;gt;FS: Replace securekernel.exe with old version
    WU-&amp;gt;&amp;gt;FS: Replace ci.dll with old version
    Note over FS: System still reports &quot;fully patched&quot;
    FS-&amp;gt;&amp;gt;Boot: Boot with vulnerable binaries
    Boot-&amp;gt;&amp;gt;Boot: VBS runs with known vulnerabilities
    Note over Boot: All previously patched bugs are re-exposed
&lt;p&gt;Microsoft does not consider admin-to-kernel a security boundary, which is why CVE-2024-21302 required an &quot;opt-in&quot; fix rather than an automatic security update. Organizations must explicitly deploy KB5042562 to enable rollback protection.&lt;/p&gt;
&lt;p&gt;Microsoft responded with KB5042562, publishing a SkuSiPolicy.p7b revocation policy to block loading of outdated VBS-related binaries [@ms-rollback-guidance]. A UEFI variable lock reduces the risk of firmware-level rollback, though Leviev&apos;s research demonstrated it can be bypassed through Windows Update manipulation without physical access [@leviev-downdate-update]. But deployment is opt-in and complex -- applying it incorrectly can cause boot failures. And the underlying mechanism (admin-level control over the update process) remains exploitable [@leviev-downdate-update].&lt;/p&gt;
&lt;p&gt;The weaponization of VBS itself followed shortly. At DEF CON 33 in August 2025, Akamai researchers demonstrated &quot;BYOVE&quot; (Bring Your Own Vulnerable Enclave) and &quot;Mirage&quot; -- techniques for running malware inside a VBS enclave, hidden from EDR and antimalware tools that cannot inspect VTL1 memory [@akamai-vbs-weaponization]. The very isolation that protects legitimate secrets can also protect malicious code.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The same VTL1 isolation that makes VBS enclaves secure for legitimate applications makes them invisible to security tools. An attacker who can load a legitimately signed but vulnerable enclave DLL gains a hiding place that no VTL0 security product can inspect. Microsoft is actively hardening the enclave trust boundary [@ms-vbs-enclave-hardening], but the fundamental tension between isolation and visibility persists.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The pattern is clear: VBS raises the cost of attack, attackers find creative bypasses, Microsoft hardens further. The question is no longer &quot;is VBS breakable?&quot; but &quot;where does the research go next?&quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Open Questions: Where Research Is Heading&lt;/h2&gt;
&lt;p&gt;The Secure Kernel is mature but not finished. Five open problems define the next decade of research.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Complete rollback prevention.&lt;/strong&gt; KB5042562 is a start, but complete protection may require hardware-enforced monotonic version counters -- similar to ARM&apos;s anti-rollback fuse bits -- integrated into platform firmware [@ms-rollback-guidance]. Without hardware support, the administrator-who-controls-updates problem remains fundamentally unsolved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secure Kernel vulnerability discovery.&lt;/strong&gt; Jonathan Jagt&apos;s 2025 MSc thesis at Radboud University documented the process of setting up a Secure Kernel debugging environment and analyzed patched security bugs to identify vulnerability patterns [@jagt-thesis]. A key finding: the tooling for VTL1 research is scarce. Building a VTL1 debugging environment requires VMware-specific configurations and custom modifications that most researchers do not have access to. Better tooling would accelerate both offensive and defensive research.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VBS Enclave security model.&lt;/strong&gt; The tension between protecting legitimate secrets and preventing malware evasion has no clean solution. Microsoft&apos;s hardening guidance addresses developer mistakes (TOCTOU races, pointer validation, reentrancy risks) [@ms-vbs-enclave-hardening], but the architectural problem -- that VTL1 isolation is equally useful to attackers and defenders -- requires a new approach to enclave attestation and monitoring.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Formal verification.&lt;/strong&gt; Can we ever prove Hyper-V correct? The seL4 proof covers approximately 8,700 lines of C [@sel4-whitepaper]. Hyper-V is hundreds of thousands of lines. Current verification technology cannot scale to that size. Partial verification of critical subsystems (the SLAT enforcement logic, the secure call dispatcher) might be feasible and would meaningfully reduce the trusted computing base.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Side-channel elimination.&lt;/strong&gt; Requires fundamentally different CPU designs. Current mitigations (microcode patches, partitioned caches, branch prediction barriers) reduce the leakage rate but cannot close the channel entirely while VTL0 and VTL1 share physical hardware [@spectre-paper]. Some academic designs propose physically separate execution units for different trust levels, but these are years from production.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The theoretically perfect system would combine: a formally verified hypervisor, hardware with no shared microarchitectural state between trust levels, a complete binary revocation mechanism preventing all rollback attacks, and zero performance overhead. Each requirement is individually infeasible today. The Secure Kernel is the best available approximation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Windows Secure Kernel is the most significant architectural change to Windows security since the NT reference monitor. It does not make Windows invulnerable -- no technology does. But it changed what &quot;kernel compromise&quot; means.&lt;/p&gt;

gantt
    title Windows Kernel Security Evolution
    dateFormat YYYY
    axisFormat %Y
    section Gen 0
    NT Kernel (flat trust)       :1993, 2005
    section Gen 1
    PatchGuard (KPP)             :2005, 2012
    KMCS (driver signing)        :2007, 2012
    DEP                          :2004, 2012
    ASLR                         :2007, 2012
    SMEP                         :2011, 2015
    section Gen 2
    UEFI Secure Boot             :2012, 2015
    Measured Boot + TPM           :2012, 2015
    section Gen 3
    VBS + Secure Kernel           :2015, 2026
    Credential Guard              :2015, 2026
    HVCI / Memory Integrity       :2015, 2026
    System Guard Attestation      :2018, 2026
    Secured-core PCs              :2019, 2026
    section Gen 3.5
    VBS Enclaves                  :2024, 2026
&lt;p&gt;Modern Windows runs all three generations simultaneously -- PatchGuard still watches for kernel tampering, Secure Boot still verifies the boot chain, and VBS adds hardware-enforced isolation on top. Newer defenses supplement rather than replace earlier ones.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Theory is valuable; practice pays the bills. Here is how to enable, verify, and troubleshoot VBS on your systems.&lt;/p&gt;
&lt;h3&gt;Hardware Requirements&lt;/h3&gt;
&lt;p&gt;VBS requires: a 64-bit CPU with hardware virtualization (Intel VT-x or AMD-V), Second Level Address Translation (Intel EPT or AMD NPT), TPM 2.0, and UEFI firmware with Secure Boot [@ms-vbs]. For optimal HVCI performance, Intel Kaby Lake (7th Gen) or newer (for MBEC) or AMD Zen 2 or newer (for GMET) is recommended [@ms-hvci].&lt;/p&gt;
&lt;h3&gt;Enabling VBS&lt;/h3&gt;
&lt;p&gt;VBS can be enabled through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Group Policy:&lt;/strong&gt; Computer Configuration &amp;gt; Administrative Templates &amp;gt; System &amp;gt; Device Guard &amp;gt; Turn On Virtualization Based Security&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intune/MDM:&lt;/strong&gt; Use the DeviceGuard CSP or endpoint security policies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Registry:&lt;/strong&gt; Set &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\EnableVirtualizationBasedSecurity&lt;/code&gt; to 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HVCI/Memory Integrity can be enabled separately via Windows Security &amp;gt; Device Security &amp;gt; Core Isolation &amp;gt; Memory Integrity.&lt;/p&gt;
&lt;h3&gt;Verifying VBS Status&lt;/h3&gt;
&lt;p&gt;{`
// Simulates Get-CimInstance -ClassName Win32_DeviceGuard
// -Namespace root/Microsoft/Windows/DeviceGuard&lt;/p&gt;
&lt;p&gt;const vbsStatus = {
  VirtualizationBasedSecurityStatus: 2, // 0=Not enabled, 1=Enabled but not running, 2=Running
  RequiredSecurityProperties: [1, 2],   // 1=Hypervisor support, 2=Secure Boot
  AvailableSecurityProperties: [1, 2, 3, 5, 6], // What hardware supports
  SecurityServicesConfigured: [1, 2],   // 1=CredentialGuard, 2=HVCI
  SecurityServicesRunning: [1, 2],      // Which services are active
};&lt;/p&gt;
&lt;p&gt;const statusNames = { 0: &quot;Not enabled&quot;, 1: &quot;Enabled (not running)&quot;, 2: &quot;Running&quot; };
const serviceNames = { 1: &quot;Credential Guard&quot;, 2: &quot;HVCI / Memory Integrity&quot;, 3: &quot;System Guard&quot; };&lt;/p&gt;
&lt;p&gt;console.log(&quot;VBS Status:&quot;, statusNames[vbsStatus.VirtualizationBasedSecurityStatus]);
console.log(&quot;\nConfigured Security Services:&quot;);
vbsStatus.SecurityServicesConfigured.forEach(s =&amp;gt;
  console.log(&quot;  -&quot;, serviceNames[s] || &quot;Unknown (&quot; + s + &quot;)&quot;)
);
console.log(&quot;\nRunning Security Services:&quot;);
vbsStatus.SecurityServicesRunning.forEach(s =&amp;gt;
  console.log(&quot;  -&quot;, serviceNames[s] || &quot;Unknown (&quot; + s + &quot;)&quot;)
);
console.log(&quot;\nTo check on your system, run in PowerShell:&quot;);
console.log(&quot;Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root/Microsoft/Windows/DeviceGuard&quot;);
`}&lt;/p&gt;
&lt;p&gt;You can also verify VBS status via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;msinfo32.exe:&lt;/strong&gt; Look for &quot;Virtualization-based security&quot; in the System Summary&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows Security app:&lt;/strong&gt; Device Security &amp;gt; Core Isolation details&lt;/li&gt;
&lt;/ul&gt;

**Driver compatibility:** Some older drivers violate W^X policy and fail to load with HVCI enabled. Check the Windows Event Log (CodeIntegrity events) for blocked drivers. Microsoft&apos;s Hardware Lab Kit (HLK) provides HVCI compatibility testing.&lt;p&gt;&lt;strong&gt;Performance impact:&lt;/strong&gt; VBS/HVCI adds roughly 5-10% overhead in CPU-bound workloads, especially gaming benchmarks [@vbs-perf]. On modern CPUs with MBEC/GMET, the overhead is lower. For gaming workloads, you may see reduced frame rates in CPU-bound scenarios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Credential Guard and NLA:&lt;/strong&gt; Network Level Authentication can fail if Credential Guard is enabled but the domain controller does not support the required Kerberos extensions. Ensure domain controllers are running Windows Server 2016 or later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cannot enable VBS:&lt;/strong&gt; Verify that virtualization is enabled in BIOS/UEFI settings, Secure Boot is on, and TPM 2.0 is present and enabled. Some older systems lack SLAT support.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 1. Open msinfo32.exe and confirm &quot;Virtualization-based security: Running&quot; 2. Check that &quot;Credential Guard&quot; and &quot;Hypervisor enforced Code Integrity&quot; appear under running services 3. Run &lt;code&gt;Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root/Microsoft/Windows/DeviceGuard&lt;/code&gt; in PowerShell for detailed status 4. Verify Secure Boot is enabled and TPM 2.0 is present&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Windows Secure Kernel is the most important Windows security feature most people have never heard of. It does not make the headlines that zero-days do. But it quietly changed the fundamental question of Windows security -- from &quot;can we keep attackers out of the kernel?&quot; to &quot;what can we protect even after they get in?&quot; The secrets behind the VTL1 wall remain safe. At least until the next chapter of the arms race.&lt;/p&gt;
&lt;hr /&gt;

VBS and HVCI add roughly 5-10% overhead in CPU-bound workloads, with gaming seeing the most noticeable impact [@vbs-perf]. For typical business usage (email, documents, web browsing), the impact is negligible. Modern CPUs with Intel MBEC (Kaby Lake / 7th Gen+) or AMD GMET (Zen 2+) significantly reduce this overhead through hardware-accelerated W^X enforcement.

No. securekernel.exe coexists with ntoskrnl.exe. The NT kernel handles all general OS operations -- process management, file systems, networking, device drivers. The Secure Kernel handles only security-critical functions: credential isolation, code integrity enforcement, enclave management. They run in parallel in separate VTLs.

No. VBS protects specific assets (credentials, code integrity, application secrets) from a compromised kernel. The NT kernel itself can still be exploited -- an attacker can still gain SYSTEM access, install rootkits in VTL0, and control the standard OS environment. What they cannot do is access VTL1-protected secrets or load unsigned kernel drivers (with HVCI enabled).

No. VBS uses the Hyper-V *hypervisor*, not traditional VMs. You can run VBS without creating any virtual machines. The hypervisor runs as a thin layer beneath both VTLs to enforce memory isolation. If you also use Hyper-V VMs, VBS coexists with them.

No. Credential Guard protects stored credentials (NTLM hashes, Kerberos TGTs) from extraction, but it does not eliminate the need for strong authentication. It does not protect against phishing, password reuse, or credential relay attacks (as demonstrated by Pass-the-Challenge [@lyak-pass-the-challenge]). Credential Guard is one layer in a defense-in-depth strategy.

Not without rebooting. And with Secure Boot and a UEFI lock, VBS cannot be easily disabled even across reboots. However, the Windows Downdate attack demonstrated that VBS *components* can be silently downgraded to vulnerable versions without disabling VBS itself [@leviev-downdate]. Deploying KB5042562 rollback protection mitigates this risk.

No. VBS creates isolated execution environments within a single OS instance, not separate VMs. VTL0 and VTL1 share the same OS, the same desktop, the same processes (with the exception of trustlets in VTL1). The isolation is at the memory level via SLAT, not at the OS level. It is more like having a secure safe inside your house than having two separate houses.
&lt;hr /&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;secure-kernel-windows&quot; keyTerms={[
  { term: &quot;VBS&quot;, definition: &quot;Virtualization-Based Security -- uses Hyper-V hypervisor to create hardware-isolated Virtual Trust Levels within a single OS instance&quot; },
  { term: &quot;VTL0&quot;, definition: &quot;Normal World -- where the standard NT kernel, drivers, and applications run&quot; },
  { term: &quot;VTL1&quot;, definition: &quot;Secure World -- where securekernel.exe and security-critical trustlets like lsaiso.exe run, isolated by SLAT&quot; },
  { term: &quot;SLAT&quot;, definition: &quot;Second Level Address Translation (Intel EPT / AMD NPT) -- hardware feature enabling hypervisor-enforced memory isolation between VTLs&quot; },
  { term: &quot;HVCI&quot;, definition: &quot;Hypervisor-Protected Code Integrity -- enforces W^X and code signing from VTL1&quot; },
  { term: &quot;Credential Guard&quot;, definition: &quot;VBS feature isolating NTLM hashes and Kerberos TGTs in VTL1 via lsaiso.exe&quot; },
  { term: &quot;BYOVD&quot;, definition: &quot;Bring Your Own Vulnerable Driver -- attack using signed-but-vulnerable drivers to bypass code signing&quot; },
  { term: &quot;PatchGuard&quot;, definition: &quot;Software-only kernel integrity monitor that runs at Ring 0 -- same level as attackers&quot; },
  { term: &quot;W^X&quot;, definition: &quot;Write XOR Execute -- memory policy preventing pages from being both writable and executable&quot; },
  { term: &quot;Trustlet&quot;, definition: &quot;A process running in VTL1 Isolated User Mode, protected from all VTL0 access&quot; }
]} questions={[
  { q: &quot;Why can&apos;t PatchGuard provide the same security guarantees as VBS?&quot;, a: &quot;PatchGuard runs at Ring 0 -- the same privilege level as the attackers it monitors. Any Ring 0 code can find and disable PatchGuard given sufficient effort. VBS uses the hypervisor (Ring -1) to enforce isolation from a higher privilege level.&quot; },
  { q: &quot;What is the fundamental difference between VBS and AMD SEV-SNP?&quot;, a: &quot;VBS trusts the hypervisor and uses it to protect OS components from a compromised kernel. SEV-SNP distrusts the hypervisor and encrypts VM memory to protect guests from a compromised hypervisor. They address different threat models.&quot; },
  { q: &quot;Why can&apos;t Credential Guard prevent Pass-the-Challenge attacks?&quot;, a: &quot;Credential Guard isolates raw credentials in VTL1 but must provide an interface for using them (via lsaiso.exe). Pass-the-Challenge relays authentication challenges through this interface without extracting the secret -- exploiting the necessary API rather than breaking the isolation.&quot; },
  { q: &quot;What would it take to formally verify Hyper-V&apos;s isolation guarantees?&quot;, a: &quot;seL4 was verified for approximately 8,700 lines of C. Hyper-V is hundreds of thousands of lines. Current formal verification tools cannot scale to this size. Partial verification of critical subsystems (SLAT enforcement, secure call dispatch) might be feasible.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>secure-kernel</category><category>virtualization-based-security</category><category>credential-guard</category><category>hvci</category><category>kernel-security</category><category>hypervisor</category><category>operating-systems</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>