<?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: lsass</title><description>Posts tagged lsass.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 07 Jun 2026 04:13:15 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/lsass/rss.xml" rel="self" type="application/rss+xml"/><item><title>Mimikatz and the Credential-Theft Decade: The Windows Security Wars Part 3 (2009-2014)</title><link>https://paragmali.com/blog/mimikatz-and-the-credential-theft-decade-the-windows-securit/</link><guid isPermaLink="true">https://paragmali.com/blog/mimikatz-and-the-credential-theft-decade-the-windows-securit/</guid><description>Microsoft killed the rootkit class with AppLocker, Secure Boot, ELAM, and AppContainer. Then a side project in C named Mimikatz proved the wrong layer had been hardened.</description><pubDate>Sun, 31 May 2026 00:00:00 GMT</pubDate><content:encoded>
**2009-2014 was Windows security&apos;s parallel-revolution decade.** Microsoft shipped AppLocker, Secure Boot, ELAM, AppContainer, and in-box Defender [@ms-applocker; @ms-secure-boot; @ms-elam], retiring the rootkit class and the unsigned-bootloader class. In the same window, Stuxnet burned four Windows zero-days [@symantec-stuxnet-dossier-v14] against Iranian centrifuges and Benjamin Delpy released Mimikatz, which extracted every cached credential from LSASS in one command [@mimikatz-github; @greenberg-mimikatz-wired]. The defensive playbook closed per-binary attack surface while attackers pivoted up the trust stack to the credential layer that hardened binaries still had to trust. By November 11, 2014, Microsoft had acknowledged in product (Restricted Admin RDP, LSA Protected Process, KB2871997&apos;s WDigest opt-out) [@kb2871997; @ms-lsa-protection] and in print (the Mitigating Pass-the-Hash whitepaper v1 December 2012 and v2 July 2014) [@ms-pth-v1-landing; @ms-pth-v2] that the in-VTL0 LSASS model was structurally indefensible against an admin-privileged attacker on the same host. The architectural answer -- Virtualisation-Based Security and Credential Guard in Windows 10 1507 [@ms-credential-guard] -- ships eight months outside the window and opens Part 4.
&lt;h2&gt;1. Two Continents, Eleven Months Apart&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites.&lt;/strong&gt; This article assumes the reader has the pre-2009 Windows-security context covered by &lt;a href=&quot;https://paragmali.com/blog/two-months-without-code-the-windows-security-wars-part-1-199/&quot; rel=&quot;noopener&quot;&gt;Part 1&lt;/a&gt; and &lt;a href=&quot;https://paragmali.com/blog/eight-primitives-one-worm-the-windows-security-wars-part-2-2/&quot; rel=&quot;noopener&quot;&gt;Part 2&lt;/a&gt;, a working mental model of the Windows process / token / privilege-ring architecture (LSASS, NTLM, Kerberos AS-REQ/TGS-REQ, NTFS DACLs, EPROCESS internals, PCRs, SLAT, VTL0/VTL1), and familiarity with MS-NLMP section 3.3.2 NTLMv2 if you have not seen the construction before [@ms-nlmp-ntlmv2]. The graduate-seminar baseline is &lt;em&gt;Windows Internals&lt;/em&gt; 6e Parts 1 and 2 [@windows-internals-6e-p1; @windows-internals-6e-p2].&lt;/p&gt;
&lt;p&gt;June 17, 2010. An antivirus analyst at VirusBlokAda in Minsk named Sergey Ulasen receives a sample from an Iranian customer whose Windows boxes are rebooting on their own [@zetter-countdown-to-zero-day]. The dropper carries valid Authenticode signatures from Realtek Semiconductor and JMicron Technology [@symantec-stuxnet-dossier-v14]. The worm propagates via a previously unknown LNK shortcut bug that fires when Windows merely &lt;em&gt;displays&lt;/em&gt; the icon of a crafted file [@ms-bulletin-ms10-046]. Eleven months later, in May 2011, a French government IT engineer named Benjamin Delpy publishes a closed-source proof-of-concept called Mimikatz that pulls NT hashes and Kerberos tickets out of the LSASS process memory of every Windows box he has ever logged into and prints them to the operator&apos;s console in one command [@greenberg-mimikatz-wired; @wikipedia-mimikatz]. The conventional history puts these two events on different pages of different books. This article argues they are the two visible faces of a single structural shift.&lt;/p&gt;
&lt;p&gt;The shift is easy to state and easy to underrate. &lt;em&gt;Defensive success at one layer reliably produces attacker innovation at the next layer up.&lt;/em&gt; Microsoft spent the 2009-2014 window shipping the most ambitious per-binary hardening programme of any commercial operating system in history -- AppLocker, ASLR improvements, BitLocker To Go, UEFI Secure Boot, Measured Boot, Early Launch Antimalware, AppContainer, the WinRT sandbox, and in-box Windows Defender [@ms-applocker; @ms-secure-boot; @ms-elam; @windows-internals-6e-p1]. The programme worked. It killed the unsigned-bootloader rootkit class, the pre-antivirus-launch malware class, and the in-process Internet Explorer rendering pwnage class. None of those primitives stopped Stuxnet on a Windows 7 host with USB enabled, and none of them stopped Mimikatz on any host where an administrator opened a console.&lt;/p&gt;
&lt;p&gt;The reason is structural, not engineering. Every per-binary mitigation prevents the &lt;em&gt;wrong&lt;/em&gt; code from running. Stuxnet&apos;s win32k.sys kernel exploit and Mimikatz&apos;s &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; command did not need to be wrong code. They needed to be the &lt;em&gt;right&lt;/em&gt; code -- code an administrator chose to run, or a signed driver Microsoft itself had allowed to load -- running where the credentials lived. The credentials lived in the memory of a long-lived user-mode service called LSASS, and they lived there by design because the single sign-on contract requires the operating system to re-authenticate the user to network servers without re-prompting [@ms-credentials-processes]. The mitigation surface and the attack surface were not at the same layer.&lt;/p&gt;

timeline
    title 2009-2014 Windows Security Split Screen
    section Defender
        Oct 22 2009 : Windows 7 GA: AppLocker, ASLR improvements, BitLocker To Go
        Oct 26 2012 : Windows 8 GA: Secure Boot, ELAM, AppContainer, in-box Defender
        Oct 17 2013 : Windows 8.1: Restricted Admin RDP, LSA Protected Process
        May 13 2014 : KB2871997: WDigest opt-out, Restricted Admin back-port
        Nov 11 2014 : MS14-066 Schannel patch closes the window
    section Attacker
        Jan 12 2010 : Operation Aurora disclosed (single IE 0-day, espionage)
        Jun 17 2010 : VirusBlokAda identifies Stuxnet from an Iranian customer sample
        Dec 27 2010 : Dang and Ferrie present Stuxnet analysis at 27C3 Berlin
        May 2011 : Delpy releases Mimikatz (closed source)
        Aug 1 2013 : Duckwall and Campbell BlackHat USA Pass-the-Hash 2
        Apr 6 2014 : Mimikatz GitHub repository created
        Aug 7 2014 : Delpy and Duckwall BlackHat USA Golden Ticket reveal
&lt;p&gt;If both events were faces of the same shift, what was the shift? To see it, we have to start with what Microsoft was actually shipping.&lt;/p&gt;
&lt;h2&gt;2. The Hardening Decade: What Microsoft Was Doing 2009-2014&lt;/h2&gt;
&lt;p&gt;The popular story of 2009-2014 is that Microsoft was asleep while the Russians ate their lunch. That story is wrong. Microsoft shipped, in a single five-year window, more new platform-security primitives than the company had shipped in the previous decade combined. The problem was not the engineering. The problem was that the entire programme was orthogonal to the credential layer.&lt;/p&gt;
&lt;h3&gt;2.1 Windows 7 (October 22, 2009): per-binary control, finally&lt;/h3&gt;
&lt;p&gt;Windows 7 was the first Microsoft client operating system shipped after the &lt;a href=&quot;https://paragmali.com/blog/two-months-without-code-the-windows-security-wars-part-1-199/&quot; rel=&quot;noopener&quot;&gt;Trustworthy Computing memo&lt;/a&gt; had finished one full Secure Development Lifecycle revolution. The headline platform addition was &lt;strong&gt;AppLocker&lt;/strong&gt;, an application-control framework that let administrators allow or deny executables, scripts, MSI installers, DLLs, and packaged apps by publisher, file hash, or path [@ms-applocker]. Rules were authored in Group Policy and enforced by the Application Identity service. The rule-collection design was the first time a Microsoft Windows shipped a coherent allowlisting story rather than a bag of registry knobs.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/wdac--hvci-code-integrity-at-every-layer-in-windows/&quot; rel=&quot;noopener&quot;&gt;AppLocker&lt;/a&gt; carried two structural gaps that took years to live down. First, the DLL rule collection was off by default. Enabling it broke application compatibility on almost every real estate. Second, the Application Identity service ran as a normal Windows service, which meant an attacker who reached LocalSystem could &lt;code&gt;sc stop AppIDSvc&lt;/code&gt; and degrade enforcement open until the next reboot.This admin-stoppable-service gap is the design lesson that becomes the brief for Windows Defender Application Control&apos;s kernel-enforced policy model in Part 4 of this series. A third structural gap matters for the credential-theft era this article documents. AppLocker&apos;s publisher- and path-rule design decisions assume the file-system DACL stack enforces a clean read-allow / write-deny split for low-privileged users [@ms-applocker-design]. It does not.&lt;/p&gt;
&lt;p&gt;The well-known operator bypass on a default Windows 7 install proceeds in four steps. Step one: identify a directory whose path matches the AppLocker default &lt;code&gt;%WINDIR%\*&lt;/code&gt; allow rule for non-administrators (&lt;code&gt;%WINDIR%\Tasks&lt;/code&gt; is the canonical example because it ships with permissive ACLs to let the Task Scheduler service write child files). Step two: drop the unsigned payload binary into that directory. Step three: invoke the binary by full path. Step four: observe that AppLocker&apos;s path-rule engine consults the configured policy rather than the file&apos;s actual DACL stack and permits execution because the parent directory matches the allow-rule glob. The bypass exists because AppLocker&apos;s rule evaluation and NTFS&apos;s DACL stack live on two independent rails that disagree about which paths a non-administrator may write; the cleanup that closes this class of bypass landed in Windows Defender Application Control, which is the Part 4 story.&lt;/p&gt;
&lt;p&gt;AppLocker killed the per-binary &quot;double-click an unsigned EXE on a managed desktop&quot; attack class on every estate that deployed it, which turned out to be a strikingly small fraction of the Fortune 500.&lt;/p&gt;
&lt;p&gt;Windows 7 also tightened the in-process mitigation surface. Address Space Layout Randomisation got a new opt-in &lt;em&gt;ForceASLR&lt;/em&gt; flag callable via the loader&apos;s &lt;code&gt;MitigationOptions&lt;/code&gt; field, letting administrators force randomisation even on EXEs and DLLs that had been compiled without the &lt;code&gt;/DYNAMICBASE&lt;/code&gt; linker switch [@windows-internals-6e-p1].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BitLocker To Go for removable media&lt;/strong&gt; finally gave administrators a defensible answer to the lost-USB-stick incident report. The on-disk format is a Full Volume Encryption v2 (FVE2) volume encrypted with plain AES-CBC; unlike fixed-disk &lt;a href=&quot;https://paragmali.com/blog/bitlocker-on-windows-architecture-attacks-and-the-limits-of-/&quot; rel=&quot;noopener&quot;&gt;BitLocker&lt;/a&gt; on Vista and original-release Windows 7, BitLocker To Go &lt;em&gt;disables&lt;/em&gt; the Elephant Diffuser on removable drives so the small unencrypted &lt;em&gt;discovery volume&lt;/em&gt; at the start of the device can ship &lt;code&gt;BitLockerToGo.exe&lt;/code&gt;, the Windows XP / Vista &lt;em&gt;BitLocker To Go Reader&lt;/em&gt; that supports plain AES-CBC only [@ms-bitlocker-configure]. The Reader prompts for one of three key protectors: a password, a smart card, or an automatic-unlock recovery key escrowed by Group Policy to Active Directory. The discovery-volume design is the operational concession that lets a 2009 administrator hand a BitLocker-To-Go stick to a vendor running Windows XP SP3 without giving the vendor a usable plaintext copy; the diffuser drop is the cryptographic concession that makes the Reader compatibility story possible. The threat-model concession that BitLocker To Go does not cover is the unattended-laptop / cold-boot attack class against the &lt;em&gt;primary&lt;/em&gt; disk&apos;s TPM-released VMK [@ms-bitlocker-countermeasures], which is the Evil-Maid territory Joanna Rutkowska and Alex Tereshkin demonstrated against TrueCrypt full-disk encryption in October 2009 [@rutkowska-evil-maid-2009] and which BitLocker would not fully answer until pre-boot PIN enforcement matured.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DirectAccess&lt;/strong&gt; shipped as an always-on, certificate-anchored, IPsec-over-IPv6 tunnelled successor to traditional VPNs. The architectural design used a dual-tunnel model [@ms-directaccess-design-guide]: an &lt;em&gt;infrastructure tunnel&lt;/em&gt; established at machine boot using a machine certificate, which gave the client reach-back to domain controllers, DNS, and management infrastructure &lt;em&gt;before&lt;/em&gt; any user had logged on; and an &lt;em&gt;intranet tunnel&lt;/em&gt; established at user logon using user credentials, which carried application traffic to the internal corporate network.&lt;/p&gt;
&lt;p&gt;Because DirectAccess required end-to-end IPv6 in an era when public IPv6 was a rounding error, the design layered three transition technologies in priority order: 6to4 (for clients with a public IPv4 address), Teredo (for clients behind NAT), and IP-HTTPS (a TLS-encapsulated IPv6 transport that worked across any environment that allowed outbound HTTPS, included specifically as the fallback for hotel and conference networks that blocked native IPv6 and UDP-Teredo). The always-on-before-logon property is what made DirectAccess operationally distinct from a traditional VPN: a help-desk-recoverable password reset, a Group Policy push, or a software-distribution job could reach a remote machine the instant it had Internet connectivity, with no user action required.DirectAccess was later quietly deprecated in favour of Always On VPN and Microsoft Tunnel; the architectural lesson it carries is that certificate-anchored client trust scales operationally only when the certificate lifecycle is automated end-to-end.&lt;/p&gt;
&lt;p&gt;What this killed: the per-binary &quot;unsigned EXE on a managed desktop&quot; class. What it did not touch: anything inside an LSASS-holding process tree.&lt;/p&gt;
&lt;h3&gt;2.2 Windows 8 (October 26, 2012): the boot chain and the sandbox&lt;/h3&gt;
&lt;p&gt;Windows 8 is the year the per-binary playbook reached architectural maturity. Four primitives shipped at once, and they all aim at distinct points on the trust stack.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UEFI Secure Boot&lt;/strong&gt; anchors the boot chain in firmware. The Platform Key, signed Key Exchange Keys, and the signature database &lt;code&gt;db&lt;/code&gt; together require the firmware to verify the signature of every UEFI driver, every option ROM, and the operating-system loader before transferring control [@ms-secure-boot; @ms-bulletin-ms10-046]. A revocation database &lt;code&gt;dbx&lt;/code&gt; lets Microsoft retire keys and binaries that have been compromised. Windows 8 was the first Microsoft client operating system whose Logo certification required &lt;a href=&quot;https://paragmali.com/blog/secure-boot-in-windows-the-chain-from-sector-zero-to-userini/&quot; rel=&quot;noopener&quot;&gt;Secure Boot&lt;/a&gt; enablement by default; the chain is anchored to the UEFI 2.3.1 Errata C specification (June 2012).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Measured Boot&lt;/strong&gt; complements Secure Boot. Each stage of the boot chain extends a SHA-256 measurement into Platform Configuration Registers 0 through 7 of the Trusted Platform Module, and the TPM event log records what was measured [@windows-internals-6e-p1]. BitLocker can then bind its Volume Master Key release to a specific PCR profile, so a tampered bootloader will not yield the disk key on next boot. Secure Boot decides whether the code is allowed to run; &lt;a href=&quot;https://paragmali.com/blog/measured-boot-the-tcg-event-log-from-srtm-to-pcr-bound-bitlo/&quot; rel=&quot;noopener&quot;&gt;Measured Boot&lt;/a&gt; decides whether to release secrets to the code that ran.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Early Launch Antimalware (ELAM)&lt;/strong&gt; is the first boot-start driver loaded after the kernel. ELAM gets to inspect, classify, and refuse subsequent boot-start drivers via the &lt;code&gt;BDCB_CLASSIFICATION&lt;/code&gt; enumeration, which returns Good, Bad, Unknown, or BadButCritical [@ms-elam].Microsoft&apos;s own ELAM driver, WdBoot.sys, ships with Windows Defender; third-party antivirus vendors such as McAfee, Symantec, CrowdStrike, and SentinelOne ship their own ELAM drivers post-2014. ELAM services themselves run as a Protected Process Light, which prevents lower-signer-level code from injecting into the antimalware engine. ELAM killed the rootkit-loaded-before-AV class that had defined kernel-mode malware tradecraft since the early 2000s.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AppContainer&lt;/strong&gt; introduces the LowBox access token. Each Modern (Metro) Windows Runtime app receives a token with a per-package security identifier and a vector of capability SIDs; resource access checks intersect the capability set with the resource&apos;s discretionary access control list [@windows-internals-6e-p1]. The model is structurally similar to iOS entitlements: the kernel refuses any access the manifest did not declare. Windows 8 also ships the in-box &lt;a href=&quot;https://paragmali.com/blog/the-defenders-dilemma-microsoft-antivirus/&quot; rel=&quot;noopener&quot;&gt;Windows Defender&lt;/a&gt; (replacing the optional Microsoft Security Essentials), and Internet Explorer 10 runs Enhanced Protected Mode inside an &lt;a href=&quot;https://paragmali.com/blog/appcontainer-and-lowbox-tokens-windowss-capability-sandbox/&quot; rel=&quot;noopener&quot;&gt;AppContainer&lt;/a&gt;, killing the in-process IE-rendering pwnage class that had dominated browser-borne malware for a decade.&lt;/p&gt;
&lt;p&gt;A word on branding discipline. Windows 8&apos;s sandbox is correctly named WinRT plus AppContainer plus Modern (Metro) apps. &lt;em&gt;UWP&lt;/em&gt; (Universal Windows Platform) is the Windows 10 brand introduced July 29, 2015; calling any Windows 8 deliverable UWP is a category error.&lt;/p&gt;
&lt;p&gt;What this killed: unsigned-bootloader rootkits (Secure Boot), pre-AV-launch malware (ELAM), in-process IE-rendering pwnage (AppContainer plus Enhanced Protected Mode). What it did not touch: LSASS.&lt;/p&gt;
&lt;h3&gt;2.3 Windows 8.1 and Server 2012 R2 (October 17, 2013): the first counter-pivot&lt;/h3&gt;
&lt;p&gt;Windows 8.1 is where Microsoft first lands product-level controls that &lt;em&gt;directly&lt;/em&gt; answer credential-replay tradecraft.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Restricted Admin RDP&lt;/strong&gt; changes the protocol so that the client never sends the user&apos;s plaintext password to the server&apos;s LSASS [@kb2871997]. Instead, the server issues a network challenge that the client signs with its local NT hash. The classic credential-disclosure-at-server failure mode (a foothold on the RDP server learns every administrator&apos;s plaintext password as they log in) is closed. The replay failure mode is not, but Section 6 evaluates that honestly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LSA Protected Process&lt;/strong&gt; loads the LSASS process as a Protected Process Light with the signer level &lt;code&gt;PsProtectedSignerLsa&lt;/code&gt;. Once Protected, even a process running as NT AUTHORITY\SYSTEM cannot call &lt;code&gt;OpenProcess(PROCESS_VM_READ)&lt;/code&gt; against LSASS [@ms-lsa-protection]. The flag is enabled by setting &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;. The architectural intuition is right; the bypass class lives in kernel mode and gets evaluated in Section 6.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Restricted Admin RDP and LSA Protected Process are the first product-level Microsoft acknowledgements that the credential layer needed its own defensive rail, distinct from the per-binary playbook. Together they foreshadow the architectural pivot that ships in Windows 10 1507 as Virtualisation-Based Security and Credential Guard [@ms-credential-guard]. The full evaluation of both controls -- what they accomplish, what they leave open, and why -- is the subject of Section 6.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every primitive above stops the wrong code from running. The threat model is about to move on.&lt;/p&gt;
&lt;h2&gt;3. Stuxnet: The Nation-State Zero-Day Reveal&lt;/h2&gt;
&lt;h3&gt;3.1 Discovery timeline&lt;/h3&gt;
&lt;p&gt;Sergey Ulasen&apos;s June 17, 2010 sample at VirusBlokAda is the public discovery date [@zetter-countdown-to-zero-day]. The worm had been operating in the wild since at least 2009. Within weeks, Kaspersky, Symantec, and ESET independently confirmed the family. By September 2010, Ralph Langner at Langner Communications had identified the payload&apos;s specific target: Siemens Step 7 industrial-control software running on S7-300 programmable logic controllers, programmed to manipulate the rotor speeds of cascade-mounted gas centrifuges at the Natanz uranium enrichment facility in Iran [@langner-to-kill-a-centrifuge].&lt;/p&gt;
&lt;p&gt;On December 27, 2010, Bruce Dang of Microsoft&apos;s Security Response Center and Peter Ferrie co-presented &quot;Adventures in Analyzing Stuxnet&quot; at the 27th Chaos Communication Congress (27C3) in Berlin [@dang-ferrie-27c3].The venue is 27C3, not 29C3, and Dang&apos;s affiliation is Microsoft MSRC, not Symantec; the talk is the canonical engineering primary for the win32k.sys keyboard-layout kernel exploit. Their first-hand engineering walkthrough of the win32k.sys kernel exploit is the canonical record of how Stuxnet escalated privilege on patched Windows 7 systems. In February 2011, Nicolas Falliere, Liam O Murchu, and Eric Chien of Symantec Security Response published the v1.4 W32.Stuxnet Dossier, which enumerated the four Windows zero-days, the two stolen Authenticode certificates, and the Step 7 / S7-300 payload [@symantec-stuxnet-dossier-v14]. Ralph Langner&apos;s November 2013 &quot;To Kill a Centrifuge&quot; closed the analytical loop by identifying not one but two distinct centrifuge-attacks bundled into the same worm: an earlier rotor-overpressure attack and the later rotor-speed manipulation attack [@langner-to-kill-a-centrifuge].&lt;/p&gt;
&lt;h3&gt;3.2 The four zero-days&lt;/h3&gt;
&lt;p&gt;The Symantec dossier&apos;s accounting of Stuxnet&apos;s Windows zero-days is the canonical inventory. There were four, used across the worm&apos;s propagation and escalation surfaces, &lt;strong&gt;not&lt;/strong&gt; chained in a single sequential exploit.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bulletin&lt;/th&gt;
&lt;th&gt;CVE&lt;/th&gt;
&lt;th&gt;Role in the worm&lt;/th&gt;
&lt;th&gt;Patch date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;MS10-046&lt;/td&gt;
&lt;td&gt;CVE-2010-2568&lt;/td&gt;
&lt;td&gt;LNK shortcut RCE; propagation via USB without autorun [@ms-bulletin-ms10-046]&lt;/td&gt;
&lt;td&gt;August 2, 2010&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MS10-061&lt;/td&gt;
&lt;td&gt;CVE-2010-2729&lt;/td&gt;
&lt;td&gt;Print Spooler RCE; network-layer propagation [@ms-bulletin-ms10-061]&lt;/td&gt;
&lt;td&gt;September 14, 2010&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MS10-073&lt;/td&gt;
&lt;td&gt;CVE-2010-2743&lt;/td&gt;
&lt;td&gt;win32k.sys keyboard-layout local privilege escalation [@ms-bulletin-ms10-073]&lt;/td&gt;
&lt;td&gt;October 12, 2010&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MS10-092&lt;/td&gt;
&lt;td&gt;CVE-2010-3338&lt;/td&gt;
&lt;td&gt;Task Scheduler local privilege escalation [@ms-bulletin-ms10-092]&lt;/td&gt;
&lt;td&gt;December 14, 2010&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The LNK bug (MS10-046) is the propagation-by-USB primitive that gave Stuxnet its air-gap-jumping reputation: merely displaying the icon of a crafted shortcut, which Windows Explorer did automatically when the user opened the USB drive, triggered code execution [@ms-bulletin-ms10-046]. The Print Spooler RCE (MS10-061) addressed a Spooler permissions-validation bug that let Stuxnet propagate over the network as a printer-share request [@ms-bulletin-ms10-061].The Print Spooler attack surface returned a decade later as CVE-2021-34527 PrintNightmare, demonstrating that a sufficiently complex local-privilege-escalation surface tends to be re-discoverable across architectural rewrites. The keyboard-layout LPE (MS10-073) was the one Dang and Ferrie walked at 27C3 -- the kernel indexed a table of function pointers when loading a keyboard layout from disk, and Stuxnet supplied a layout that pointed the index at attacker memory [@ms-bulletin-ms10-073]. The Task Scheduler LPE (MS10-092) corrected the way Task Scheduler conducted integrity checks to validate that tasks ran with their intended user privileges [@ms-bulletin-ms10-092]. Stuxnet also re-used the older MS08-067 NetAPI worm bug on unpatched hosts as a non-zero-day propagation path [@ms-bulletin-ms08-067] -- this is the Conficker bug from October 2008, not a 2010 zero-day, and any four-zero-day count that includes it is wrong.&lt;/p&gt;

flowchart LR
    subgraph Propagation
        A[&quot;LNK shortcut RCE&lt;br /&gt;MS10-046 / CVE-2010-2568&quot;]
        B[&quot;Print Spooler RCE&lt;br /&gt;MS10-061 / CVE-2010-2729&quot;]
    end
    subgraph Escalation
        C[&quot;win32k.sys keyboard-layout LPE&lt;br /&gt;MS10-073 / CVE-2010-2743&quot;]
        D[&quot;Task Scheduler LPE&lt;br /&gt;MS10-092 / CVE-2010-3338&quot;]
    end
    subgraph Payload
        E[&quot;Siemens Step 7 / S7-300 PLC&lt;br /&gt;centrifuge rotor manipulation&quot;]
    end
    A --&amp;gt; C
    A --&amp;gt; D
    B --&amp;gt; C
    B --&amp;gt; D
    C --&amp;gt; E
    D --&amp;gt; E
&lt;h3&gt;3.3 The stolen Authenticode certificates&lt;/h3&gt;
&lt;p&gt;The worm&apos;s dropper was signed by two real, valid &lt;a href=&quot;https://paragmali.com/blog/authenticode-and-catalog-files-the-crypto-foundation-under-w/&quot; rel=&quot;noopener&quot;&gt;Authenticode certificates&lt;/a&gt; issued to Realtek Semiconductor and JMicron Technology [@symantec-stuxnet-dossier-v14]. Both certificates were revoked within weeks of disclosure, but during the operational window of Stuxnet, every signature check Windows performed against the dropper returned a clean verdict.The Realtek and JMicron certificates were not merely stolen out of an email inbox; the corresponding hardware security modules were almost certainly accessed in person at the original equipment manufacturers&apos; facilities in the Hsinchu Science Park, Taiwan -- the long-form reconstruction in Kim Zetter&apos;s &lt;em&gt;Countdown to Zero Day&lt;/em&gt; lays out the physical-access logistics that the wire-only theft hypothesis cannot satisfy [@zetter-countdown-to-zero-day]. This prefigured the supply-chain attack class that becomes SolarWinds a decade later. This was the first publicly analyzed kinetic-effect proof that the code-signing trust root -- Authenticode and the kernel-mode driver signing PKI that depended on it -- was an adversary target rather than a structural defence.&lt;/p&gt;
&lt;h3&gt;3.4 Architectural lessons&lt;/h3&gt;
&lt;p&gt;Two structural lessons emerged from the disclosure cycle. First, USB as an attack surface acquired its own discipline. In February 2011, Microsoft re-released the autorun update covered by Microsoft Security Advisory 967940 / KB971029 as an automatic update via Windows Update, having previously offered it as an optional patch in February 2009 [@krebs-autorun-2011]. Second, IT and operational-technology (OT) cross-domain trust collapsed as a defensible perimeter -- Natanz was an air-gapped network that a USB stick crossed, and every CISO with operational-technology assets had to re-ask the question of whether a nation-state would burn a Windows zero-day to break their plant.&lt;/p&gt;
&lt;h3&gt;3.5 Did Stuxnet defeat any defender primitive Windows 7 shipped?&lt;/h3&gt;
&lt;p&gt;The narrow answer is no, the worm did not need to. Stuxnet&apos;s propagation primitives carried their own attack code -- the LNK bug ran from Explorer, the Spooler bug ran from the printer-share RPC interface -- so they did not need to defeat AppLocker (AppLocker only blocks executions a configured rule denies; an explorer.exe rendering a crafted shortcut was not a denied execution) or ASLR or DEP. The win32k.sys local privilege escalation, however, foreshadowed the Section 5 argument neatly: the per-binary mitigations Windows 7 shipped (AppLocker, ASLR, DEP, ForceASLR) did nothing for a kernel-mode bug, because kernel-mode is where those mitigations are enforced from.&lt;/p&gt;
&lt;h3&gt;3.6 Was Stuxnet really the &lt;em&gt;first&lt;/em&gt; nation-state Windows zero-day operation?&lt;/h3&gt;
&lt;p&gt;Only with two qualifiers. Operation Aurora -- the espionage campaign Google publicly disclosed on January 12, 2010 [@google-aurora-blog; @google-aurora-wayback] -- pre-dates Stuxnet&apos;s June 2010 public identification by roughly five months and used a single Windows / Internet Explorer zero-day, the IE use-after-free catalogued as CVE-2010-0249 [@nvd-cve-2010-0249], for cyber-espionage. Google&apos;s own disclosure stated that &quot;at least twenty other large companies from a wide range of businesses -- including the Internet, finance, technology, media and chemical sectors -- have been similarly targeted&quot; [@google-aurora-wayback]. The publicly named subset that emerged across the January 12-15, 2010 disclosure window included Adobe Systems (acknowledged on the Adobe corporate blog January 12, 2010) [@adobe-aurora-disclosure], Juniper Networks, Rackspace [@wikipedia-operation-aurora], plus Yahoo, Symantec, Northrop Grumman, Dow Chemical, and Morgan Stanley named in Ariana Eunjung Cha and Ellen Nakashima&apos;s Washington Post coverage on January 14, 2010 [@wapo-aurora-cha-nakashima]. Dmitri Alperovitch of McAfee Labs named the campaign &quot;Operation Aurora&quot; on January 14, 2010 based on a &lt;code&gt;\..\Aurora_Src\AuroraVNC\&lt;/code&gt; file-path string recovered from the malware binaries [@mcafee-aurora-alperovitch]. Microsoft patched the IE bug out-of-band as MS10-002 on January 21, 2010 [@ms-bulletin-ms10-002].&lt;/p&gt;

Aurora is the necessary disambiguation. The popular framing of Stuxnet as the first nation-state Windows zero-day operation is *false* without qualifiers. Aurora used one zero-day for espionage in January 2010; Stuxnet used four zero-days for kinetic effect in June 2010. The defensible framing is: *Stuxnet is the first publicly analyzed nation-state Windows operation that burned multiple zero-days for kinetic, physical effect* [@symantec-stuxnet-dossier-v14; @google-aurora-blog; @nvd-cve-2010-0249]. Both qualifiers (&quot;multi-zero-day&quot; and &quot;kinetic / physical&quot;) are load-bearing. Drop either and Aurora falsifies the framing.
&lt;p&gt;Stuxnet showed nation-states would burn four Windows zero-days for a single operation. But four zero-days is an expensive way to compromise a credential, and as it turned out, a French engineer was about to make zero-days irrelevant for the credential-theft problem.&lt;/p&gt;
&lt;h2&gt;4. Mimikatz: The Credential Layer Demolition&lt;/h2&gt;
&lt;p&gt;Benjamin Delpy describes Mimikatz, in Andy Greenberg&apos;s Wired profile, as &quot;a side project to learn C&quot; [@greenberg-mimikatz-wired]. The reader&apos;s natural reaction -- a side project that broke a decade of Microsoft&apos;s most ambitious hardening programme? -- is precisely the point.&lt;/p&gt;
&lt;h3&gt;4.1 Delpy, LSASS, and the May 2011 release&lt;/h3&gt;
&lt;p&gt;Delpy was at the time an IT manager at a French government institution he declines to name [@greenberg-mimikatz-wired]. He had become curious about an architectural quirk: Windows could prompt for his password at logon, then later authenticate him to remote IIS and SMB servers using HTTP Digest without ever asking again. Something inside the OS had to hold a recoverable form of his password. He started reverse-engineering the Local Security Authority Subsystem Service (LSASS) and the credential-provider tree behind it.&lt;/p&gt;

A long-lived user-mode Windows process that holds the secrets the operating system needs to satisfy single sign-on across SMB, RPC, HTTP, RDP, IIS, and MS-SQL without re-prompting the user. By design, LSASS caches NT hashes, Kerberos Ticket-Granting Tickets, and (depending on the loaded security packages) recoverable plaintext credentials [@ms-credentials-processes]. It is the load-bearing target of every credential-extraction tool the next decade produces.
&lt;p&gt;The architectural quirk was structural, not accidental. The single sign-on contract requires the operating system to &lt;em&gt;re-authenticate&lt;/em&gt; the user to network services, and the network protocols of the 1990s and 2000s (NTLM, Kerberos, HTTP Digest, MS-CHAP) all required either a hash, a ticket, or a recoverable plaintext to do that re-authentication [@ms-credentials-processes]. LSASS held all three. There was no way to satisfy the contract without holding the secret in some recoverable form inside an LSASS-controlled memory region.&lt;/p&gt;
&lt;p&gt;Delpy released the first version of Mimikatz in May 2011 as closed-source software [@greenberg-mimikatz-wired; @wikipedia-mimikatz].Delpy describes Mimikatz as &quot;a side project to learn C&quot; in the Wired profile; the framing matters because it underlines that breaking Windows credential security at this depth did not require nation-state resources -- a single engineer with a debugger could do it. Microsoft&apos;s response to his initial private disclosure had been, in his telling, that &quot;you don&apos;t want to fix it&quot;; he made the tool public to force the conversation. The GitHub repository &lt;code&gt;gentilkiwi/mimikatz&lt;/code&gt; was created on April 6, 2014 at 18:30:02 UTC -- the API-verifiable timestamp [@mimikatz-github]. Any &quot;Mimikatz first released in 2007&quot; claim refers to Delpy&apos;s pre-release private experimentation, not a public release.&lt;/p&gt;
&lt;h3&gt;4.2 Four primitives that broke the credential layer&lt;/h3&gt;
&lt;p&gt;The Mimikatz module set Delpy authored over 2011-2014 contains four primitives that together explain why every per-binary mitigation Microsoft had shipped was insufficient.&lt;/p&gt;

Replay an NT hash as a bearer credential against any service that accepts NTLM authentication, *without* ever knowing the user&apos;s plaintext password [@mimikatz-github; @duckwall-campbell-bh2013]. The NTLM protocol authenticates by proof-of-possession of the NT hash, not proof-of-knowledge of the password.
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/ntlmless-the-death-of-ntlm-in-windows/&quot; rel=&quot;noopener&quot;&gt;Pass-the-Hash&lt;/a&gt; is the load-bearing primitive. NTLM authentication on the wire authenticates by proof-of-possession of the NT hash, not proof-of-knowledge of the password. The plaintext password is computed exactly once, at logon, to derive the NT hash via &lt;code&gt;MD4(UTF16LE(password))&lt;/code&gt;. After that the operating system does not need the cleartext again for NTLM. Anyone holding the hash can authenticate as the user without ever knowing the password. The real NTLMv2 protocol per MS-NLMP §3.3.2 is a two-stage HMAC-MD5 construction [@ms-nlmp-ntlmv2]: stage 1 derives an intermediate &lt;code&gt;NTOWFv2 = HMAC_MD5(NT_hash, UTF16LE(UPPERCASE(user) || domain))&lt;/code&gt;; stage 2 computes &lt;code&gt;NTProofStr = HMAC_MD5(NTOWFv2, ServerChallenge || ClientChallengeBlob)&lt;/code&gt;. The bearer-credential invariant survives both stages -- the function consumes the NT hash directly and never references the cleartext -- which is the exact property Pass-the-Hash exploits.&lt;/p&gt;
&lt;p&gt;{`
// Illustrative -- the real NTLMv2 protocol is a two-stage HMAC-MD5
// construction (see MS-NLMP section 3.3.2):
//   Stage 1: NTOWFv2 = HMAC_MD5(NT_hash, UPPERCASE(user) || domain)
//   Stage 2: NTProofStr = HMAC_MD5(NTOWFv2, ServerChallenge || temp)
// The Pass-the-Hash invariant -- that the NT hash is the bearer
// credential because the protocol consumes it without ever needing
// the cleartext password -- survives the simplification below.
const crypto = require(&apos;crypto&apos;);&lt;/p&gt;
&lt;p&gt;function ntlmResponse(ntHash, serverNonce, clientNonce) {
  // Simplified single-stage HMAC-MD5 keyed on the NT hash.
  // The plaintext password is never used by the protocol after logon.
  const hmac = crypto.createHmac(&apos;md5&apos;, Buffer.from(ntHash, &apos;hex&apos;));
  hmac.update(Buffer.concat([serverNonce, clientNonce]));
  return hmac.digest(&apos;hex&apos;);
}&lt;/p&gt;
&lt;p&gt;const stolenHash = &apos;8846f7eaee8fb117ad06bdd830b7586c&apos;;
const serverNonce = Buffer.from(&apos;0123456789abcdef&apos;, &apos;hex&apos;);
const clientNonce = Buffer.from(&apos;fedcba9876543210&apos;, &apos;hex&apos;);&lt;/p&gt;
&lt;p&gt;console.log(&apos;NTLM response:&apos;, ntlmResponse(stolenHash, serverNonce, clientNonce));
console.log(&apos;No plaintext password was used. The hash IS the credential.&apos;);
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The plaintext password is not the secret. Once the operating system has derived the hash at logon, anyone who reaches LSASS and reads that hash can authenticate as the user against any NTLM-accepting service for as long as that hash remains valid -- which is until the user next changes the password. The credential-replay class is a corollary of this single insight applied to different bearer credentials.&lt;/p&gt;
&lt;/blockquote&gt;

Extract a Kerberos Ticket-Granting Ticket or service ticket from LSASS and re-import it into another logon session for replay. Mimikatz exposes both halves: `sekurlsa::tickets /export` extracts; `kerberos::ptt` re-imports [@mimikatz-github].
&lt;p&gt;Pass-the-Ticket is the Kerberos analogue of Pass-the-Hash. A Kerberos TGT is a bearer credential by design -- it proves the holder authenticated to the Key Distribution Center -- and like the NT hash, anyone holding the ticket can replay it. Mimikatz&apos;s &lt;code&gt;kerberos::ptt&lt;/code&gt; injects a ticket blob into the local session&apos;s ticket cache; the next call to &lt;code&gt;klist&lt;/code&gt; shows it as if the local logon had earned it.&lt;/p&gt;

Use a stolen NT hash to request a *fresh* Kerberos TGT from the Key Distribution Center -- the bridge from an NTLM-recovered hash to a Kerberos-issued ticket. Defeats estates that have disabled NTLM but trust Kerberos pre-authentication keys derived from the same password hash [@mimikatz-github].
&lt;p&gt;Overpass-the-Hash is the bridge primitive. Estates that disabled NTLM in 2012-2014 in response to early Pass-the-Hash discussion believed they had closed the credential-replay door. Overpass-the-Hash re-opened it by re-using the NT hash to compute the Kerberos pre-authentication value, then sending a normal Kerberos AS-REQ. The KDC issued a TGT keyed on the same secret the NTLM stack had used. From there, every subsequent Kerberos service ticket request was a legitimate Kerberos exchange backed by a stolen secret.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WDigest plaintext-in-memory&lt;/strong&gt; is the fourth primitive, and the one that surprised even Microsoft&apos;s own teams when Delpy demonstrated it. Microsoft&apos;s WDigest Security Support Provider, which implemented HTTP Digest authentication on the server side and Digest single sign-on on the client side, held the user&apos;s plaintext password in LSASS memory by design, recoverable as long as the user&apos;s session was active.WDigest predates the modern web; HTTP Digest authentication had been essentially deprecated by the time Mimikatz operationalised the plaintext-recovery primitive, which is why the KB2871997 opt-out has near-zero operational downside on any post-2010 estate. Mimikatz&apos;s &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; enumerated the loaded credential-providers, walked the LSASS heap structures, and printed every cached secret it could decrypt -- including, on most pre-2014 estates, the user&apos;s plaintext password in clear text.&lt;/p&gt;
&lt;p&gt;(One discipline note. Skeleton Key is &lt;em&gt;not&lt;/em&gt; one of the Part 3 Mimikatz primitives. Skeleton Key was disclosed by Dell SecureWorks Counter Threat Unit on January 12, 2015 [@secureworks-skeleton-key] and Delpy added &lt;code&gt;misc::skeleton&lt;/code&gt; to Mimikatz on January 17, 2015, both outside the Part 3 window. It opens Part 4.)&lt;/p&gt;

sequenceDiagram
    participant Op as Operator (Admin)
    participant Mim as mimikatz.exe
    participant Krn as Windows Kernel
    participant LSA as LSASS.exe
    Op-&amp;gt;&amp;gt;Mim: privilege::debug
    Mim-&amp;gt;&amp;gt;Krn: AdjustTokenPrivileges (SeDebugPrivilege)
    Krn--&amp;gt;&amp;gt;Mim: TRUE
    Op-&amp;gt;&amp;gt;Mim: sekurlsa::logonpasswords
    Mim-&amp;gt;&amp;gt;Krn: OpenProcess (PROCESS_VM_READ on LSASS PID)
    Krn--&amp;gt;&amp;gt;Mim: process handle
    Mim-&amp;gt;&amp;gt;LSA: ReadProcessMemory (walk security-package list)
    LSA--&amp;gt;&amp;gt;Mim: encrypted credential blobs
    Mim-&amp;gt;&amp;gt;Krn: BCryptDecrypt (LSA master key from same address space)
    Krn--&amp;gt;&amp;gt;Mim: cleartext NT hashes, TGTs, WDigest plaintexts
    Mim--&amp;gt;&amp;gt;Op: print every cached secret
&lt;h3&gt;4.3 The 2013 inflection: graph-walking offensive Active Directory&lt;/h3&gt;
&lt;p&gt;In August 2013, Skip Duckwall and Chris Campbell delivered &quot;Pass-the-Hash 2: The Admin&apos;s Revenge&quot; at Black Hat USA [@duckwall-campbell-bh2013]. The talk did not invent the primitives Mimikatz had already shipped. It made offensive Active Directory tradecraft a public, named discipline by formalising the graph-walking insight: every Windows host an administrator logs into caches a credential for that administrator; every credential cached on a compromised host is a stolen credential; every stolen credential is a new starting node for the next lateral movement. The attack graph closes on the domain controller within hops measured in single digits on almost every real enterprise estate.&lt;/p&gt;
&lt;p&gt;The discipline decomposes into a four-step iterative loop on any Windows estate with cached domain credentials [@duckwall-campbell-bh2013]. &lt;strong&gt;Step one: enumerate active sessions on the compromised host&lt;/strong&gt; -- &lt;code&gt;NetSessionEnum&lt;/code&gt; returns inbound SMB sessions, &lt;code&gt;NetWkstaUserEnum&lt;/code&gt; returns the logged-on user list (pre-KB4480964 without admin rights), and &lt;code&gt;quser&lt;/code&gt; / &lt;code&gt;qwinsta&lt;/code&gt; enumerate interactive logons. The output is the &lt;code&gt;(user, host)&lt;/code&gt; tuple set representing every credential cached in the host&apos;s LSASS. &lt;strong&gt;Step two: identify a reachable administrator&lt;/strong&gt; -- cross-reference each enumerated user against local Administrators group membership and against the domain groups that grant administrative access to a higher-tier host. The output is a set of &lt;code&gt;(harvested-user, target-host)&lt;/code&gt; tuples where the harvested credential can be replayed against the target with administrative privilege. &lt;strong&gt;Step three: Pass-the-Hash to the higher-tier host&lt;/strong&gt; -- inject the harvested NT hash into a new logon session via &lt;code&gt;sekurlsa::pth /run:...&lt;/code&gt; and execute remote commands against the target as the harvested user, with no need for the cleartext password [@mimikatz-github]. &lt;strong&gt;Step four: harvest the new host&apos;s LSASS and repeat&lt;/strong&gt; -- &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; against the new beachhead dumps every credential that host has cached, each becoming a new starting node for the next iteration. The loop terminates when one harvested credential is a Domain Admin.&lt;/p&gt;
&lt;p&gt;This four-step loop is the &lt;em&gt;implicit&lt;/em&gt; graph the article&apos;s diagram illustrates: vertices are users and hosts, edges are &lt;code&gt;MemberOf&lt;/code&gt; (user is a group member), &lt;code&gt;AdminTo&lt;/code&gt; (user has administrative access to a host), and &lt;code&gt;HasSession&lt;/code&gt; (a host currently caches a credential for a user). Three years later, Andy Robbins, Will Schroeder, and Rohan Vazarkar productized this graph at DEF CON 24 in Las Vegas on August 6, 2016 as &lt;a href=&quot;https://paragmali.com/blog/ad-is-a-graph-how-bloodhound-made-defenders-think-like-attac/&quot; rel=&quot;noopener&quot;&gt;BloodHound&lt;/a&gt;, which uses the &lt;code&gt;SharpHound&lt;/code&gt; collector to enumerate every vertex and edge, loads them into a Neo4j database, and runs Cypher shortest-path queries from any compromised principal to the &lt;code&gt;Domain Admins&lt;/code&gt; group [@bloodhound-defcon24]. BloodHound is a 2016 artifact and properly belongs to Part 4; for the 2009-2014 Part 3 window, the graph existed only in operator notebooks and on Duckwall and Campbell&apos;s whiteboard, but every Windows estate already had it -- the attacker just had to walk it.&lt;/p&gt;
&lt;h3&gt;4.4 The 2014 inflection: the Golden Ticket&lt;/h3&gt;
&lt;p&gt;In August 2014, Benjamin Delpy and Skip Duckwall jointly presented &quot;Abusing Microsoft Kerberos: Sorry You Guys Don&apos;t Get It&quot; at Black Hat USA [@delpy-duckwall-bh2014].The dual authorship matters: Delpy and Duckwall presented the talk together, and any single-author attribution misses the collaboration that produced the Golden Ticket walkthrough. The headline reveal was the &lt;strong&gt;Golden Ticket&lt;/strong&gt;: a forged Kerberos Ticket-Granting Ticket signed with the stolen NT hash of the domain&apos;s &lt;code&gt;krbtgt&lt;/code&gt; account.&lt;/p&gt;

A forged Kerberos Ticket-Granting Ticket signed with the stolen NT hash of the domain&apos;s krbtgt service account. Grants arbitrary user, arbitrary group, and arbitrary lifetime impersonation across every domain controller in the Active Directory forest. Survives every password reset *except* the krbtgt account&apos;s own [@delpy-duckwall-bh2014; @metcalf-golden-ticket].
&lt;p&gt;The &lt;a href=&quot;https://paragmali.com/blog/krbtgt-the-account-that-owns-active-directory/&quot; rel=&quot;noopener&quot;&gt;krbtgt account&lt;/a&gt; is the master signing key for the domain&apos;s Kerberos infrastructure. Every TGT a domain controller issues is signed with the krbtgt NT hash, and the domain trusts any TGT that verifies against that hash. If an attacker holding domain-admin privileges has ever extracted the krbtgt hash from a domain controller&apos;s LSASS, they can forge a TGT for any user, with any group membership, with any lifetime they choose -- and the domain controllers will accept it as if it had been legitimately issued. The forged ticket survives every routine password reset on the domain because routine password resets do not rotate the krbtgt account. Sean Metcalf&apos;s ADSecurity walkthrough remains the practitioner-grade canonical reference [@metcalf-golden-ticket].&lt;/p&gt;
&lt;h3&gt;4.5 What this proved&lt;/h3&gt;
&lt;p&gt;By the end of 2014, the Mimikatz codebase had operationalised pass-the-hash, pass-the-ticket, overpass-the-hash, WDigest plaintext recovery, and the Golden Ticket on a default-configured modern Windows host. Every credential the LSA process held in memory in a recoverable form was structurally exposed.&lt;/p&gt;
&lt;p&gt;The scope of that claim matters. TPM-bound keys, smart-card private keys behind a hardware boundary, and Kerberos service keys on Windows servers whose LSASS the attacker had not yet compromised were &lt;em&gt;not&lt;/em&gt; exposed by Mimikatz. The precise statement is &lt;em&gt;every credential the LSA process held in memory in a recoverable form&lt;/em&gt;, not &quot;every Windows credential primitive ever,&quot; and the precise statement is the one Microsoft eventually acknowledged in the Mitigating Pass-the-Hash whitepaper series [@ms-pth-v2].&lt;/p&gt;

Mimikatz did not need to defeat AppLocker, ASLR, DEP, or Authenticode. It ran as an administrator, called OpenProcess on LSASS, and walked away with every cached credential the operating system would ever hold. The defender&apos;s playbook had been answering the wrong question.
&lt;p&gt;Stuxnet was a four-zero-day operation that ran once. Mimikatz was a free, open-source command that ran every time. The offensive economics of attacking Windows fleets shifted decisively away from zero-day-burning and toward credential replay. &lt;em&gt;Why&lt;/em&gt; did this happen, and what does it mean for the next decade of Windows defence?&lt;/p&gt;
&lt;h2&gt;5. The Causal Link: Hardening Birthed the Credential-Theft Class&lt;/h2&gt;
&lt;p&gt;After two parallel narratives, the reader has the evidence to follow the argument. This is the article&apos;s intellectual centre.&lt;/p&gt;
&lt;h3&gt;5.1 The pivot up the trust stack&lt;/h3&gt;
&lt;p&gt;While Microsoft was closing per-binary attack surface -- Authenticode, kernel-mode code signing, ASLR, DEP, AppLocker, AppContainer, ELAM, Secure Boot -- attackers pivoted up the trust stack to what those hardened binaries still had to trust: the credentials in LSASS memory, the Kerberos tickets in the LSA cache, and the LSA process address space itself. The mitigation surface and the attack surface are not at the same layer. This is the article&apos;s structural insight, and it is the single sentence the rest of the argument exists to defend.&lt;/p&gt;

flowchart TD
    A[&quot;Hardware root: TPM, UEFI Secure Boot db/dbx&quot;]
    B[&quot;Bootloader signature chain (Secure Boot, Measured Boot)&quot;]
    C[&quot;Kernel-mode code (KMCS, ELAM as first boot-start driver, PatchGuard)&quot;]
    D[&quot;User-mode signed binaries (Authenticode, AppLocker rules)&quot;]
    E[&quot;Sandboxed renderers (AppContainer, EPM, WinRT)&quot;]
    F[&quot;LSASS process memory: NT hashes, Kerberos TGTs, krbtgt key&quot;]
    G[&quot;Attacker primitive: Mimikatz sekurlsa::logonpasswords&quot;]
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E --&amp;gt; F
    G -.reads.-&amp;gt; F
    style F fill:#fde68a,stroke:#b45309,color:#5f370e
    style G fill:#fecaca,stroke:#991b1b,color:#7f1d1d
&lt;p&gt;The diagram makes the asymmetry visible. Every defender control protects a layer &lt;em&gt;below&lt;/em&gt; LSASS. Mimikatz attacks LSASS directly. None of the per-binary controls is in the attack path because Mimikatz does not need to defeat them -- it runs as a process the per-binary controls approved.&lt;/p&gt;
&lt;h3&gt;5.2 The Mimikatz codebase as a single causal node&lt;/h3&gt;
&lt;p&gt;Every credential-replay class that defines the next decade of red-team tradecraft traces to one 2011 codebase. Pass-the-Hash, Pass-the-Ticket, Overpass-the-Hash, Golden Ticket -- all four landed in &lt;code&gt;gentilkiwi/mimikatz&lt;/code&gt;. After the GitHub repository creation on April 6, 2014 [@mimikatz-github], the same codebase later grew the post-Part-3 modules (Skeleton Key and DCSync; see §11 FAQ) [@secureworks-skeleton-key; @metcalf-dcsync]. There is no comparable single codebase on the defender side. Microsoft&apos;s countermeasures landed across at least three product teams (Active Directory, Windows Defender, Hyper-V), and the architectural answer required a hypervisor.&lt;/p&gt;

Because you don&apos;t want to fix it, I&apos;ll show it to the world to make people aware of it. -- Benjamin Delpy [@greenberg-mimikatz-wired]
&lt;p&gt;Delpy&apos;s framing converted a defender&apos;s blind spot into a public, weaponised primitive. Microsoft&apos;s initial dismissal of his private disclosure -- that the credential model was &quot;by design&quot; -- was true, in the most damaging possible sense. The model &lt;em&gt;was&lt;/em&gt; by design. The single sign-on contract required it. Closing the gap required a different design.&lt;/p&gt;
&lt;h3&gt;5.3 The economic argument&lt;/h3&gt;
&lt;p&gt;The shift was economic as much as architectural. A reliable Windows zero-day exploit chain commanded a substantial unit price on the early-2010s grey market and burned on first use: once a sample was disclosed and patched, the exploit was worthless to a serious operator. A Mimikatz invocation, by contrast, is free, reusable indefinitely on any pre-Credential-Guard estate, leaves no on-disk footprint, and runs as the operator the attacker already compromised. The asymmetry is not subtle.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Stuxnet (June 2010)&lt;/th&gt;
&lt;th&gt;Mimikatz (May 2011 onward)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Attacker cost&lt;/td&gt;
&lt;td&gt;Four Windows zero-days + two stolen Authenticode certificates + ICS payload [@symantec-stuxnet-dossier-v14]&lt;/td&gt;
&lt;td&gt;Free open-source tool [@mimikatz-github]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusability&lt;/td&gt;
&lt;td&gt;Single-use; zero-days patched within months [@ms-bulletin-ms10-046; @ms-bulletin-ms10-061; @ms-bulletin-ms10-073; @ms-bulletin-ms10-092]&lt;/td&gt;
&lt;td&gt;Indefinite on any pre-Credential-Guard host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-disk footprint&lt;/td&gt;
&lt;td&gt;Multi-megabyte signed dropper + Step 7 / S7 payloads&lt;/td&gt;
&lt;td&gt;Single executable; can run in memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detection footprint&lt;/td&gt;
&lt;td&gt;Symantec / Kaspersky / ESET signatures within weeks of disclosure [@symantec-stuxnet-dossier-v14]&lt;/td&gt;
&lt;td&gt;Initially evades signature-based AV; later detected via ProcessAccess masks on LSASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target population&lt;/td&gt;
&lt;td&gt;Specific ICS estate (Natanz)&lt;/td&gt;
&lt;td&gt;Every Windows AD estate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threat-model implication&lt;/td&gt;
&lt;td&gt;Nation-states will burn zero-days for kinetic effect&lt;/td&gt;
&lt;td&gt;Anyone with admin can replay every cached credential&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Defensive success at one layer reliably produces attacker innovation at the next layer up. The 2009-2014 window proves it: Microsoft killed the rootkit, bootkit, and unsigned-bootloader classes; attackers responded by reading the credentials in LSASS memory that every hardened binary still had to trust. The mitigation surface and the attack surface were not at the same layer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the credential layer was structurally broken, why didn&apos;t Microsoft just fix it? They tried. The next section is the honest evaluation of Microsoft&apos;s counter-pivot through November 2014.&lt;/p&gt;
&lt;h2&gt;6. Microsoft&apos;s Counter-Pivot: 2013-2014&lt;/h2&gt;
&lt;p&gt;Microsoft was not asleep. By Windows 8.1 General Availability on October 17, 2013, three controls landed that were &lt;em&gt;directly&lt;/em&gt; a response to Mimikatz. They were partial wins, all of them; the architectural acknowledgement that LSASS-in-VTL0 was unsalvageable would arrive only with Virtualisation-Based Security and Credential Guard in Windows 10 1507 [@ms-credential-guard], outside this article&apos;s window. This section is the honest evaluation of what shipped, what it accomplished, and why none of it was enough.&lt;/p&gt;
&lt;h3&gt;6.1 Restricted Admin RDP&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/rdp-authentication-26-years/&quot; rel=&quot;noopener&quot;&gt;Restricted Admin RDP&lt;/a&gt; changes the Remote Desktop Protocol so that the client never sends the user&apos;s plaintext password to the server&apos;s LSASS [@kb2871997]. Instead, the server issues a Network Level Authentication challenge that the client signs using its local NT hash; the user authenticates to the remote desktop session as a network logon rather than an interactive logon. Critical credential material is never present on the RDP server.&lt;/p&gt;
&lt;p&gt;The bug Restricted Admin closes is the credential-disclosure failure mode: a foothold on the RDP server used to learn every administrator&apos;s plaintext password as they logged in. The bug it leaves open is replay. A Restricted Admin RDP session is a &lt;em&gt;network&lt;/em&gt; logon, and an attacker holding the NT hash for an administrative account can invoke &lt;code&gt;sekurlsa::pth /run:&quot;mstsc /restrictedadmin&quot;&lt;/code&gt; from a compromised host and authenticate to the target RDP server using only the hash. Restricted Admin reduced disclosure; it did not close replay.&lt;/p&gt;

sequenceDiagram
    participant C as RDP Client
    participant S as RDP Server (LSASS)
    Note over C,S: Classic RDP (credential delegation)
    C-&amp;gt;&amp;gt;S: TLS handshake plus plaintext credentials
    S-&amp;gt;&amp;gt;S: LSASS caches plaintext password for session
    Note over S: Foothold on server reveals every admin password
    Note over C,S: Restricted Admin RDP (post-KB2871997)
    C-&amp;gt;&amp;gt;S: Network Level Authentication challenge request
    S-&amp;gt;&amp;gt;C: server nonce
    C-&amp;gt;&amp;gt;C: sign nonce with local NT hash
    C-&amp;gt;&amp;gt;S: signed response
    S-&amp;gt;&amp;gt;S: verify against domain controller
    Note over S: Server never sees plaintext
    Note over C: Attacker with NT hash can still run mstsc with restrictedadmin
&lt;p&gt;Server-side Restricted Admin shipped at Windows 8.1 / Server 2012 R2 General Availability on October 17, 2013. The client-side back-port to Windows 7, Server 2008 R2, Windows 8, and Server 2012 followed via KB2871997 on May 13, 2014 [@kb2871997], which is also where the WDigest opt-out and TokenLeakDetectDelaySecs primitives shipped.&lt;/p&gt;
&lt;h3&gt;6.2 LSA Protected Process (RunAsPPL)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://paragmali.com/blog/protected-process-light-when-the-administrator-isnt-enough/&quot; rel=&quot;noopener&quot;&gt;LSA Protected Process&lt;/a&gt; loads LSASS as a Protected Process Light with the signer level &lt;code&gt;PsProtectedSignerLsa&lt;/code&gt;. Once Protected, the Windows kernel refuses any &lt;code&gt;OpenProcess(PROCESS_VM_READ)&lt;/code&gt; call against LSASS from a process running at a lower signer level -- including a process running as NT AUTHORITY\SYSTEM with &lt;code&gt;SeDebugPrivilege&lt;/code&gt; [@ms-lsa-protection]. The flag is enabled by setting &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;. RunAsPPL is the strongest credential-protection primitive Microsoft shipped inside Windows 8.1.&lt;/p&gt;

A kernel-enforced signer level that prevents OpenProcess(PROCESS_VM_READ) and CreateRemoteThread against the protected process from any process running at a lower signer level, regardless of token privileges or session [@itm4n-lsa-protection; @ms-lsa-protection]. The Lsa variant requires every LSA plug-in DLL (SSP, AP, custom credential providers) to itself be signed at a compatible signer level, which is why enabling RunAsPPL on real estates requires an LSA plug-in audit.
&lt;p&gt;The bypass class is Bring Your Own Vulnerable Driver. A malicious kernel-mode driver, loaded through a vulnerable but Microsoft-signed third-party driver that the attacker has placed on disk, can clear the &lt;code&gt;Protection&lt;/code&gt; byte in the kernel &lt;code&gt;EPROCESS&lt;/code&gt; structure for LSASS, after which the &lt;code&gt;OpenProcess(PROCESS_VM_READ)&lt;/code&gt; call succeeds. Mimikatz ships its own kernel driver, &lt;code&gt;mimidrv.sys&lt;/code&gt;, that performs exactly this manipulation [@mimikatz-github]. The structural problem is that RunAsPPL is enforced by the same kernel an attacker is compromising to bypass it; the protection cannot be made strictly stronger inside the same privilege ring than the kernel that enforces it.&lt;/p&gt;

A common misreading is that PPL is a partial Credential Guard, or that Credential Guard replaces PPL. The most useful framing is itm4n&apos;s: *&quot;I noticed that this protection tends to be confused with Credential Guard, which is completely different&quot;* [@itm4n-lsa-protection]. PPL is a same-privilege gate inside VTL0 -- both LSASS and the attacker live in the same kernel address space, and the kernel decides whether to grant a process handle. Credential Guard is a cross-privilege isolation between VTL0 and VTL1 (the Virtual Trust Levels Hyper-V introduces in Windows 10 1507) [@ms-credential-guard]: the credential material lives in a Virtual Secure Mode trustlet (LSAISO) that the VTL0 kernel cannot read because the hypervisor&apos;s Second-Level Address Translation tables deny the mapping. The two controls are complementary -- PPL hardens LSASS against in-VTL0 attackers; Credential Guard moves the high-value secret out of VTL0 entirely. §8.3 develops the cross-privilege isolation argument formally.
&lt;h3&gt;6.3 The Mitigating Pass-the-Hash whitepaper series&lt;/h3&gt;
&lt;p&gt;Microsoft published the Mitigating Pass-the-Hash and Other Credential Theft whitepaper in two versions: v1 in December 2012 from the Trustworthy Computing group [@ms-pth-v1-landing] and v2 in July 2014 [@ms-pth-v2]. There is no v3. Post-2014 guidance migrated into the &lt;em&gt;Securing Privileged Access&lt;/em&gt; online documentation rather than appearing as a numbered v3 PDF, and any &quot;v3 2017&quot; reference is incorrect.&lt;/p&gt;
&lt;p&gt;The v1 paper introduced the tier 0 / tier 1 / tier 2 administrative-account model: separate the accounts that manage the forest (tier 0: domain controllers, AD), the accounts that manage server applications (tier 1: file servers, Exchange, SQL), and the accounts that manage end-user workstations (tier 2: helpdesk, desktop support). The rule is that a tier-N credential must never be exposed on a tier-(N+1) host. The model is sound. The problem is that v1 was recommendations-only with no enforcement primitive inside the operating system, and operators routinely violated tiering (the helpdesk technician fixing the CEO&apos;s laptop with a tier-2 credential and then RDPing to a tier-1 file server exposes the credential at the laptop&apos;s LSASS). The v2 paper integrated the technical D5 controls (RunAsPPL, Restricted Admin, KB2871997) precisely because v1 alone could not move the needle on real estates.&lt;/p&gt;
&lt;h3&gt;6.4 KB2871997 and the WDigest opt-out&lt;/h3&gt;
&lt;p&gt;The May 13, 2014 update KB2871997 is the single most operationally impactful credential-protection control of the entire window [@kb2871997]. It carried three deliverables. First, the Restricted Admin client back-port to Windows 7 / Server 2008 R2 / Windows 8 / Server 2012, which Section 6.1 covers. Second, the &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest\UseLogonCredential = 0&lt;/code&gt; registry default that disabled WDigest plaintext credential storage in LSASS memory on a freshly patched system. Third, the &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa\TokenLeakDetectDelaySecs&lt;/code&gt; (default 30 seconds) cleanup of leaked logon-session credentials.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The WDigest opt-out (&lt;code&gt;UseLogonCredential = 0&lt;/code&gt;) has zero operational downside on any post-2010 estate -- HTTP Digest authentication is essentially extinct in the enterprise -- and removes the most-cited credential-recovery primitive Mimikatz used through 2014 [@kb2871997]. It ships with the same back-port that brings Restricted Admin to down-level Windows. There is no defensible argument for not applying it on any supported Windows from 2014 onward.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The WDigest opt-out was buried in the KB2871997 bulletin because the headline framing was Restricted Admin RDP; many 2014-era administrators applied the patch for the RDP fix without realising the WDigest default had also changed [@kb2871997].&lt;/p&gt;
&lt;h3&gt;6.5 The seeds of Credential Guard&lt;/h3&gt;
&lt;p&gt;By late 2014 Microsoft was already prototyping the Hyper-V-as-security-boundary architecture that becomes Virtualisation-Based Security, &lt;a href=&quot;https://paragmali.com/blog/the-empty-hash-credential-guard-the-lsaiso-trustlet-and-the-/&quot; rel=&quot;noopener&quot;&gt;Credential Guard&lt;/a&gt;, and Hypervisor-protected Code Integrity in Windows 10 1507 on July 29, 2015 [@ms-credential-guard]. For the Part 3 reader, the key observation is that Microsoft had already concluded by mid-2014 that no amount of in-VTL0 hardening could close the credential-replay gap structurally, and that the architectural answer required moving the credential cache to a different privilege domain than the kernel attackers compromise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Restricted Admin reduced disclosure but not replay. RunAsPPL stopped a Mimikatz invocation only until BYOVD. The Pass-the-Hash tiering model named the problem but had no enforcement primitive inside the operating system. Microsoft&apos;s counter-pivot in the Part 3 window was correct in direction and &lt;em&gt;insufficient by construction&lt;/em&gt; -- because the architecture was the problem, not the engineering.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Microsoft shipped the right primitives. None of them was sufficient by construction, because the architecture was the problem. To see why, we have to look at the one structural thing the window left open: the SChannel attack surface, and the impossibility argument behind it.&lt;/p&gt;
&lt;h2&gt;7. The SChannel Coda: WinShock (MS14-066, November 11, 2014)&lt;/h2&gt;
&lt;p&gt;The window closes on November 11, 2014 with the last great pre-cloud TLS-stack remote code execution in Windows. WinShock is a counterpoint that reinforces the article&apos;s thesis rather than contradicting it: even with every credential-layer control of 2013-2014 deployed, an unrelated per-binary defect in the Schannel TLS stack could still hand an attacker remote code execution before any application code ran. The credential-layer hardening Microsoft spent the year shipping could not have prevented this bug, and the bug&apos;s existence is part of the evidence that hardening one layer leaves orthogonal layers exposed.&lt;/p&gt;
&lt;p&gt;A note up front, because the popular framing got this wrong. The bulletin itself was &lt;em&gt;not&lt;/em&gt; silent. MS14-066 was published on the November 11, 2014 Patch Tuesday with a Critical severity rating, an explicit CVE assignment (CVE-2014-6321), contemporary Brian Krebs coverage [@krebs-ms14-066], and public proof-of-concept walkthroughs within months [@nvd-cve-2014-6321]. The &quot;silent&quot; framing applies only to the additional Schannel hardening fixes Microsoft bundled into the same update without separate disclosures.&lt;/p&gt;
&lt;h3&gt;7.1 The mechanism&lt;/h3&gt;
&lt;p&gt;A crafted TLS handshake triggered a memory-corruption path inside &lt;code&gt;schannel.dll&lt;/code&gt;, the Windows Secure Channel security package that implements TLS for every in-box TLS consumer [@ms-bulletin-ms14-066; @nvd-cve-2014-6321]. The bug allowed remote code execution before any application code ran -- the handshake itself was the attack. The NVD entry catalogues the affected platforms as Windows Server 2003 SP2, Windows Vista SP2, Windows Server 2008 SP2 and R2 SP1, Windows 7 SP1, Windows 8, Windows 8.1, Windows Server 2012 Gold and R2, and Windows RT Gold and 8.1 -- essentially every supported Windows of the era [@nvd-cve-2014-6321].&lt;/p&gt;
&lt;p&gt;The attack surface was universal across the Windows enterprise estate of late 2014. Every IIS host terminating HTTPS, every SMB-over-HTTPS endpoint, every RDP-over-TLS listener, every Exchange ActiveSync endpoint, every Active Directory Federation Services endpoint terminating TLS in Schannel was exposed. A defensible writer-side abstraction (which this article takes) is that a crafted handshake triggered a memory-corruption path; the precise internal type and function family Microsoft fixed are not safely attributable without a primary-source walkthrough beyond the bulletin&apos;s published abstract.&lt;/p&gt;
&lt;h3&gt;7.2 The bundled extras&lt;/h3&gt;
&lt;p&gt;Microsoft bundled additional Schannel hardening into MS14-066 without separate bulletins. The article does not name specific CVE IDs for those bundled extras because prior pipeline runs found such attributions factually wrong (those CVE IDs belong to other bulletins or are REJECTED in NVD). The defensible framing is that Microsoft bundled additional Schannel hardening into the same update without separate bulletins, anchored to contemporary coverage of the patch cycle [@krebs-ms14-066]. The substantive point survives without speculative CVE attribution.&lt;/p&gt;
&lt;p&gt;The &quot;no public exploitation&quot; framing of MS14-066 is wrong. BeyondTrust&apos;s &quot;Triggering MS14-066&quot; blog post and the SecuritySift &quot;Exploiting MS14-066 (CVE-2014-6321) aka &apos;Winshock&apos;&quot; walkthrough are both referenced from the NVD entry as Exploit Third Party Advisory [@nvd-cve-2014-6321]. The CVE was patched, and the exploitation tradecraft was public; only the bundled hardening extras went unannotated.&lt;/p&gt;
&lt;h3&gt;7.3 Strategic significance&lt;/h3&gt;
&lt;p&gt;WinShock is the bookend on an era when the Windows Schannel stack was the front door of every enterprise. After 2014, TLS termination for major Windows estates increasingly happened at Azure Front Door, Akamai, Cloudflare, or AWS Application Load Balancer rather than at the Windows Schannel layer. Microsoft&apos;s own first-party services -- Exchange Online, SharePoint Online, the Office 365 ingress fleet -- terminated TLS at Azure-managed edge appliances, the topology documented in Microsoft&apos;s &lt;em&gt;Microsoft 365 network connectivity principles&lt;/em&gt; as the recommended &quot;connect locally to the Microsoft global network&quot; architecture in which the customer&apos;s traffic enters Microsoft&apos;s network as close to the user as possible and TLS is terminated at the nearest edge node [@ms-365-network-principles]. The architectural lesson is not that Schannel was uniquely fragile; it is that monolithic TLS stacks across hundreds of in-box consumers were a brittle design that the industry stopped accepting as the default deployment topology for enterprise services.&lt;/p&gt;
&lt;p&gt;WinShock closed the window with a per-binary patch. But the bigger story -- the credential layer Microsoft had spent the year trying to close -- was structurally broken in a way no patch could fix. To see why, we have to make the impossibility argument formally.&lt;/p&gt;
&lt;h2&gt;8. Theoretical Limits: Why No Per-Binary Hardening Could Fix the Credential Layer&lt;/h2&gt;
&lt;p&gt;A reframe. Every section so far has narrated &lt;em&gt;evidence&lt;/em&gt;. This section turns that evidence into an argument from architecture -- a structural reason the per-binary playbook &lt;em&gt;could not have&lt;/em&gt; fixed the credential layer, regardless of how good Microsoft&apos;s engineering was.&lt;/p&gt;
&lt;h3&gt;8.1 The trusted-computing-base argument&lt;/h3&gt;
&lt;p&gt;Every authenticated Windows process must, at some point, hold a verifiable secret. As §4.1 established, the single sign-on contract forces LSASS to hold a recoverable secret in memory [@ms-credentials-processes]. As long as that secret lives in a memory space the OS can read, an attacker who reaches that memory space can read it too.&lt;/p&gt;
&lt;p&gt;AppLocker, ASLR, DEP, AppContainer, ELAM, and Secure Boot are all per-binary mitigations [@ms-applocker; @ms-elam; @ms-secure-boot]. They prevent the &lt;em&gt;wrong&lt;/em&gt; code from running. They do not prevent the &lt;em&gt;right&lt;/em&gt; code (an administrator-launched Mimikatz; a Microsoft-signed but vulnerable third-party kernel driver) from reading LSASS memory through documented Win32 APIs. The per-binary playbook is a code-execution control, not a memory-access control, and the credential-theft attack is not a code-execution attack.&lt;/p&gt;
&lt;h3&gt;8.2 The asymmetry&lt;/h3&gt;
&lt;p&gt;The defender must close 100% of the per-binary attack surface to prevent a single piece of attacker code from running. The attacker needs only one credential primitive to remain extractable to win. The two budgets are not comparable. The defender&apos;s job is exponentially harder by construction, and any single residual gap -- one unsigned plug-in, one cached WDigest plaintext, one stolen NT hash -- gives the attacker domain-wide replay. This is not a Microsoft engineering failure. It is an architectural inevitability of the in-VTL0 LSASS model.&lt;/p&gt;
&lt;h3&gt;8.3 The VTL0-symmetry argument&lt;/h3&gt;
&lt;p&gt;In any single-privilege-ring operating system, no protection mechanism implemented &lt;em&gt;inside&lt;/em&gt; that ring can structurally defend a memory region against an attacker who reaches that ring. This is the formal statement of the limit Microsoft hit in 2014.&lt;/p&gt;
&lt;p&gt;RunAsPPL is the strongest 2014-era expression of this bound. As §6.2 documented, a BYOVD-loaded kernel driver can clear the &lt;code&gt;Protection&lt;/code&gt; byte on the LSASS &lt;code&gt;EPROCESS&lt;/code&gt; and &lt;code&gt;OpenProcess(PROCESS_VM_READ)&lt;/code&gt; succeeds [@itm4n-lsa-protection; @ms-lsa-protection]; the protection is enforced by the same kernel the attacker is compromising; the kernel cannot enforce a protection against itself.&lt;/p&gt;
&lt;p&gt;The architectural way to state it: $\text{Protection}&lt;em&gt;{\text{in-ring}}(M) \lt \text{Adversary}&lt;/em&gt;{\text{in-ring}}(M)$ for any memory region $M$ in the same privilege ring as the adversary. The protection function and the adversary function operate on the same domain, and the adversary always wins by construction. The algebraic notation is informal; the formal capture is the Bell-LaPadula / Lampson confinement bound, which states that in a single-privilege-ring system an adversary who reaches that ring can read any memory the kernel can map [@wikipedia-bell-lapadula]. Closing the gap requires moving $M$ to a privilege domain $\text{D}&apos;$ such that the in-ring adversary cannot map $\text{D}&apos;$ at all.&lt;/p&gt;
&lt;p&gt;That is exactly what Virtualisation-Based Security does in Windows 10 1507 [@ms-credential-guard]. Hyper-V boots before the Windows kernel and creates two Virtual Trust Levels: VTL0 is the normal Windows kernel attackers compromise; VTL1 is Virtual Secure Mode, an isolated execution domain whose memory the VTL0 kernel cannot read because the hypervisor&apos;s Second-Level Address Translation tables deny the mapping. Credential Guard hosts an LSA Isolated trustlet (LSAISO) in VTL1 that holds the high-value credential material; the VTL0 LSASS process holds only obfuscated references that LSAISO can resolve. A Mimikatz invocation in VTL0 can still extract the references, but the references no longer dereference to a credential the VTL0 kernel can read.&lt;/p&gt;

As long as the kernel that protects LSASS executes in the same privilege ring as the kernel an attacker compromises, every protection inside that ring is bypassable. The credential cache must live in a different privilege domain than the kernel that the attacker can compromise.
&lt;h3&gt;8.4 The way out, foreshadowed&lt;/h3&gt;
&lt;p&gt;Hardware-rooted isolation of the credential cache is the only structural answer. Virtualisation-Based Security, Credential Guard, and the LSAISO trustlet in VTL1 -- the spine of &lt;a href=&quot;https://paragmali.com/blog/above-the-kernel-the-windows-security-wars-part-4-2015-2019/&quot; rel=&quot;noopener&quot;&gt;Part 4&lt;/a&gt; -- are the architectural answer to the architectural problem the Part 3 window proves cannot be closed inside VTL0 [@ms-credential-guard]. The article closes the Part 3 argument by naming the problem precisely so Part 4 can name the solution precisely.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Hardware-rooted isolation of the credential cache -- the LSAISO trustlet in a VTL1 the VTL0 kernel cannot read -- is the only structural answer. Part 4 ships it. Part 3 names &lt;em&gt;why&lt;/em&gt; it had to.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The architecture was the problem. What did practitioners do with this evidence at the end of 2014?&lt;/p&gt;
&lt;h2&gt;9. Open Problems at the End of 2014&lt;/h2&gt;
&lt;p&gt;Picture a Fortune-500 security operations centre on a Friday afternoon in early December 2014. The team has applied every Microsoft patch through MS14-066 [@ms-bulletin-ms14-066], deployed AppLocker on Enterprise SKUs [@ms-applocker], set &lt;code&gt;RunAsPPL = 1&lt;/code&gt; after a careful LSA plug-in audit [@ms-lsa-protection], applied KB2871997 to disable WDigest plaintext storage [@kb2871997], and read the Mitigating Pass-the-Hash v2 whitepaper cover to cover [@ms-pth-v2]. They run an internal red-team exercise the following Monday. Mimikatz still works. Why?&lt;/p&gt;
&lt;p&gt;The credential layer is still essentially open. WDigest plaintext storage is now opt-out by default on freshly patched hosts, which closes the single most embarrassing primitive Delpy&apos;s 2011 demonstration exposed [@kb2871997]. But the cached NT hashes that NTLM authentication needs, the Kerberos Ticket-Granting Tickets the SSO contract holds in the LSA ticket cache, and the krbtgt master signing key on any domain controller whose LSASS the attacker can &lt;code&gt;OpenProcess&lt;/code&gt; against all remain extractable [@mimikatz-github; @ms-credentials-processes]. RunAsPPL stops a Mimikatz invocation from user mode, but it does not stop Mimikatz from invoking its own &lt;code&gt;mimidrv.sys&lt;/code&gt; driver (or any other vulnerable signed third-party driver) to clear the protection byte from kernel mode and proceed [@itm4n-lsa-protection; @mimikatz-github]. The same &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; that worked in May 2011 still works in December 2014 on every estate that has not stripped its third-party drivers down to a zero-BYOVD baseline -- which is no real estate at all.&lt;/p&gt;
&lt;p&gt;One open problem the security community debated through 2014 deserves a sharper treatment because it surfaces the &lt;em&gt;structural&lt;/em&gt; limit of any in-LSASS hardening strategy: why does Microsoft not simply relocate or obfuscate the LSA secret structures whose offsets Mimikatz hard-codes? The Mimikatz codebase carries an explicit, per-Windows-build signature table in &lt;code&gt;mimikatz/modules/sekurlsa/kuhl_m_sekurlsa_utils.c&lt;/code&gt; that maps every supported Windows kernel / &lt;code&gt;lsasrv.dll&lt;/code&gt; / &lt;code&gt;livessp.dll&lt;/code&gt; / &lt;code&gt;wdigest.dll&lt;/code&gt; build to the byte offsets and signature byte sequences Mimikatz scans for at run time [@mimikatz-sekurlsa-source]. The maintenance cost on the offensive side is one row per shipped Windows build per quarter. The proposed defensive response -- shuffle the struct layouts each cumulative update, randomise the symbol offsets, swap the byte signatures -- fails as a defence for three independent reasons. First, cost asymmetry. Microsoft would commit the test, validation, and Windows Hardware Quality Labs re-certification cost of every layout shuffle across every supported Windows SKU, language pack, and architecture every quarter; Mimikatz&apos;s maintainers would commit one pull request and one signature-table row per build. Second, defender-side fragility. The same LSASS structures the offsets index are consumed by Microsoft&apos;s own security tooling, by every third-party Endpoint Detection and Response agent, and by Windows Error Reporting; randomising the layout breaks the defender&apos;s own dependencies first and the attacker&apos;s last. Third, adversary-side robustness. Mimikatz already supports pattern-based signature scanning that finds the target structures even when their absolute offsets move; the offset hard-coding is a performance optimisation, not a requirement. The only structural defence is the one the engineering pipeline is already building: lift the credential cache out of the VTL0 user-mode process space entirely and into a Virtualisation-Based Security trustlet whose memory the VTL0 kernel cannot read. Alex Ionescu&apos;s Black Hat USA 2015 &quot;Battle of SKM and IUM&quot; talk lays out the VTL1 / IUM architecture in operator-facing detail and forward-references the Credential Guard design that ships in Windows 10 1507 [@ionescu-skm-ium-bhusa15]. The Part 3 community could see the answer; the architectural prerequisites simply had not yet shipped.&lt;/p&gt;
&lt;p&gt;Microsoft is prototyping Virtualisation-Based Security and Credential Guard, but the architectural answer ships outside this article&apos;s window [@ms-credential-guard]. Even after it ships, Credential Guard requires Windows 10 Enterprise, UEFI 2.3.1, Secure Boot, a 64-bit CPU with virtualisation extensions, and -- on most estates -- a hardware refresh cycle that costs years and millions. The deployment surface that needs the protection most cannot adopt it until well into 2017.&lt;/p&gt;
&lt;p&gt;AppLocker still carries its Windows 7 structural gaps in late 2014: the Application Identity service can be stopped by any process running as LocalSystem, after which enforcement degrades open until reboot, and the dual-DACL bypass class (rules that pass both Publisher and Path checks but reach a different binary at runtime) remains unaddressed [@ms-applocker; @ms-applocker-design]. Windows Defender Application Control -- the kernel-enforced policy successor that closes both gaps -- is still a Windows 10 enterprise feature in the Part 4 window. Secure Boot has its first &lt;code&gt;dbx&lt;/code&gt; revocation politics in this window: Microsoft&apos;s revocation list has to retire compromised UEFI bootloaders without bricking dual-boot Linux installations on the millions of OEM machines that ship with Secure Boot enabled, and the cadence and scope of &lt;code&gt;dbx&lt;/code&gt; updates becomes a recurring operational point of friction between Microsoft, OEMs, and the Linux distribution community [@ms-secure-boot; @mjg59-shim-signed]. The Pass-the-Hash v2 tiering recommendations are aspirational for the vast majority of 2014 deployments -- a complete tier 0 / tier 1 / tier 2 administrative-account programme is a multi-year project that requires Active Directory restructuring, change-management governance, and operator retraining at scale, and most estates that read the v2 paper applied KB2871997 and stopped there [@ms-pth-v2].&lt;/p&gt;
&lt;p&gt;Mimikatz&apos;s post-Part-3 modules (Skeleton Key and DCSync; see §11 FAQ) sit in the same codebase, are anchor events in the Part 4 window, and define the credential-replay horizon the Part 3 reader is staring at [@secureworks-skeleton-key; @metcalf-dcsync].&lt;/p&gt;
&lt;p&gt;The defining open question at the end of 2014 is how Microsoft isolates a long-lived user-mode process (LSASS) holding the most valuable secrets in the operating system from an administrator-privileged attacker on the same host, without breaking the hundreds of in-tree dependencies LSASS has accumulated since NT 3.1. The answer -- Virtualisation-Based Security plus the trustlet model -- is the spine of Part 4. It requires a hypervisor, a hardware-rooted boot chain, a re-architected LSA plug-in protocol that splits sensitive operations into LSAISO trustlet calls, and an operational deployment story that took Microsoft from late 2014 prototypes to general availability in 2015 and broad enterprise adoption only by 2018-2019.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; At the end of 2014, WDigest plaintext storage is closed by default. NT hashes, Kerberos TGTs, the krbtgt master key, and every other secret LSASS holds in recoverable form remain extractable by any administrator on the same host who can load a kernel driver. The architectural answer -- Credential Guard in Windows 10 1507 -- ships eight months later [@ms-credential-guard]. The Part 3 window proves the problem is real; Part 4 ships the answer.&lt;/p&gt;
&lt;/blockquote&gt;

Even at end-of-2014, with every Microsoft control available, the dominant Fortune-500 estate had applied the WDigest opt-out [@kb2871997] and almost nothing else. Tiering [@ms-pth-v2] is a multi-year programme. RunAsPPL [@ms-lsa-protection] requires an LSA plug-in audit that breaks any custom credential provider not yet re-signed at the PPL signer level. The architectural answer -- Credential Guard in 2015 [@ms-credential-guard] -- arrives to a deployment surface still struggling to deploy the 2013 controls. The gap between *the security primitive Microsoft shipped* and *the security primitive a Fortune-500 estate actually had running* was the largest it had ever been, and it grew through the Windows 10 1507 General Availability window.
&lt;p&gt;Eight open problems. None of them admits a Part 3-era technical solution. So how does a practitioner read the 2009-2014 primitives against a 2026 Windows 11 baseline?&lt;/p&gt;
&lt;h2&gt;10. Practical Guide: Reading the 2009-2014 Primitives Against a 2026 Windows 11 Baseline&lt;/h2&gt;
&lt;p&gt;The previous nine sections built the structural argument. This section answers the operator&apos;s question: which of these 2009-2014 primitives are still load-bearing in 2026, and which were superseded?&lt;/p&gt;
&lt;h3&gt;10.1 Which Part 3 primitives are still load-bearing in 2026&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Primitive (Part 3)&lt;/th&gt;
&lt;th&gt;Still in use 2026?&lt;/th&gt;
&lt;th&gt;Superseded by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;AppLocker (Win 7+) [@ms-applocker]&lt;/td&gt;
&lt;td&gt;Yes, on Windows 10/11 Enterprise estates&lt;/td&gt;
&lt;td&gt;App Control for Business (WDAC) for new deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ELAM (Win 8+) [@ms-elam]&lt;/td&gt;
&lt;td&gt;Yes, load-bearing for the boot chain on every supported Windows&lt;/td&gt;
&lt;td&gt;Unchanged primitive; Defender&apos;s WdBoot.sys is the in-box ELAM driver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UEFI Secure Boot (Win 8+) [@ms-secure-boot]&lt;/td&gt;
&lt;td&gt;Yes; mandatory for Windows 11 hardware certification&lt;/td&gt;
&lt;td&gt;Strengthened with mandatory dbx revocation enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AppContainer (Win 8+) [@windows-internals-6e-p1]&lt;/td&gt;
&lt;td&gt;Yes; substrate for MSIX, Edge renderers, Win32 App Isolation, Recall trustlet&lt;/td&gt;
&lt;td&gt;Generalised across all packaged Win32 apps via App Isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LSA Protected Process (Win 8.1+) [@ms-lsa-protection]&lt;/td&gt;
&lt;td&gt;Yes; &lt;em&gt;on by default&lt;/em&gt; on &lt;strong&gt;new installations&lt;/strong&gt; of Windows 11 22H2 and later (upgraded systems retain default-off and require manual or GPO enablement)&lt;/td&gt;
&lt;td&gt;Complemented by Credential Guard on enterprise hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restricted Admin RDP (Win 8.1+) [@kb2871997]&lt;/td&gt;
&lt;td&gt;Yes; still recommended&lt;/td&gt;
&lt;td&gt;Remote Credential Guard (Win 10 1607+) for high-tier environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WDigest plaintext disablement (KB2871997) [@kb2871997]&lt;/td&gt;
&lt;td&gt;Default on every supported Windows since 2014&lt;/td&gt;
&lt;td&gt;Unchanged primitive; WDigest itself is essentially deprecated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mitigating Pass-the-Hash tiering model [@ms-pth-v2]&lt;/td&gt;
&lt;td&gt;Yes; lives on as Privileged Access Workstations and Enterprise Access Model&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Securing Privileged Access&lt;/em&gt; online documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Two surprises in the table. First, LSA Protected Process is &lt;em&gt;on by default&lt;/em&gt; on &lt;strong&gt;new installations&lt;/strong&gt; of Windows 11 22H2 and later -- which closes the gap for newly-shipped devices, though estates that upgraded from earlier Windows versions still require the manual or GPO enablement step that defined the 2014-2020 period. Second, AppLocker is still in production on enterprise estates ten-plus years after Windows 7 General Availability; the WDAC successor is the recommendation for new deployments, but the installed AppLocker base did not get replaced.&lt;/p&gt;
&lt;h3&gt;10.2 Mimikatz tradecraft as the floor of red-team capability&lt;/h3&gt;
&lt;p&gt;On any pre-Credential-Guard Windows estate -- and that is still a non-trivial fraction of the 2026 install base -- Mimikatz&apos;s 2011-2014 module set defines the floor of red-team capability. &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; reads every LSA-cached credential the operator&apos;s privileges allow [@mimikatz-github]. &lt;code&gt;sekurlsa::tickets /export&lt;/code&gt; extracts every Kerberos ticket from the LSA cache. &lt;code&gt;lsadump::secrets&lt;/code&gt; reads LSA private secrets. &lt;code&gt;lsadump::sam&lt;/code&gt; reads local SAM hashes. &lt;code&gt;kerberos::ptt&lt;/code&gt; re-imports tickets for replay. &lt;code&gt;kerberos::golden&lt;/code&gt; forges Golden Tickets given a stolen krbtgt hash [@metcalf-golden-ticket]. The Part 3 window&apos;s primitives are the foundation any practitioner reasoning about lateral movement in a Windows-AD estate uses every day, and the conceptual model Sean Metcalf documented on ADSecurity.org remains the canonical operator-grade reference.&lt;/p&gt;
&lt;h3&gt;10.3 Detection&lt;/h3&gt;
&lt;p&gt;Where to look. Sysmon ProcessAccess events on LSASS (event ID 10) with Granted Access masks of &lt;code&gt;0x1010&lt;/code&gt;, &lt;code&gt;0x1410&lt;/code&gt;, or &lt;code&gt;0x143A&lt;/code&gt; correspond to the read-and-decrypt access pattern Mimikatz&apos;s &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; requires; the masks decompose into &lt;code&gt;PROCESS_VM_READ + PROCESS_QUERY_LIMITED_INFORMATION&lt;/code&gt; (0x1010), plus &lt;code&gt;PROCESS_VM_OPERATION&lt;/code&gt; (0x1410), plus &lt;code&gt;PROCESS_VM_WRITE + PROCESS_CREATE_THREAD&lt;/code&gt; (0x143A), and are widely-attested operator-grade detection lore catalogued across EDR vendor blogs and MITRE ATT&amp;amp;CK T1003.001 (OS Credential Dumping: LSASS Memory) sub-techniques [@mitre-t1003-001]. Windows Security event 4673 (sensitive privilege use) on &lt;code&gt;SeDebugPrivilege&lt;/code&gt; fires when a process adjusts its token to enable debug privileges -- the prerequisite for &lt;code&gt;privilege::debug&lt;/code&gt; -- which is interesting in itself when the actor is not a known debugger. System Access Control Lists on the krbtgt account, paired with Domain Controller audit subcategories for Kerberos AS-REQ and TGS-REQ, surface the AS-REQ-without-corresponding-logon anomalies that Golden Ticket use produces [@metcalf-golden-ticket]. Microsoft Defender for Identity raises Suspected Golden Ticket and Suspected Skeleton Key alerts on its analysis of domain-controller telemetry (the Skeleton Key alert is a Part 4 forward reference).&lt;/p&gt;
&lt;p&gt;{`
// Conceptual classifier for Sysmon event ID 10 (ProcessAccess) targeting LSASS.
// The canonical &quot;read-and-decrypt&quot; mask pattern Mimikatz needs to call
// OpenProcess + ReadProcessMemory + BCryptDecrypt against LSASS.
function isMimikatzLikely(event) {
  if (event.id !== 10) return false;
  if (!/lsass.exe$/i.test(event.targetImage)) return false;
  const interesting = new Set([&apos;0x1010&apos;, &apos;0x1410&apos;, &apos;0x143A&apos;]);
  return interesting.has(event.grantedAccess.toLowerCase().toUpperCase());
}&lt;/p&gt;
&lt;p&gt;const sample = {
  id: 10,
  targetImage: &apos;C:\\Windows\\System32\\lsass.exe&apos;,
  grantedAccess: &apos;0x1410&apos;,
  sourceImage: &apos;C:\\tools\\mimikatz.exe&apos;
};&lt;/p&gt;
&lt;p&gt;console.log(&apos;Alert?&apos;, isMimikatzLikely(sample));
console.log(&apos;SOCs combine this with allow-listed debugger paths and PPL state.&apos;);
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The same Restricted Admin flag that closes the disclosure-at-server gap [@kb2871997] also enables a Pass-the-Hash operator to invoke &lt;code&gt;sekurlsa::pth /run:&quot;mstsc /restrictedadmin&quot;&lt;/code&gt; from a compromised host and authenticate to the target RDP server using only the stolen NT hash [@mimikatz-github]. Restricted Admin is a &lt;em&gt;disclosure&lt;/em&gt; mitigation, not a &lt;em&gt;replay&lt;/em&gt; mitigation. Combine it with Remote Credential Guard (Windows 10 1607+) on tier 0 administrative paths.&lt;/p&gt;
&lt;/blockquote&gt;

1. Apply KB2871997 with `UseLogonCredential = 0` on every supported Windows. Zero downside.
2. Enable `RunAsPPL = 1` after a one-cycle LSA plug-in audit. Plan a rollback for any custom credential provider not yet re-signed at the PPL signer level [@ms-lsa-protection].
3. Adopt the Pass-the-Hash v2 tiering model as planning vocabulary, then operationalise it as Microsoft&apos;s *Securing Privileged Access* / Enterprise Access Model documentation. Multi-year programme; treat as a roadmap [@ms-pth-v2].
4. Use Restricted Admin for administrative RDP; promote to Remote Credential Guard on tier 0 paths.
5. Run AppLocker on every Enterprise SKU you have not yet migrated to WDAC [@ms-applocker]. Lock down `AppIDSvc` start-type to disabled-but-set-by-policy.
6. Enable Secure Boot, Measured Boot, and BitLocker (TPM + PIN) on every laptop [@ms-secure-boot]. Microsoft&apos;s default platform validation profile on native UEFI + Secure Boot systems is PCR 7 (Secure Boot State) and PCR 11 (BitLocker access control), which is the *correct* profile to use when Secure Boot is on and the platform&apos;s option ROMs are trusted [@ms-bitlocker-configure]. For hardened estates that want to detect tampering with the UEFI firmware itself, the option-ROM configuration, or the boot-manager binary independent of Secure Boot&apos;s signature check, expand the profile to PCRs 0, 2, 4, 7, 11 -- adding PCR 0 (UEFI firmware code), PCR 2 (option-ROM code), and PCR 4 (boot-manager binary measurements) on top of the default [@ms-bitlocker-countermeasures]. The hardened profile generates more BitLocker recovery-key prompts after legitimate firmware updates, so the operational cost is real and the choice between the two profiles is the standard balance between detection coverage and help-desk load.
7. Enable Credential Guard (Windows 10 1607+) on every estate whose hardware supports it [@ms-credential-guard]. This is the architectural answer; everything above is harm reduction.
&lt;p&gt;The 2009-2014 primitives are still here. So is Mimikatz. Part 4 explains why, and what Microsoft did about it.&lt;/p&gt;
&lt;h2&gt;11. Frequently asked questions&lt;/h2&gt;

No. The four zero-days -- MS10-046 (LNK shortcut RCE), MS10-061 (Print Spooler RCE), MS10-073 (win32k.sys keyboard-layout LPE), and MS10-092 (Task Scheduler LPE) -- were used across the worm&apos;s propagation and escalation surfaces, *not* chained in a single sequential exploit [@symantec-stuxnet-dossier-v14; @ms-bulletin-ms10-046; @ms-bulletin-ms10-061; @ms-bulletin-ms10-073; @ms-bulletin-ms10-092]. Different hosts encountered different combinations depending on patch level, USB usage, network shape, and whether the local user already had administrative privileges.

Only with two qualifiers -- multi-zero-day and kinetic-physical effect. Operation Aurora (January 12, 2010) used a single Internet Explorer 0-day (CVE-2010-0249) against Google and at least twenty other named victims including Adobe, Juniper, Yahoo, Symantec, Northrop Grumman, Dow Chemical, and Morgan Stanley (full sourcing and the verbatim Google wording in §3.6) [@google-aurora-wayback; @nvd-cve-2010-0249]; Stuxnet (June 17, 2010) used four zero-days for kinetic effect [@symantec-stuxnet-dossier-v14]. Drop either qualifier and Aurora falsifies the framing.

No. The Pass-the-Hash concept dates to Paul Ashton&apos;s 1997 NTBugtraq post [@wikipedia-pth] and was operationalised by Hernan Ochoa&apos;s 2008 Pass-the-Hash Toolkit (`iam.exe` / `whosthere.exe`) at Core Security Corelabs [@core-ptht-2008]. What Mimikatz did was make the primitive operational on a default-configured modern Windows host without requiring custom NTLM client code [@greenberg-mimikatz-wired; @mimikatz-github]. It turned a known protocol weakness into a one-line operator tool that ran against any LSASS the operator could `OpenProcess` against, and it added the Kerberos primitives (Pass-the-Ticket, Overpass-the-Hash, Golden Ticket) that previous Pass-the-Hash toolchains had not addressed. Skip Duckwall and Chris Campbell&apos;s *Pass-the-Hash 2: The Admin&apos;s Revenge* at Black Hat USA 2013 formalised the graph-walking discipline that ties Mimikatz primitives together into the lateral-movement operating model the rest of the decade inherits [@duckwall-campbell-bh2013].

Partially. The headline CVE (CVE-2014-6321) was patched on a published Patch Tuesday bulletin on November 11, 2014 [@ms-bulletin-ms14-066; @nvd-cve-2014-6321] with contemporary KrebsOnSecurity coverage [@krebs-ms14-066] and public proof-of-concept walkthroughs within months. The &quot;silent&quot; framing applies only to the additional Schannel hardening fixes Microsoft bundled into the same bulletin without separate disclosures. This article deliberately does not name specific CVE IDs for those bundled extras, because prior pipeline runs found such attributions factually wrong.

It wasn&apos;t, because Microsoft published v1 in December 2012 [@ms-pth-v1-landing] and v2 in July 2014 [@ms-pth-v2] and then migrated subsequent guidance into the post-2014 *Securing Privileged Access* online documentation rather than producing a numbered v3 PDF. Any &quot;v3 2017&quot; reference in secondary sources is incorrect; the canonical documentation chain after v2 is the *Securing Privileged Access* and *Enterprise Access Model* pages on Microsoft Learn.

No. The Symantec dossier was authored by Nicolas Falliere, Liam O Murchu, and Eric Chien of Symantec Security Response, v1.4, February 2011 [@symantec-stuxnet-dossier-v14]. Bruce Dang was at Microsoft&apos;s Security Response Center and co-presented &quot;Adventures in Analyzing Stuxnet&quot; with Peter Ferrie at the 27th Chaos Communication Congress (27C3) in Berlin on December 27, 2010 [@dang-ferrie-27c3], which is a separate primary covering the win32k.sys CVE-2010-2743 kernel exploit walkthrough (the 27C3-not-29C3 venue correction is documented in the §3.1 sidenote). Dang&apos;s affiliation is Microsoft MSRC, not Symantec.

No. Mimikatz&apos;s first public release was May 2011 (closed source) [@greenberg-mimikatz-wired; @wikipedia-mimikatz]. The GitHub repository `gentilkiwi/mimikatz` was created on April 6, 2014 at 18:30:02 UTC -- a timestamp anyone can verify via the GitHub API [@mimikatz-github]. Any &quot;2007&quot; date refers to Delpy&apos;s pre-release private experimentation, not a public release.

No. Both anchor events post-date the Part 3 window. Dell SecureWorks Counter Threat Unit disclosed the Skeleton Key malware family on January 12, 2015 [@secureworks-skeleton-key], and Delpy added the corresponding `misc::skeleton` module to Mimikatz on January 17, 2015. Skeleton Key, DCSync, and the Credential Guard architectural pivot are the spine of Part 4 [@metcalf-dcsync; @ms-credential-guard].
&lt;p&gt;Skeleton Key. Virtualisation-Based Security. Credential Guard. Part 4 opens on January 17, 2015, with the same Mimikatz codebase and a new technique.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;windows-security-wars-part-3-hardening-decade&quot; keyTerms={[
  { term: &quot;LSASS&quot;, definition: &quot;Local Security Authority Subsystem Service: the long-lived user-mode Windows process that caches NT hashes, Kerberos tickets, and (depending on loaded security packages) recoverable plaintext credentials for single sign-on.&quot; },
  { term: &quot;AppContainer&quot;, definition: &quot;A Windows access token with a per-package security identifier and capability-SID vector; resource access checks intersect the capability set with the resource ACL. The substrate for WinRT / Modern apps in Windows 8 and MSIX / Win32 App Isolation in modern Windows.&quot; },
  { term: &quot;Pass-the-Hash (PtH)&quot;, definition: &quot;Replay an NT hash as a bearer credential against any NTLM-accepting service, without ever knowing the user&apos;s plaintext password.&quot; },
  { term: &quot;Pass-the-Ticket (PtT)&quot;, definition: &quot;Extract a Kerberos Ticket-Granting Ticket or service ticket from LSASS and re-import it into another logon session for replay.&quot; },
  { term: &quot;Overpass-the-Hash&quot;, definition: &quot;Use a stolen NT hash to request a fresh Kerberos TGT from the KDC; the bridge from an NTLM-recovered hash to a Kerberos-issued ticket.&quot; },
  { term: &quot;Golden Ticket&quot;, definition: &quot;A forged Kerberos TGT signed with the stolen NT hash of the domain&apos;s krbtgt account; grants arbitrary user, group, and lifetime impersonation across the AD forest.&quot; },
  { term: &quot;Protected Process Light (PPL)&quot;, definition: &quot;A kernel-enforced signer level that prevents OpenProcess(PROCESS_VM_READ) and code injection against the protected process from lower-signer-level callers, regardless of token privileges.&quot; },
  { term: &quot;ELAM&quot;, definition: &quot;Early Launch Antimalware: the first boot-start driver, allowed to inspect and classify subsequent boot-start drivers via BDCB_CLASSIFICATION before they load.&quot; },
  { term: &quot;Secure Boot&quot;, definition: &quot;UEFI firmware verification of the signature of every UEFI driver, option ROM, and OS loader before transferring control, anchored by the Platform Key and signed Key Exchange Keys.&quot; },
  { term: &quot;VTL0 / VTL1&quot;, definition: &quot;Virtual Trust Levels introduced by Hyper-V in Windows 10 1507; VTL0 is the normal Windows kernel attackers compromise, VTL1 is Virtual Secure Mode where Credential Guard hosts the LSAISO trustlet that holds high-value credential material.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>mimikatz</category><category>stuxnet</category><category>pass-the-hash</category><category>credential-theft</category><category>applocker</category><category>secure-boot</category><category>lsass</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>The Empty Hash: Credential Guard, the LsaIso Trustlet, and the Eleven-Year LSASS Extraction Tradition</title><link>https://paragmali.com/blog/the-empty-hash-credential-guard-the-lsaiso-trustlet-and-the-/</link><guid isPermaLink="true">https://paragmali.com/blog/the-empty-hash-credential-guard-the-lsaiso-trustlet-and-the-/</guid><description>Why a 2026 Mimikatz dump returns [LSA Isolated Data] instead of an NTLM hash, what LsaIso.exe really computes, and the five things Credential Guard was never going to close.</description><pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Credential Guard moves the long-lived NTLM hash and the Kerberos long-term key out of `lsass.exe` (in the VTL0 NT kernel) and into `LsaIso.exe` (in VTL1, behind the hypervisor).** The hash is no longer in the dump because the hash is no longer in the process. Default-on for domain-joined, non-domain-controller Windows 11 22H2+ and Windows Server 2025 systems that meet the hardware requirements [@ms-cg-overview]. The architecture closes the eleven-year LSASS-memory-dump class. It does not close credential **use** (Kerberoast [@attack-kerberoast]), token impersonation (the PrintSpoofer / Potato chain [@itm4n-printspoofer]), plaintext-secret protocols [@ms-cg-considerations] (NTLMv1, MS-CHAPv2, Digest, CredSSP), or the trustlet&apos;s own RPC output (Pass-the-Challenge, December 2022 [@lyak-passchallenge-wayback]). This is the deep look at the canonical VBS trustlet -- the encrypted-blob fields, the IUM API surface, the five residual attack classes, and Microsoft&apos;s own honest accounting of what Credential Guard was never going to protect.
&lt;h2&gt;1. The 3:14 a.m. Mimikatz that returned an empty hash&lt;/h2&gt;
&lt;p&gt;It is 3:14 a.m. on a 2026 Windows 11 24H2 box. The operator has SYSTEM. The operator has &lt;code&gt;SeDebugPrivilege&lt;/code&gt;. The operator has bypassed Protected Process Light the way PPLdump [@github-ppldump] did in 2021, has dumped &lt;code&gt;lsass.exe&lt;/code&gt; with &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; from Mimikatz [@github-mimikatz], and is staring at the screen.&lt;/p&gt;
&lt;p&gt;For the nine years before mid-2015, the next line on that screen would have been the user&apos;s NTLM hash. Tonight, the next line is something else entirely.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;msv : [00000003] Primary * Username : alice * Domain   : CONTOSO * NTLM     : [LSA Isolated Data] Is NT Present: True Context Handle: 0x1b6d5216c60 Proxy Info: 0x7ffdd8bfd380 Encrypted blob: a000000000000000080000006400000001000000010100000100000036...4e746c6d48617368... DPAPI: c02c86e371103ad7d7d352b19af1a74a00000000&lt;/code&gt; Structurally identical to the PassTheChallenge README [@github-passthechallenge] example, with username and domain renamed for narrative clarity. Hex prefix, field names, and embedded &lt;code&gt;NtlmHash&lt;/code&gt; ASCII tag are verbatim. This is the artefact that tells the operator the architectural shift happened.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The literal string &lt;code&gt;[LSA Isolated Data]&lt;/code&gt; sits where the NTLM hash used to sit. The hex prefix &lt;code&gt;a000000000000000&lt;/code&gt; is the same prefix on every Credential-Guard-protected box on the planet. The trailing ASCII tag &lt;code&gt;4e746c6d48617368&lt;/code&gt; decodes to &lt;code&gt;NtlmHash&lt;/code&gt;: the field name survives, the value does not.&lt;/p&gt;
&lt;p&gt;This article is the deep look at the canonical VBS trustlet that is responsible for that empty hash. It is the companion piece to the broader VBS trustlets treatment in this series [@paragmali-com-secure-kernel], which uses &lt;code&gt;LsaIso.exe&lt;/code&gt; as its running example without unfolding the four things that matter most about it: the eleven-year extraction history that motivated the design; what &lt;code&gt;LsaIso.exe&lt;/code&gt; actually computes and what every field of the encrypted blob means; Pass-the-Challenge -- the residual class Credential Guard was never going to close; and the honest, Microsoft-documented limits [@ms-cg-howitworks], enumerated.&lt;/p&gt;
&lt;p&gt;A note on intent and verification: this is defensive research. Every primary source was verified live on 2026-05-11, against the public web; every command and tool named is in the open-source security canon, used today by Microsoft&apos;s own product teams, by enterprise red teams, and by every blue team that takes the storage-versus-use distinction seriously.&lt;/p&gt;
&lt;p&gt;The hash is not in the dump because the hash is no longer in the process. Where it went, and why Microsoft moved it, is twenty-two years of &lt;code&gt;lsass.exe&lt;/code&gt; history.&lt;/p&gt;
&lt;h2&gt;2. Why LSASS became the single highest-value memory dump on Windows (1993--2014)&lt;/h2&gt;
&lt;p&gt;Twenty-two years before the empty hash, &lt;code&gt;lsass.exe&lt;/code&gt; shipped in Windows NT 3.1. It was not, at first, the most-attacked process on Windows. It became that, slowly, over the course of eleven years and one tool. This is the tradition the trustlet was built to break.&lt;/p&gt;

The user-mode Windows service that handles interactive logon, NTLM challenge-response, Kerberos AS/TGS exchanges, security-policy enforcement, password changes, and the loading of every Security Support Provider DLL the system uses for authentication. Until Credential Guard, it also held every long-lived authentication secret for every signed-in user in its own process memory, because the protocols it implemented required the secret to be present when the network talked to it. See the canonical LSA Authentication [@ms-lsa-authentication] reference.
&lt;p&gt;The architectural reason &lt;code&gt;lsass.exe&lt;/code&gt; had to hold the secret is structural to the protocols it speaks. NTLM [@paragmali-com-in-windows] and Kerberos are challenge-response protocols. The server sends a challenge; the client encrypts the challenge with a key derived from the password; the server compares. The key the client uses is not the password itself but the NT one-way function output (NTOWF) [@wiki-pth] -- the MD4 of the UTF-16-LE password. For Kerberos the client uses a long-term key (DES, RC4, or AES) derived from the password under a protocol-defined string-to-key function [@en-wikipedia-org-wiki-kerberosprotocol]). In both cases the server expects the client to prove possession of a value that is functionally equivalent to the password, every time the client authenticates.&lt;/p&gt;

The MD4 hash of the UTF-16-LE encoded password. Despite the name, NTOWF is one-way only with respect to the original password. With respect to the network, the NTOWF *is* the credential: any process that holds it can compute the response to any NTLM challenge any server will ever issue, with no further information about the user.
&lt;p&gt;For single-sign-on to work -- the user types the password once, the OS uses it transparently for every later authentication that day -- something has to remember that derived value, in clear, in a process that wakes up whenever a remote service asks the kernel to authenticate. That something is &lt;code&gt;lsass.exe&lt;/code&gt;. Until 2015, &quot;remembers&quot; meant &quot;holds the bytes in process memory.&quot;The phrase &quot;the hash is the password&quot; is not a metaphor. The NTLM challenge-response computation &lt;code&gt;DESL(NTOWF, challenge)&lt;/code&gt; (three separate DES encryptions on 7-byte key segments per MS-NLMP section 3.3.1) accepts the NTOWF directly. An attacker who holds the NTOWF and can reach a server that speaks NTLM does not need to know the password at all. This is the &lt;em&gt;structural&lt;/em&gt; reason Pass-the-Hash works on every NTLM-speaking service in the network.&lt;/p&gt;
&lt;h3&gt;The eight inflection points&lt;/h3&gt;
&lt;p&gt;In 1997, Paul Ashton published the original Pass-the-Hash technique on Bugtraq [@wiki-pth] -- a modified Samba SMB client that accepted user password hashes instead of cleartext passwords. The conceptual claim landed: if the client only proves possession of the hash, the hash is the credential. The implementation claim took another eleven years to land.&lt;/p&gt;
&lt;p&gt;In 2001, Sir Dystic of Cult of the Dead Cow disclosed SMBRelay at lanta.con on March 31 [@cdc-smbrelay] (not March 21 as some Wikipedia revisions claim, per the project&apos;s own page). SMBRelay was the &lt;em&gt;use&lt;/em&gt;-class breakthrough: rather than crack the hash, intercept the protocol exchange and let the victim&apos;s own client do the cryptography against the attacker&apos;s chosen target.&lt;/p&gt;
&lt;p&gt;In 2008, Hernan Ochoa shipped the Pass-the-Hash Toolkit [@wiki-pth] -- the load-bearing 2008 contribution -- and introduced &quot;dump the hash from &lt;code&gt;lsass.exe&lt;/code&gt; memory&quot; as a public, repeatable post-exploitation technique. The toolkit was later superseded by Windows Credential Editor [@wiki-pth]. For the first time, an attacker did not need to crack anything. The attacker needed &lt;code&gt;OpenProcess(VM_READ)&lt;/code&gt; on &lt;code&gt;lsass.exe&lt;/code&gt; and a parser.The Pass-the-Hash Toolkit and Windows Credential Editor [@wiki-pth] were the immediate ancestors of Mimikatz. They established the LSASS-process-memory dump as the canonical credential-extraction primitive on Windows; Mimikatz only had to follow the trail and add WDigest plaintext recovery on top.&lt;/p&gt;
&lt;p&gt;In May 2011, Benjamin Delpy released Mimikatz, closed-source [@wired-mimikatz], and added one feature on top of WCE that turned the field upside down: &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; returned not just NTLM hashes but plaintext WDigest passwords. WDigest -- a digest-authentication SSP that Microsoft had shipped in Windows XP and Server 2003 to support HTTP digest -- stored the encrypted password blob &lt;em&gt;and&lt;/em&gt; the encryption key in &lt;code&gt;lsass.exe&lt;/code&gt; memory, simultaneously, so that the SSP could re-derive the digest response on demand. Delpy called the result, accurately, &quot;like storing a password-protected secret in an email with the password in the same email&quot; [@wired-mimikatz].&lt;/p&gt;

It&apos;s like storing a password-protected secret in an email with the password in the same email. -- Benjamin Delpy, on the WDigest plaintext-cache architecture
&lt;p&gt;In September 2011, Mimikatz was used in the DigiNotar breach [@wiki-diginotar] -- the certificate-authority compromise that issued forged certificates for Google, Microsoft, and Twitter domains, used via MITM against roughly 300,000 Iranian Gmail users [@wiki-diginotar]. Mimikatz crossed from researcher curio to nation-state-grade tradecraft in a single news cycle.Wired&apos;s profile [@wired-mimikatz] recounts an early-2012 Positive Hack Days incident in Moscow in which, immediately after Delpy&apos;s Mimikatz talk, a man in a dark suit demanded that Delpy put his slides and a copy of Mimikatz on a USB drive. Delpy complied and then -- before leaving Russia -- published the code as open source on GitHub. It is the moment Delpy realised he had built something that nation-state services were now travelling to obtain in person.&lt;/p&gt;
&lt;p&gt;On April 6, 2014, at 22:02:03, Delpy committed Mimikatz 2.0 to GitHub as open source [@github-mimikatz]. The compile timestamp is in the README banner, verbatim. Microsoft&apos;s lead time on every WDigest-class disclosure dropped from &quot;months&quot; to &quot;the next minute any attacker reads the README.&quot;&lt;/p&gt;
&lt;p&gt;On May 13, 2014, Microsoft shipped KB2871997 / MSA 2871997 [@ms-kb2871997]. On Windows 8.1 and Server 2012 R2 and later, the registry value &lt;code&gt;WDigest\UseLogonCredential&lt;/code&gt; defaults to &lt;code&gt;0&lt;/code&gt; and WDigest no longer caches plaintext credentials in &lt;code&gt;lsass.exe&lt;/code&gt; memory. The plaintext leg closed. The hash leg could not, because the protocol required it.&lt;/p&gt;

timeline
    title LSASS as the highest-value Windows process, 1993-2014
    1993 : NT 3.1 ships : lsass.exe holds NTOWF + Kerberos keys
    1997 : Paul Ashton : Pass-the-Hash on Bugtraq
    2001 : Sir Dystic : SMBRelay at lanta.con
    2008 : Hernan Ochoa : Pass-the-Hash Toolkit (later WCE)
    2011 : Benjamin Delpy : Mimikatz closed-source release (May)
    2011 : DigiNotar breach : Mimikatz used in the wild (September)
    2014 : Mimikatz 2.0 : GitHub open-source (April 6, 22:02:03)
    2014 : KB2871997 : WDigest cache disabled by default (May 13)

The class of attack in which an authenticated client proves possession of an NTOWF (the NT one-way function output, MD4 of the UTF-16-LE password) directly, without ever knowing the cleartext password. The technique was originally published by Paul Ashton in 1997 [@wiki-pth] and was made native to Windows by Hernan Ochoa&apos;s 2008 Pass-the-Hash Toolkit [@wiki-pth]. It is structural to the NTLM protocol; closing the class requires either eliminating the protocol or moving the hash out of any process the attacker can read.
&lt;p&gt;By May 2014, Microsoft had patched what could be patched. Mimikatz 2.0 was on GitHub. The hash was still in the process, because it had to be. The next move had to be architectural. But before Microsoft made that move, they tried four other things.&lt;/p&gt;
&lt;h2&gt;3. What Microsoft tried before trustlets (2007--2014)&lt;/h2&gt;
&lt;p&gt;If you cannot move the secret, what can you do? Microsoft tried four answers between 2007 and 2014. Each is in production today. None of them moves the secret.&lt;/p&gt;
&lt;h3&gt;Generation 2: Vista&apos;s Protected Process (2007)&lt;/h3&gt;
&lt;p&gt;In Windows Vista, Microsoft introduced the Protected Process [@ionescu-bh2015-pdf] primitive: a binary signed under a designated Microsoft media-protection certificate could run in a process whose memory other Windows processes -- including processes running as administrator -- could not read or modify. The reason was DRM. Audio and video pipelines wanted a way to keep AACS and PlayReady decryption keys out of debuggers. The Protected Process primitive was not, in 2007, applied to &lt;code&gt;lsass.exe&lt;/code&gt;. Six years passed before Microsoft generalised it.&lt;/p&gt;
&lt;h3&gt;Generation 3: LSA Protection / &lt;code&gt;RunAsPPL&lt;/code&gt; (2013)&lt;/h3&gt;
&lt;p&gt;In Windows 8.1, Microsoft generalised Protected Process into Protected Process Light (PPL) [@itm4n-runasppl], a signer-level lattice that allowed multiple signer &quot;kinds&quot; to live alongside the original DRM kind, and the &lt;code&gt;RunAsPPL&lt;/code&gt; registry value lit up &lt;code&gt;lsass.exe&lt;/code&gt; as a PPL [@paragmali-com-app-ide].&lt;/p&gt;

A Windows process that runs at a signer-level higher than ordinary administrator processes, such that ordinary administrators cannot open it for memory read or for code injection. Created in Windows 8.1 as a generalisation of the Vista Protected Process primitive. Enforcement is done by the NT kernel: `OpenProcess` with `PROCESS_VM_READ` from a non-PPL caller returns `ERROR_ACCESS_DENIED` (0x5) [@itm4n-runasppl] regardless of the caller&apos;s token privileges.
&lt;p&gt;itm4n&apos;s reference write-up of &lt;code&gt;RunAsPPL&lt;/code&gt; [@itm4n-runasppl] reproduces what Mimikatz sees on a PPL-protected &lt;code&gt;lsass.exe&lt;/code&gt;: the call to &lt;code&gt;OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, lsass_pid)&lt;/code&gt; -- the verbatim opener of &lt;code&gt;kuhl_m_sekurlsa_acquireLSA()&lt;/code&gt; -- fails with &lt;code&gt;0x00000005&lt;/code&gt;, &lt;code&gt;ERROR_ACCESS_DENIED&lt;/code&gt;. The hash extraction routine never runs, because the attacker cannot read the page.&lt;/p&gt;
&lt;p&gt;itm4n&apos;s writeup is also the canonical source for what &lt;code&gt;RunAsPPL&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt;. The same NT kernel that enforces PPL is the kernel the attacker is trying to subvert. Two bypass classes exist in the public record. The first is kernel-mode: an attacker who loads a signed driver -- including Delpy&apos;s own &lt;code&gt;mimidrv.sys&lt;/code&gt; [@itm4n-runasppl] -- can suspend PPL enforcement from kernel-space because the kernel is the enforcement mechanism. This is the &lt;em&gt;bring your own vulnerable driver&lt;/em&gt; bypass class.&lt;/p&gt;

A privilege-escalation pattern in which an attacker with administrator privilege loads a signed-but-vulnerable third-party driver, then exploits a known vulnerability in the driver to run arbitrary code at kernel mode. Because the driver is signed, the kernel loads it; because the kernel loaded the driver, the driver can disable any defence the kernel enforces, including PPL. Microsoft&apos;s recommended vulnerable-driver block-list shrinks the BYOVD inventory; it does not eliminate the class. Delpy&apos;s own `mimidrv.sys` is the canonical reference exploit driver [@itm4n-runasppl] for this class against `lsass.exe`.
&lt;p&gt;The second is userland: itm4n&apos;s PPLdump (April 2021) [@github-ppldump] exploited a structural weakness in the PPL section-validation logic. A new Windows process loads NTDLL, then asks the image loader to load other DLLs. PPLs are allowed to load DLLs from the &lt;code&gt;\KnownDlls&lt;/code&gt; directory, and -- crucially -- the digital signature of a &lt;code&gt;\KnownDlls&lt;/code&gt; entry is checked when the section is created, not when it is mapped into the address space of a PPL process. PPLdump used &lt;code&gt;DefineDosDevice&lt;/code&gt; to swap the symbolic link of a &lt;code&gt;\KnownDlls&lt;/code&gt; entry, and the PPL &lt;code&gt;lsass.exe&lt;/code&gt; mapped the swapped-in attacker DLL into its own address space, with PPL enforcement intact. The SCRT writeup [@blog-scrt-bypass-lsa] is the canonical 2021 reference. Microsoft closed the userland weakness in build 19044.1826, the July 2022 update [@itm4n-end-of-ppldump], with an &lt;code&gt;LdrpInitializeProcess&lt;/code&gt; patch in NTDLL gated by a &lt;code&gt;Feature_Servicing_2206c_38427506__private_IsEnabled&lt;/code&gt; feature flag. On Windows 8.1 and Server 2012 R2, PPLdump&apos;s behaviour is unstable per the project README [@github-ppldump]: itm4n notes the exploit fails on fully updated machines for an unidentified earlier patch. The userland weakness is therefore closed across the modern estate; legacy boxes that have lapsed on cumulative updates remain the practical exposure.itm4n is explicit about the architectural framing: LSA Protection is &quot;a true quick win [@itm4n-runasppl]&quot; because attackers &quot;will have to use some relatively advanced tricks if they want to work around it, which ultimately increases their chance of being detected.&quot; But in the same post: &quot;[LSA Protection] tends to be confused with [Credential Guard], which is completely different ... Credential Guard and LSA Protection are actually complementary.&quot; That confusion is the most common architectural error in defensive-security reviews of Windows endpoints.&lt;/p&gt;
&lt;h3&gt;Generation 4: KB2871997 + the compensating-control playbook (2014)&lt;/h3&gt;
&lt;p&gt;KB2871997 [@ms-kb2871997] shipped on May 13, 2014 and rolled up three behavioural changes: WDigest cache disabled by default in Windows 8.1 / Server 2012 R2 and later (&lt;code&gt;UseLogonCredential = 0&lt;/code&gt;); the &lt;code&gt;TokenLeakDetectDelaySecs&lt;/code&gt; registry default; and a follow-on October 14, 2014 update that added Restricted Admin mode for Remote Desktop Connection [@ms-kb2871997] on Windows 7 / Server 2008 R2. The same broader 2013--2014 credential-protection initiative also delivered the Protected Users group [@ms-protected-users] (an Active Directory feature shipped in Windows 8.1 [@wiki-win81] / Server 2012 R2 [@wiki-ws2012r2], October 2013). Protected Users is the device-side mitigation: members cannot use credential delegation (CredSSP), Windows Digest, NTLM cached credentials or NTOWFs, DES or RC4 in Kerberos preauthentication, or offline cached verifiers; their TGT lifetime is capped at four hours.&lt;/p&gt;

Protected Users membership requires AES-only Kerberos. Estates with legacy applications that rely on RC4 service tickets (a long tail in any healthcare or industrial deployment) cannot enable Protected Users without a forklift modernisation of their Kerberos client and server inventory. This is the practical reason the Protected Users adoption rate, ten years after the feature shipped, sits well below 100% on enterprise estates that have every other 2014-era mitigation enabled.
&lt;h3&gt;Generation 4.5: Tier 0 isolation, jump-server architecture, AdminSDHolder hygiene&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;Mitigating Pass-the-Hash&lt;/em&gt; v1 (2012) and v2 (2014) playbooks [@ms-lsa-protection] layered organisational changes on top of the per-host technical changes: tier the administrative model so that Tier 0 credentials never log on to Tier 1 or Tier 2 hosts; require every Tier 0 administrative session to traverse a dedicated jump server; clean up AdminSDHolder so that orphaned high-privilege accounts cannot be re-used. The playbooks are still cited in 2026 deployment guides because the underlying recommendations remain correct.&lt;/p&gt;

flowchart TD
    G0[&quot;Gen 0: NT 3.1 lsass.exe (1993)&lt;br /&gt;NTOWF + Kerberos keys in process memory&quot;]
    G1[&quot;Gen 1: WDigest plaintext cache (XP/2003)&lt;br /&gt;Plaintext + key both in lsass.exe&quot;]
    G2[&quot;Gen 2: Vista Protected Process (2007)&lt;br /&gt;For DRM; not applied to lsass.exe&quot;]
    G3[&quot;Gen 3: LSA Protection / RunAsPPL (2013)&lt;br /&gt;NT-kernel-enforced; mimidrv + PPLdump bypassable&quot;]
    G4[&quot;Gen 4: KB2871997 + Protected Users (2014)&lt;br /&gt;WDigest off; AES-only Kerberos; 4hr TGT&quot;]
    G5[&quot;Gen 5: Credential Guard / LsaIso.exe (2015)&lt;br /&gt;Hypervisor-enforced; NT kernel out of TCB&quot;]
    G6[&quot;Gen 6: Default-on (Win 11 22H2 / Server 2025)&lt;br /&gt;No-UEFI-lock&quot;]
    G0 --&amp;gt; G1 --&amp;gt; G2 --&amp;gt; G3 --&amp;gt; G4
    G4 --&amp;gt;|&quot;NT kernel still in TCB&quot;| G5
    G5 --&amp;gt; G6
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; As long as the secret lives in a process whose address space is governed by the same NT kernel that the attacker can compromise, the secret is extractable. Generations 0--4 add layers inside the NT-kernel TCB. The 2014 conclusion -- that you cannot patch your way out of the storage problem -- is structural to that TCB argument; the chain Gen 0 -&amp;gt; Gen 4 above traces it explicitly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Each of these layers shrinks the attack surface. None of them changes where the hash physically lives. The 2014 conclusion was unavoidable: the only fix is to move the hash out of the kernel that the attacker can compromise. So Microsoft did.&lt;/p&gt;
&lt;h2&gt;4. Credential Guard lands, then hardens, then defaults on (May 2015 -- November 2024)&lt;/h2&gt;
&lt;p&gt;On May 4, 2015, Brad Anderson stood at Microsoft Ignite [@ms-brad-ignite-2015] and said &lt;em&gt;&quot;more than 75 percent of all these attacks come down to weak credentials or compromised identities.&quot;&lt;/em&gt; Eighty-six days later, Windows 10 Enterprise RTM shipped with &lt;code&gt;LsaIso.exe&lt;/code&gt; running in VTL1.The 75-percent figure is the verbatim Anderson keynote quote and tracks Microsoft&apos;s own internal incident-response telemetry from the 2014--2015 period. The keynote explicitly demonstrates Device Guard at length; the &lt;em&gt;Credential Guard&lt;/em&gt; announcement at the same event is corroborated by ITPro Today&apos;s same-day recap (Wayback snapshot) [@itprotoday-ignite] and Microsoft&apos;s own subsequent blog posts.&lt;/p&gt;
&lt;h3&gt;The eight-event chronology&lt;/h3&gt;
&lt;p&gt;On May 4, 2015, the Anderson Ignite keynote announced Virtualization-Based Security, Device Guard, and Credential Guard alongside the Hello and Microsoft Passport identity story. On July 29, 2015, Windows 10 RTM [@ms-cg-overview] shipped with &lt;code&gt;LsaIso.exe&lt;/code&gt; as Trustlet ID 1 on Enterprise and Education SKUs. On August 5--6, 2015, Alex Ionescu reverse-engineered the trustlet model at Black Hat USA and published the slide deck [@ionescu-bh2015-pdf] that documents the dual-EKU + Signature Level 12 constraint and names &lt;code&gt;LSAISO.EXE&lt;/code&gt; as Trustlet ID 1 verbatim.&lt;/p&gt;
&lt;p&gt;Through 2016--2020, Server 2016 brought Credential Guard to server installs [@ms-ws2016-whatsnew], and the VSM master key + TPM 2.0 binding [@ms-cg-howitworks] hardened the persistent-state path.&lt;/p&gt;
&lt;p&gt;On September 20, 2022, Windows 11 22H2 became generally available with Credential Guard default-on for domain-joined non-DC hardware-eligible boxes [@ms-cg-overview], shipped without UEFI Lock. On December 26, 2022, Oliver Lyak published Pass-the-Challenge [@lyak-passchallenge-wayback]: the trustlet itself was faithful, but its RPC output became the new attack surface. On November 1, 2024, Windows Server 2025 became generally available and extended the default-on stance to server with the same domain-controller carve-out: &quot;Enabling Credential Guard on domain controllers isn&apos;t recommended. Credential Guard doesn&apos;t provide any added security to domain controllers, and can cause application compatibility issues on domain controllers.&quot; [@ms-cg-overview]&lt;/p&gt;

A binary signed at Signature Level 12 with both the Windows System Component Verification EKU (1.3.6.1.4.1.311.10.3.6) and the Isolated User Mode EKU (1.3.6.1.4.1.311.10.3.37), exporting an `s_IumPolicyMetadata` structure from a `.tpolicy` PE section, loaded by the Secure Kernel into VTL1 user mode at boot via `NtCreateUserProcess` with the `PsAttributeSecureProcess` attribute. Documented verbatim in Alex Ionescu&apos;s Black Hat USA 2015 deck [@ionescu-bh2015-pdf], which is still the load-bearing reverse-engineering primary on the trustlet model.

Two privilege levels enforced by the Hyper-V hypervisor on top of the host CPU&apos;s existing ring 0 / ring 3 split. VTL0 is the Normal World: Ring 3 user mode and Ring 0 NT kernel mode. VTL1 is the Secure World: Ring 3 user mode runs trustlets like `LsaIso.exe`, Ring 0 runs the Secure Kernel (`securekernel.exe`). The hypervisor uses Second-Level Address Translation (SLAT) to ensure VTL0 page tables cannot map physical pages that VTL1 has marked private. The Hypervisor TLFS Virtual Secure Mode reference [@ms-tlfs-vsm] defines `#define HV_NUM_VTLS 2` and notes that &quot;Architecturally, up to 16 levels of VTLs are supported; however a hypervisor may choose to implement fewer than 16 VTLs. Currently, only two VTLs are implemented.&quot;

The Ring-3 user mode component of VTL1. IUM hosts trustlets (signed user-mode binaries) that the Secure Kernel loads at boot. IUM processes have no device drivers, no third-party modules, and no normal-world IPC except via the explicitly-marshalled secure-call interface that the Secure Kernel mediates. Quarkslab&apos;s IUM debugging walkthrough [@quarkslab-falcon-ium] names &quot;the isolated version of LSASS (`LSAIso.exe`) when Credential Guard is enabled&quot; as the canonical IUM example.
&lt;p&gt;The four shipping trustlets per Ionescu&apos;s 2015 reverse-engineering [@ionescu-bh2015-pdf]: Trustlet ID 0 is the Secure Kernel Process (Device Guard); Trustlet ID 1 is &lt;code&gt;LSAISO.EXE&lt;/code&gt; (Credential Guard); Trustlet ID 2 is &lt;code&gt;vmsp.exe&lt;/code&gt; (the Hyper-V virtual TPM host side); Trustlet ID 3 is the vTPM provisioning trustlet. Eleven years later, that list is still exactly four, with ID 1 still the most-discussed.&lt;/p&gt;
&lt;p&gt;Every domain-joined Windows 11 box ships with &lt;code&gt;LsaIso.exe&lt;/code&gt; running today. What that small binary actually is, what it computes, and what an attacker who has SYSTEM on the box now sees is the load-bearing technical question of the next section.&lt;/p&gt;
&lt;h2&gt;5. What &lt;code&gt;LsaIso.exe&lt;/code&gt; actually is&lt;/h2&gt;
&lt;p&gt;The trustlet is a small binary that sits inside a separate kernel from the one your shell is running under. Its identity is precise, its API is small, and its memory is unreadable from the side of the boundary you are on. Microsoft&apos;s documentation gives the one-sentence shape:&lt;/p&gt;

With Credential Guard enabled, the LSA process in the operating system talks to a component called the isolated LSA process that stores and protects those secrets, LSAIso.exe. Data stored by the isolated LSA process is protected using VBS and isn&apos;t accessible to the rest of the operating system. -- Microsoft Learn, *How Credential Guard works* [@ms-cg-howitworks]
&lt;p&gt;That sentence hides everything interesting. The next six subsections unfold it.&lt;/p&gt;
&lt;h3&gt;5.1 Identity in the trustlet model&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;LsaIso.exe&lt;/code&gt; passes the five-gate trustlet definition [@ionescu-bh2015-pdf] by construction: Trustlet ID 1; signed at Signature Level 12; carries both the Windows System Component Verification EKU (1.3.6.1.4.1.311.10.3.6) and the Isolated User Mode EKU (1.3.6.1.4.1.311.10.3.37); exports the &lt;code&gt;s_IumPolicyMetadata&lt;/code&gt; structure from a &lt;code&gt;.tpolicy&lt;/code&gt; PE section; and is loaded by SMSS / wininit at boot through &lt;code&gt;NtCreateUserProcess&lt;/code&gt; with the &lt;code&gt;PsAttributeSecureProcess&lt;/code&gt; attribute, which routes through the Secure Kernel [@paragmali-com-the-en] rather than the NT kernel.&lt;/p&gt;

An Extended Key Usage object identifier embedded in an Authenticode signature that constrains what the signed binary is allowed to do. The Windows kernel and Secure Kernel inspect EKUs at load time. The dual-EKU requirement on trustlets means a signature legitimate for ordinary kernel-mode driver loading is *not* sufficient to load a binary as a trustlet; both the WSCV and the IUM EKU must be present, both signed by Microsoft.
&lt;p&gt;The two EKUs together are the identity gate. A binary that has only the WSCV EKU is a normal Microsoft-signed component.The IUM EKU is not a publicly issuable Authenticode EKU; only Microsoft can mint it -- per the Trustlet identity model documented verbatim in Ionescu&apos;s Black Hat USA 2015 deck [@ionescu-bh2015-pdf]. A binary that has only the IUM EKU does not exist in the wild. A binary that has both, and is signed by Microsoft, is admissible as a trustlet. The IUM EKU is not issued by any commercial CA; it is a Microsoft-internal OID with a Microsoft-internal issuance policy.&lt;/p&gt;
&lt;h3&gt;5.2 The agent / trustlet split&lt;/h3&gt;
&lt;p&gt;Credential Guard splits &lt;code&gt;lsass.exe&lt;/code&gt; (the historical agent) into two cooperating processes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;lsass.exe&lt;/code&gt; in VTL0&lt;/strong&gt; holds protocol state, network I/O, and every Security Support Provider DLL the system loads: &lt;code&gt;msv1_0.dll&lt;/code&gt; (NTLM), &lt;code&gt;kerberos.dll&lt;/code&gt; (Kerberos), &lt;code&gt;negoexts.dll&lt;/code&gt; (SPNEGO extensions), &lt;code&gt;cloudap.dll&lt;/code&gt; (the Microsoft Entra cloud authentication package), &lt;code&gt;wdigest.dll&lt;/code&gt; (Digest, with caching disabled), &lt;code&gt;tspkg.dll&lt;/code&gt; (Terminal Services / CredSSP), &lt;code&gt;livessp.dll&lt;/code&gt; (Microsoft account / Live), &lt;code&gt;pku2u.dll&lt;/code&gt; (peer-to-peer Kerberos), and &lt;code&gt;schannel.dll&lt;/code&gt; (TLS). The core SSP/AP set (Negotiate, Kerberos, NTLM, Digest, CredSSP, Schannel) is enumerated in Microsoft&apos;s SSP Packages Provided by Microsoft [@ms-ssp-packages] reference; CloudAP, NegoExts, TSPkg, LiveSSP, and PKU2U are documented under the broader LSA Authentication [@ms-lsa-authentication] reference. &lt;code&gt;lsass.exe&lt;/code&gt; does &lt;em&gt;not&lt;/em&gt; hold the long-lived NTOWF or Kerberos long-term keys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;LsaIso.exe&lt;/code&gt; in VTL1&lt;/strong&gt; holds NTLM hashes and Kerberos TGTs, plus a small fixed RPC API that lets the agent compute responses against those secrets without ever exposing them.&lt;/li&gt;
&lt;/ul&gt;

flowchart LR
    subgraph VTL0NT[&quot;VTL0 -- NT kernel governs the entire user-mode process space&quot;]
        L[&quot;lsass.exe&lt;br /&gt;NTOWF, Kerberos keys,&lt;br /&gt;SSP DLLs, network I/O&quot;]
        M[&quot;mimikatz&lt;br /&gt;SeDebugPrivilege&quot;]
    end
    M --&amp;gt;|&quot;OpenProcess(VM_READ) + memory dump&quot;| L

flowchart LR
    subgraph V0[&quot;VTL0 (Normal World)&quot;]
        L[&quot;lsass.exe&lt;br /&gt;SSP DLLs, network I/O,&lt;br /&gt;protocol state&lt;br /&gt;(NO long-term key)&quot;]
        M[&quot;mimikatz&lt;br /&gt;SeDebugPrivilege&quot;]
    end
    subgraph V1[&quot;VTL1 (Secure World)&quot;]
        I[&quot;LsaIso.exe&lt;br /&gt;NTOWF + Kerberos keys&quot;]
        SK[&quot;securekernel.exe&lt;br /&gt;(secure kernel)&quot;]
    end
    M --&amp;gt;|&quot;OpenProcess(VM_READ)&quot;| L
    L --&amp;gt;|&quot;LSA_ISO_RPC_SERVER ALPC&lt;br /&gt;NtlmIumCalculateNtResponse(...)&quot;| SK
    SK --&amp;gt;|&quot;validated secure call&quot;| I
    I --&amp;gt;|&quot;derived response&quot;| SK
    SK --&amp;gt;|&quot;return value&quot;| L
&lt;p&gt;The architectural pivot is that the &lt;code&gt;mimikatz&lt;/code&gt;-style memory dump still reaches &lt;code&gt;lsass.exe&lt;/code&gt;, but it no longer reaches the long-term key. The key has moved across a boundary the hypervisor [@paragmali-com-a-security] enforces with hardware page-table-permission bits, and no VTL0 process -- regardless of token, regardless of privilege -- can map the page.&lt;/p&gt;
&lt;h3&gt;5.3 The encrypted-blob format and the IUM API&lt;/h3&gt;
&lt;p&gt;The visible artefact of the move is the &lt;code&gt;[LSA Isolated Data]&lt;/code&gt; block in the Pypykatz dump from §1. The structure of that block is documented byte-by-byte in the PassTheChallenge README [@github-passthechallenge]: an opaque encrypted payload, a &lt;code&gt;Context Handle&lt;/code&gt; (an opaque RPC handle that identifies the per-logon session inside the trustlet), a &lt;code&gt;Proxy Info&lt;/code&gt; field that points to the protocol-side session metadata in &lt;code&gt;lsass.exe&lt;/code&gt;, and a &lt;code&gt;DPAPI&lt;/code&gt; GUID that ties the encrypted blob to the per-user DPAPI master-key chain.&lt;/p&gt;

The Windows API for protecting per-user secrets at rest. The DPAPI master-key chain is keyed off the user&apos;s password (or NTOWF for Pass-the-Hash-resistant variants), and is the canonical persistence layer for credentials and certificates that need to survive process restarts. In the Credential Guard architecture, the per-user DPAPI keys are themselves derived from material the trustlet has access to; the GUID in the `[LSA Isolated Data]` block links the in-memory trustlet record to the on-disk DPAPI chain.
&lt;p&gt;The four IUM-side methods that matter for NTLM authentication, as documented by Lyak&apos;s Pass-the-Challenge writeup [@lyak-passchallenge-wayback]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;EncryptData&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;DecryptData&lt;/code&gt;&lt;/strong&gt;: the trustlet&apos;s general-purpose AES-GCM wrap and unwrap on opaque blobs, used by every other Credential Guard code path that needs to round-trip a secret through &lt;code&gt;lsass.exe&lt;/code&gt; memory without &lt;code&gt;lsass.exe&lt;/code&gt; ever seeing the cleartext.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NtlmIumProtectCredential&lt;/code&gt;&lt;/strong&gt;: the trustlet entry point that converts an NTOWF supplied by &lt;code&gt;lsass.exe&lt;/code&gt; immediately after a logon (when the user typed the password and &lt;code&gt;msv1_0.dll&lt;/code&gt; derived the NTOWF in VTL0 memory) into the isolated form. After this call returns, the only copy of the NTOWF that survives is inside the trustlet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NtlmIumCalculateNtResponse&lt;/code&gt;&lt;/strong&gt;: the trustlet entry point that computes an NTLMv1 response from the protected NTOWF and a server-supplied challenge. This is the function that gets called every time the user authenticates to an SMB share, an MS-SQL server, an Exchange front-end, or any other NTLM endpoint.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;NtlmIumLm20GetNtlm3ChallengeResponse&lt;/code&gt;&lt;/strong&gt;: the equivalent for NTLMv2.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Pypykatz fork output structure carries the exact byte layout for all of this: &lt;em&gt;Is NT Present&lt;/em&gt;, &lt;em&gt;Context Handle&lt;/em&gt;, &lt;em&gt;Proxy Info&lt;/em&gt;, &lt;em&gt;Encrypted blob&lt;/em&gt;, &lt;em&gt;DPAPI&lt;/em&gt;. The verbatim hex prefix &lt;code&gt;a000000000000000080000006400000001000000010100000100000036&lt;/code&gt; is a length-prefixed serialisation header. The literal string &lt;code&gt;4e746c6d48617368&lt;/code&gt; -- which decodes from hex to the ASCII string &lt;code&gt;NtlmHash&lt;/code&gt; -- is the field-name tag inside the ciphertext. The ciphertext itself is the AES-GCM-wrapped NTOWF; the tag tells you what the cleartext used to be called.The fact that the field-name tag survives the wrap is not a bug. AES-GCM is an authenticated cipher; by construction, its tag is a MAC over the ciphertext, not an obfuscation primitive over the plaintext. The serialiser includes the field name in the structure so that the trustlet can correctly route the request when the agent calls back. The name tag is your verifiable &quot;the encrypted blob really is the hash&quot; artefact.&lt;/p&gt;
&lt;p&gt;{`
// The verbatim hex prefix from the PassTheChallenge README dump example.
// (Full blob is longer; this prefix is the load-bearing identifying header.)
const blobPrefix = &apos;a000000000000000080000006400000001000000010100000100000036&apos;;&lt;/p&gt;
&lt;p&gt;// Inferred field decomposition from the byte pattern of the verbatim
// PassTheChallenge README dump example. The README documents the hex-dump
// shape and the embedded NtlmHash ASCII tag, but does not name the per-byte
// fields; the decomposition below is illustrative.
//   [ProtectionLevel | StructLen | Version | Cipher | TagLen | EncryptedPayload]
// The encrypted payload itself contains an embedded ASCII tag identifying
// the field that was wrapped.&lt;/p&gt;
&lt;p&gt;function parseHeader(hex) {
  const bytes = hex.match(/.{2}/g).map(b =&amp;gt; parseInt(b, 16));
  // Little-endian 64-bit length-like fields; the trustlet uses fixed widths.
  const protectionLevel = bytes[0];                    // 0xa0 in this dump
  const structLen       = bytes.slice(8, 16);          // 8 bytes (LE)
  const version         = bytes[16];                   // 0x01
  const cipher          = bytes[20];                   // 0x01 = AES-GCM
  const tagLen          = bytes[24];                   // 0x01
  return { protectionLevel, structLen, version, cipher, tagLen };
}&lt;/p&gt;
&lt;p&gt;const fields = parseHeader(blobPrefix);
console.log(&apos;ProtectionLevel:&apos;, &apos;0x&apos; + fields.protectionLevel.toString(16));
console.log(&apos;Version       :&apos;, fields.version);
console.log(&apos;Cipher        :&apos;, fields.cipher === 1 ? &apos;AES-GCM (per spec)&apos; : &apos;unknown&apos;);
console.log(&apos;TagLen        :&apos;, fields.tagLen);&lt;/p&gt;
&lt;p&gt;// The literal &apos;4e746c6d48617368&apos; (= ASCII &apos;NtlmHash&apos;) sits inside the ciphertext
// further into the blob. Its presence is the verifiable &apos;this really is the hash&apos;
// signal in the PassTheChallenge dumps.
const ntlmHashTag = Buffer.from(&apos;4e746c6d48617368&apos;, &apos;hex&apos;).toString(&apos;ascii&apos;);
console.log(&apos;Embedded tag  :&apos;, ntlmHashTag);   // -&amp;gt; &apos;NtlmHash&apos;
`}&lt;/p&gt;
&lt;h3&gt;5.4 The &lt;code&gt;LSA_ISO_RPC_SERVER&lt;/code&gt; ALPC port&lt;/h3&gt;
&lt;p&gt;The agent reaches the trustlet through a single secure-call endpoint named &lt;code&gt;LSA_ISO_RPC_SERVER&lt;/code&gt; (terminology per Lyak&apos;s writeup [@lyak-passchallenge-wayback]). The marshalling layer is the IUM Base API. The actual VTL boundary crossing is a hypercall: when a VTL0 thread invokes the secure call, the hypervisor switches the CPU to VTL1, the Secure Kernel inspects the call ordinal, copies the input buffer across the boundary into a VTL1-owned page, and dispatches to the trustlet&apos;s entry point. The reverse path mirrors that step for the return value.&lt;/p&gt;

The undocumented Windows IPC primitive that succeeds the older LPC. ALPC ports support multiple message-passing modes, fast handles, and direct shared-memory regions. In Credential Guard, the agent talks to the trustlet via an ALPC port whose server side is implemented inside the Secure Kernel, so that the IPC delivery path itself crosses the VTL boundary without exposing any VTL1 memory to VTL0. Lyak&apos;s Pass-the-Challenge writeup [@lyak-passchallenge-wayback] names the channel verbatim.
&lt;p&gt;This single endpoint is the entire externally-reachable surface of the trustlet. There is no debugger interface, no driver-load path, no shared-memory region, and no second ALPC port. The trustlet&apos;s code runs only when the agent calls it, and the agent can only call it through one specific channel that the Secure Kernel mediates.&lt;/p&gt;

sequenceDiagram
    participant SRV as Remote SMB server
    participant LSASS as &quot;lsass.exe (VTL0 -- msv1_0.dll)&quot;
    participant SK as &quot;securekernel.exe (VTL1 ring 0)&quot;
    participant ISO as &quot;LsaIso.exe (VTL1 trustlet)&quot;
    SRV-&amp;gt;&amp;gt;LSASS: NTLM challenge (8-byte server challenge)
    LSASS-&amp;gt;&amp;gt;SK: Secure call: NtlmIumCalculateNtResponse(ctxHandle, challenge)
    SK-&amp;gt;&amp;gt;SK: validate ordinal, copy input across VTL boundary
    SK-&amp;gt;&amp;gt;ISO: dispatch (NTOWF retrieved from sealed in-process state)
    ISO-&amp;gt;&amp;gt;ISO: Three-DES against the isolated NTOWF -- NTLMv1 DESL per MS-NLMP 3.3.1
    ISO-&amp;gt;&amp;gt;SK: derived 24-byte NTLMv1 response
    SK-&amp;gt;&amp;gt;LSASS: response (no NTOWF returned)
    LSASS-&amp;gt;&amp;gt;SRV: NTLMv1 response on the wire
&lt;h3&gt;5.5 The &lt;code&gt;MSV1_0\IsolatedCredentialsRootSecret&lt;/code&gt; registry sentinel&lt;/h3&gt;
&lt;p&gt;Microsoft documents one verifiable artefact of default-on Credential Guard activation: the registry value &lt;code&gt;Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0\IsolatedCredentialsRootSecret&lt;/code&gt; [@ms-cg-overview]. Its presence on a Windows 11 22H2+ Pro / Pro Education box is the evidence that default-on activated the feature. A Pro Edu deployment that does not show this value either has Credential Guard explicitly disabled by policy, or sits on hardware that does not meet the requirements (no IOMMU, Secure Boot off, no virtualization extensions in firmware).&lt;/p&gt;
&lt;h3&gt;5.6 TPM binding and the VSM master key&lt;/h3&gt;
&lt;p&gt;Persistent state in Credential Guard is rare. The trustlet does not normally persist the NTOWF or TGT material across reboots; the next user logon re-derives both. When persistence is needed, the data is sealed under what Microsoft calls the &lt;em&gt;VSM master key&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;On recent supported hardware with TPM 2.0, VSM data that is persisted will be protected by a key called the &lt;em&gt;VSM master key&lt;/em&gt;, which is protected by device firmware protections. ... The VSM master key is protected by the TPM, ensuring that the key and secrets protected by Credential Guard can only be accessed in a trusted environment.&quot; [@ms-cg-howitworks]&lt;/p&gt;
&lt;/blockquote&gt;

A symmetric key, generated and stored only in VTL1, that wraps any persistent state the trustlets need to survive reboots. The VSM master key is itself sealed by the TPM under PCR-bound policy, so an attacker who pulls the disk and reboots into a different OS cannot unseal the VSM master key without also reproducing the platform&apos;s pristine measured boot state. See the companion TPM in Windows article [@paragmali-tpm] for the full seal / unseal primitive treatment.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Credential Guard removes the NT kernel from the TCB for the long-lived NTOWF and the Kerberos long-term keys, by moving them into a process whose pages no other VTL can map. The trustlet still answers queries about the keys; what changed is who can touch the bytes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Where the hash physically lives, in 2026, is in pages of &lt;code&gt;LsaIso.exe&lt;/code&gt; that the VTL0 NT kernel cannot map. What an attacker on a default-on Credential Guard box actually sees, what the verification surface for defenders is, and what the operational reality looks like in production is the next question.&lt;/p&gt;
&lt;h2&gt;6. The operational reality of default-on Credential Guard&lt;/h2&gt;
&lt;p&gt;Default-on means specifics. Specifically: every domain-joined Windows 11 22H2+ Enterprise / Education box that meets the hardware requirements has Virtualization-Based Security up, has &lt;code&gt;LsaIso.exe&lt;/code&gt; running, has the registry sentinel at &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0\IsolatedCredentialsRootSecret&lt;/code&gt; written, and reports &quot;Credential Guard&quot; in the running-services bitmask of &lt;code&gt;Win32_DeviceGuard&lt;/code&gt;. Pro and Pro Education boxes are not default-on targets; the special case where a Pro/Pro Edu device previously ran Credential Guard on Enterprise is the one path that lights it up there, and the registry sentinel from §5.5 is precisely how you detect that carry-over case.&lt;/p&gt;
&lt;h3&gt;Default-on scope and the no-UEFI-lock choice&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s Credential Guard overview page [@ms-cg-overview] is precise about scope: Windows 11 22H2 and later (Enterprise, Education), Windows Server 2025, domain-joined non-DC, hardware-eligible (Hyper-V Generation 2 VM with IOMMU on virtual hardware; UEFI Secure Boot, virtualization extensions, IOMMU, and TPM 2.0 on physical hardware). Pro and Pro Education hold the licence entitlement only via the Enterprise-to-Pro carry-over case. The default-on policy ships &quot;without UEFI Lock&quot; [@ms-cg-overview], which is a deliberate trade-off.&quot;Without UEFI Lock&quot; means an administrator can disable Credential Guard remotely (via Group Policy, Intune, or a registry change) without first sending someone to the box&apos;s UEFI menu. The trade-off: an attacker who has already obtained the level of privilege required to write the registry can also undo the same setting. Microsoft chose remote-disable convenience over the in-principle attacker-disable hardening because compatibility incidents -- a rolled-out third-party SSP that breaks under CG -- are an operational reality, and not being able to disable the feature remotely turns an SSP regression into a desk-side support ticket. The overview page [@ms-cg-overview] documents the rationale verbatim.&lt;/p&gt;
&lt;h3&gt;The three supported verification surfaces&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s configuration guide [@ms-cg-configure] names three supported ways to verify Credential Guard is running, and explicitly disrecommends a fourth:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;msinfo32&lt;/code&gt;&lt;/strong&gt;: opens the System Information UI; the line &quot;Virtualization-based Security Services Running&quot; includes &quot;Credential Guard&quot; when the trustlet is up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PowerShell &lt;code&gt;Get-CimInstance Win32_DeviceGuard&lt;/code&gt;&lt;/strong&gt;: returns a &lt;code&gt;SecurityServicesRunning&lt;/code&gt; array whose values are a bitmask.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WinInit Event ID 13&lt;/strong&gt; in the System log: &quot;Credential Guard (LsaIso.exe) was started and will protect LSA credentials.&quot; [@ms-cg-configure]&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The disrecommended approach is &quot;look for &lt;code&gt;LsaIso.exe&lt;/code&gt; in Task Manager.&quot; Microsoft&apos;s words: &quot;Checking Task Manager if LsaIso.exe is running isn&apos;t a recommended method for determining whether Credential Guard is running.&quot; [@ms-cg-configure] Task Manager runs in VTL0 and queries an enumeration that an attacker who controls VTL0 can hide; the three supported surfaces all consult the hypervisor or the boot-time event log, neither of which a VTL0 attacker can edit.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Use the supported surfaces, not Task Manager. The PowerShell one-liner is &lt;code&gt;(Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard).SecurityServicesRunning&lt;/code&gt;. The returned array contains &lt;code&gt;1&lt;/code&gt; when Credential Guard is running; &lt;code&gt;2&lt;/code&gt; denotes Hypervisor-Enforced Code Integrity (HVCI) per the broader &lt;code&gt;Win32_DeviceGuard&lt;/code&gt; schema [@learn-microsoft-com-code-integrity]. The corresponding WinInit Event IDs are 13 (Credential Guard started), 14 (configuration loaded), 15 (warning -- secure kernel not running), 16 (failed to launch), and 17 (UEFI configuration error), per the configuration guide [@ms-cg-configure].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{`
// Mirrors what (Get-CimInstance Win32_DeviceGuard).SecurityServicesRunning returns.
// Microsoft&apos;s Win32_DeviceGuard documentation enumerates the values:
//   1 = Credential Guard
//   2 = Hypervisor-enforced Code Integrity (HVCI / Memory Integrity)
//   3 = System Guard Secure Launch
//   4 = SMM Firmware Measurement
//   5 = Kernel-mode Hardware-enforced Stack Protection
//   6 = Kernel-mode Hardware-enforced Stack Protection (Audit mode)
//   7 = Hypervisor-Enforced Paging Translation
// Note: MBEC (Mode-Based Execution Control) is a CPU capability advertised
// in the SEPARATE AvailableSecurityProperties array, not here.
const SECURITY_SERVICES = {
  1: &apos;Credential Guard&apos;,
  2: &apos;Hypervisor-enforced Code Integrity (HVCI)&apos;,
  3: &apos;System Guard Secure Launch&apos;,
  4: &apos;SMM Firmware Measurement&apos;,
  5: &apos;Kernel-mode Hardware-enforced Stack Protection&apos;,
  6: &apos;Kernel-mode Hardware-enforced Stack Protection (Audit mode)&apos;,
  7: &apos;Hypervisor-Enforced Paging Translation&apos;
};&lt;/p&gt;
&lt;p&gt;function describe(running) {
  if (!running.length) return &apos;No VBS services running&apos;;
  return running.map(v =&amp;gt; SECURITY_SERVICES[v] || (&apos;Unknown service id &apos; + v)).join(&apos;, &apos;);
}&lt;/p&gt;
&lt;p&gt;// On a default-on Windows 11 22H2+ domain-joined Pro/Enterprise box this returns:
console.log(describe([1, 2]));
// =&amp;gt; &quot;Credential Guard, Hypervisor-enforced Code Integrity (HVCI)&quot;&lt;/p&gt;
&lt;p&gt;// On a Windows 10 box without VBS enabled:
console.log(describe([]));
// =&amp;gt; &quot;No VBS services running&quot;
`}&lt;/p&gt;
&lt;h3&gt;What changes for the protocols&lt;/h3&gt;
&lt;p&gt;When Credential Guard is enabled, four SSPs lose the ability to use signed-in credentials: &quot;NTLMv1, MS-CHAPv2, Digest, and CredSSP can&apos;t use the signed-in credentials&quot; [@ms-cg-howitworks]. For NTLMv1 and Digest the practical effect is small (NTLMv1 is end-of-life [@paragmali-ntlmless]; Digest is essentially unused outside legacy HTTP digest authentication). For MS-CHAPv2 and CredSSP the effect is real: any single-sign-on path that depended on those protocols breaks with Credential Guard on. The considerations page [@ms-cg-considerations] calls out PEAP-MSCHAPv2 / EAP-MSCHAPv2 WiFi and VPN configurations explicitly: &quot;If you&apos;re using WiFi and VPN endpoints that are based on MS-CHAPv2, they&apos;re subject to similar attacks as for NTLMv1.&quot; [@ms-cg-considerations] The recommended remediation is to migrate the endpoints to PEAP-TLS / EAP-TLS (certificate-based authentication).&lt;/p&gt;
&lt;p&gt;For Kerberos, Credential Guard &quot;doesn&apos;t allow unconstrained Kerberos delegation or DES encryption, not only for signed-in credentials, but also prompted or saved credentials&quot; [@ms-cg-howitworks]. Constrained Delegation and Resource-Based Constrained Delegation continue to work. The remaining Kerberos &lt;code&gt;etype&lt;/code&gt; choices on the wire on a Credential Guard box are AES-128 and AES-256.&lt;/p&gt;
&lt;h3&gt;What doesn&apos;t change&lt;/h3&gt;
&lt;p&gt;The agent surface still exposes every SSP that loads inside &lt;code&gt;lsass.exe&lt;/code&gt;. The trustlet isolates the secret the SSP uses; it does not isolate the &lt;em&gt;parser&lt;/em&gt; that the SSP runs against an attacker-controlled wire format. A bug in &lt;code&gt;msv1_0.dll&lt;/code&gt;&apos;s NTLM parser is exactly as exploitable on a 2026 Credential-Guard-on box as it was on a 2015 Credential-Guard-off box. The trustlet does not guard the agent; the trustlet guards the key.&lt;/p&gt;

A VBS-based feature that uses the hypervisor&apos;s SLAT enforcement to ensure that any kernel-mode page that is executable is also signed and immutable, and any writable kernel-mode page is non-executable. HVCI closes the kernel-driver-loader leg of the BYOVD / PPL bypass class for *unsigned* drivers, but it does not close BYOVD against a signed-but-vulnerable driver. HVCI is orthogonal to Credential Guard; the overview page [@ms-cg-overview] recommends running both.
&lt;p&gt;Credential Guard is on; the surface is documented; the verification is one PowerShell line. So what other things claim to &quot;protect LSASS,&quot; and how do they fit together with Credential Guard?&lt;/p&gt;
&lt;h2&gt;7. The other things that &quot;protect LSASS&quot;&lt;/h2&gt;
&lt;p&gt;Six other things in the Microsoft security stack get called &quot;LSASS protection&quot; in someone&apos;s marketing. None of them is a substitute for Credential Guard. Most of them are complements. The difference matters because the choice between them is not a choice; the answer is &lt;em&gt;all of them, layered&lt;/em&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Enforcement TCB&lt;/th&gt;
&lt;th&gt;Attacker bar to defeat&lt;/th&gt;
&lt;th&gt;Residual class it leaves open&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;LSA Protection (&lt;code&gt;RunAsPPL&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;NT kernel (signer-level lattice)&lt;/td&gt;
&lt;td&gt;Signed kernel driver (BYOVD via &lt;code&gt;mimidrv.sys&lt;/code&gt; [@itm4n-runasppl]); userland on legacy via PPLdump [@github-ppldump]&lt;/td&gt;
&lt;td&gt;Trustlet RPC outputs; non-LSA process credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credential Guard / &lt;code&gt;LsaIso.exe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hypervisor + Secure Kernel + VTL1 trustlet&lt;/td&gt;
&lt;td&gt;Hypervisor escape; VTL1 code-execution bug&lt;/td&gt;
&lt;td&gt;Pass-the-Challenge [@lyak-passchallenge-wayback]; credential &lt;em&gt;use&lt;/em&gt;; tokens; supplied creds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HVCI / Memory Integrity&lt;/td&gt;
&lt;td&gt;Hypervisor-enforced kernel page W^X&lt;/td&gt;
&lt;td&gt;Signed-and-vulnerable driver that does not load arbitrary unsigned code&lt;/td&gt;
&lt;td&gt;Kernel-mode logic bugs in signed drivers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defender for Identity LSASS read-monitoring [@ms-defender-identity]&lt;/td&gt;
&lt;td&gt;Behavioural detection (no TCB)&lt;/td&gt;
&lt;td&gt;Stealth tradecraft that does not trip the canonical signatures&lt;/td&gt;
&lt;td&gt;Anything not yet patterned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hello for Business&lt;/td&gt;
&lt;td&gt;Per-device TPM-bound asymmetric key (no shared secret on the wire)&lt;/td&gt;
&lt;td&gt;TPM compromise; on-device keylogger before sign-in&lt;/td&gt;
&lt;td&gt;Not a substitute -- it is what CG protects on cloud-joined boxes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restricted Admin / Protected Users&lt;/td&gt;
&lt;td&gt;Protocol-level credential-delegation suppression&lt;/td&gt;
&lt;td&gt;Per-protocol; does not move where the secret lives&lt;/td&gt;
&lt;td&gt;Everything Credential Guard already covers, plus the four-hour TGT cap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;LSA Protection&apos;s kernel-driver-loader bypass class is closed by HVCI for unsigned drivers but not for signed-and-vulnerable ones. Defender for Identity is a detection layer, not a TCB boundary. Hello for Business [@paragmali-com-hellos-hardwar] replaces the password with a TPM-bound asymmetric key [@ms-hello-business]: the Hello for Business overview [@ms-hello-business] row in the Security comparison table reads &quot;It uses &lt;strong&gt;key-based&lt;/strong&gt; or &lt;strong&gt;certificate-based&lt;/strong&gt; authentication. There&apos;s no symmetric secret (password) which can be stolen from a server or phished from a user and used remotely.&quot; The Microsoft Entra ID Primary Refresh Token (PRT) inside &lt;code&gt;cloudap.dll&lt;/code&gt; is the cloud-joined analogue of the on-prem trustlet model: the PRT itself is protected by the trustlet on Credential-Guard-enabled boxes, with Hello as the per-device long-term key chain. Restricted Admin and Protected Users [@ms-protected-users] suppress credential delegation at the protocol layer; on a Credential-Guard-on box they are &lt;em&gt;additionally&lt;/em&gt; effective because they remove the prompt path, but they are not a substitute for the storage-isolation primitive.&lt;/p&gt;

The structural model differs in interesting ways across general-purpose desktop operating systems. macOS uses the Apple Secure Enclave [@apple-secure-enclave]: a separate coprocessor &quot;isolated from the main processor&quot; running an Apple-customised L4 microkernel, with its own attestation chain and a constrained API surface that does not require a &quot;secure call&quot; from the application processor to be tunnelled through a trusted broker. Linux relies on the in-process Kerberos credential cache and per-user keyrings (KCM [@sssd-kcm], GNOME Keyring, KWallet); none of these provide kernel-bypass isolation by default, and the equivalent of &quot;dump LSASS&quot; is &quot;dump the user&apos;s keyring file plus the per-user master key from `~/.local/share/`.&quot; ChromeOS uses cryptohome [@chromium-cryptohome] plus per-user U2F keys, structurally close to the Hello-for-Business model. Windows is the only general-purpose desktop OS that combines a TPM-bound long-term key (Hello), a hypervisor-isolated derived-secret store (Credential Guard / LsaIso), and a behavioural detection layer (Defender for Identity). It is also the only one that accumulated the largest deployed base of password-equivalent secrets in process memory before it found the architectural answer.
&lt;p&gt;Credential Guard closes the storage class. Layering closes the adjacent classes. But there are five classes the layers cannot close -- five things Credential Guard was never going to close, by documented design. The next section enumerates each one.&lt;/p&gt;
&lt;h2&gt;8. The five things Credential Guard was never going to close&lt;/h2&gt;
&lt;p&gt;Microsoft&apos;s own &lt;em&gt;How Credential Guard works&lt;/em&gt; [@ms-cg-howitworks] page lists what Credential Guard &lt;em&gt;does not&lt;/em&gt; protect, in plain English. The list has five classes. Each class has a publicly disclosed worked example. Each worked example is in 2026 production attacker tradecraft. This is the honest accounting.&lt;/p&gt;
&lt;h3&gt;8.1 Pass-the-Challenge: the trustlet&apos;s RPC output as the new attack surface&lt;/h3&gt;
&lt;p&gt;On December 26, 2022, Oliver Lyak published Pass-the-Challenge [@lyak-passchallenge-wayback]. The technique is exactly the lesson of §5: the trustlet&apos;s pages are unreadable, but the trustlet&apos;s RPC output is exactly the response the attacker wants, and the attacker can ask for it.&lt;/p&gt;
&lt;p&gt;The attack flow, end to end:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The attacker has SYSTEM and &lt;code&gt;SeDebugPrivilege&lt;/code&gt; on a Credential-Guard-on box (any of the bypass paths from §3 still apply for getting to that point; Credential Guard does not change them).&lt;/li&gt;
&lt;li&gt;The attacker injects a security-package DLL named &lt;code&gt;SecurityPackage.dll&lt;/code&gt; (per the PassTheChallenge tool&apos;s source [@github-passthechallenge]) into &lt;code&gt;lsass.exe&lt;/code&gt;. Inside &lt;code&gt;lsass.exe&lt;/code&gt;, that DLL inherits the established ALPC channel to the trustlet, because it is now part of the agent.&lt;/li&gt;
&lt;li&gt;The attacker uses the Pypykatz fork [@github-pypykatz-ly4k] to extract the per-logon &lt;code&gt;Context Handle&lt;/code&gt; and &lt;code&gt;Proxy Info&lt;/code&gt; from the &lt;code&gt;[LSA Isolated Data]&lt;/code&gt; block of an existing user session.&lt;/li&gt;
&lt;li&gt;The attacker calls the trustlet&apos;s &lt;code&gt;NtlmIumCalculateNtResponse&lt;/code&gt; method through the established ALPC channel, supplying the &lt;code&gt;Context Handle&lt;/code&gt; and &quot;the static challenge &lt;code&gt;1122334455667788&lt;/code&gt;&quot; [@github-passthechallenge], the value historically used in pre-computed NTLMv1 rainbow tables and accepted by the &lt;code&gt;crack.sh&lt;/code&gt; [@lyak-passchallenge-wayback] cracking service.&lt;/li&gt;
&lt;li&gt;The trustlet faithfully returns the NTLMv1 response. No memory of the trustlet is read. No bug in the trustlet is exploited. The trustlet does what it was built to do.&lt;/li&gt;
&lt;li&gt;The attacker submits the response to &lt;code&gt;crack.sh&lt;/code&gt;. &quot;In less than a minute, I received an email from crack.sh stating that the NTLM hash was successfully recovered in 30 seconds: &lt;code&gt;65A13AB2FAEB5B700DE1A938AE5621CA&lt;/code&gt;.&quot; [@lyak-passchallenge-wayback]&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is also a v2 variant. Lyak: &quot;Another interesting option is to compute an NTLMv2 response using the LSAIso method &lt;code&gt;NtlmIumLm20GetNtlm3ChallengeResponse&lt;/code&gt;.&quot; [@lyak-passchallenge-wayback] The v2 variant uses an Impacket modification plus an AD CS [@lyak-passchallenge-wayback] Web Enrollment relay to obtain a certificate for the target user (Certipy&apos;s &lt;code&gt;Administrator&lt;/code&gt; certificate-based authentication path) without needing the cleartext NT hash at all.&lt;/p&gt;

sequenceDiagram
    participant ATT as Attacker (SYSTEM)
    participant LSASS as &quot;lsass.exe (VTL0) -- SecurityPackage.dll injected&quot;
    participant SK as &quot;securekernel.exe (VTL1 ring 0)&quot;
    participant ISO as &quot;LsaIso.exe (VTL1 trustlet)&quot;
    participant CRACK as crack.sh
    ATT-&amp;gt;&amp;gt;LSASS: Inject SecurityPackage.dll
    ATT-&amp;gt;&amp;gt;LSASS: Extract Context Handle from Pypykatz dump
    LSASS-&amp;gt;&amp;gt;SK: NtlmIumCalculateNtResponse(ctxHandle, 1122334455667788)
    SK-&amp;gt;&amp;gt;ISO: dispatch (signed-and-attested call)
    ISO-&amp;gt;&amp;gt;SK: 24-byte NTLMv1 response
    SK-&amp;gt;&amp;gt;LSASS: response
    LSASS-&amp;gt;&amp;gt;ATT: response (no NTOWF, just the ciphertext)
    ATT-&amp;gt;&amp;gt;CRACK: submit NTLMv1 ciphertext for 1122334455667788
    CRACK-&amp;gt;&amp;gt;ATT: NT hash recovered in ~30 seconds
&lt;p&gt;Microsoft&apos;s response landed in two phases. First, NTLMv1 was deprecated and disabled by default in Windows 11 24H2 / Server 2025 [@paragmali-ntlmless], which removes the &lt;code&gt;crack.sh&lt;/code&gt; rainbow-table leg specifically. Second, the trustlet stopped accepting NTLMv1 calls on the same builds. The &lt;em&gt;class&lt;/em&gt; -- &quot;use the trustlet to mint derived material&quot; -- remains structural to the agent / trustlet split, because closing it requires removing either the agent&apos;s ability to call the trustlet (which would defeat single-sign-on) or the attacker&apos;s ability to compromise the agent (which is the point of every other layer in the stack).&lt;/p&gt;

Pass-the-Challenge is not a Microsoft bug. It is a class property of any agent / trustlet split where the agent owns the protocol code. If `lsass.exe` could not call the trustlet, the trustlet would be useless: there would be no path from the wire challenge to a response. If `lsass.exe` *can* call the trustlet, then an attacker who compromises `lsass.exe` can call it too. Closing this gap structurally requires rewriting the SSP loading model so that protocol code, too, runs inside the trustlet -- which would put parsers for arbitrary attacker-controlled wire formats inside VTL1 and dramatically expand the trustlet TCB. Microsoft has not announced an intent to do that. The honest read of the architecture is that the storage surface is closed and the use surface is structurally open.
&lt;h3&gt;8.2 Credential &lt;em&gt;use&lt;/em&gt; without theft&lt;/h3&gt;
&lt;p&gt;Three named techniques in 2026 production tradecraft do not require reading the memory of any Credential-Guard-protected machine. They request derived material from the network and do offline cryptography on the response.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kerberoasting.&lt;/strong&gt; Tim Medin disclosed Kerberoasting at DerbyCon 4 in September 2014 [@irongeek-derbycon-medin] under the talk title &lt;em&gt;Attacking Microsoft Kerberos: Kicking the Guard Dog of Hades&lt;/em&gt;. The mechanism, per the MITRE ATT&amp;amp;CK technique page [@attack-kerberoast]: any authenticated domain user requests a TGS-REP ticket for any registered Service Principal Name. &quot;Portions of these tickets may be encrypted with the RC4 algorithm, meaning the Kerberos 5 TGS-REP etype 23 hash of the service account associated with the SPN is used as the private key and is thus vulnerable to offline Brute Force attacks that may expose plaintext credentials.&quot; [@attack-kerberoast] The ticket arrives on the attacker&apos;s machine; the cracking happens on the attacker&apos;s GPUs; no memory of any Credential-Guard-protected box is ever read.&lt;/p&gt;

The class of attack in which any authenticated domain user requests a Kerberos TGS-REP ticket for any registered Service Principal Name and submits the encrypted portion of the response to offline cracking, recovering the service-account password if it is weak. Documented as MITRE ATT&amp;amp;CK T1558.003 [@attack-kerberoast]. Kerberoasting reads no memory of the targeted host; it consumes only network responses to entirely-legitimate Kerberos requests.
&lt;p&gt;&lt;strong&gt;AS-REP Roasting.&lt;/strong&gt; The same class for accounts with &lt;code&gt;DONT_REQ_PREAUTH&lt;/code&gt; set [@attack-asreproast]: the attacker requests a Kerberos AS-REP without sending preauthentication, the KDC returns a ticket portion encrypted with the user&apos;s long-term key, and the attacker cracks offline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resource-Based Constrained Delegation (RBCD).&lt;/strong&gt; Originally described by Elad Shamir in &lt;em&gt;Wagging the Dog&lt;/em&gt; (January 2019) [@shenanigans-wagging-dog]; refined by James Forshaw&apos;s &quot;Exploiting RBCD using a normal user&quot; (May 2022) [@tiraniddo-rbcd]; turned into a turnkey LPE by Dec0ne&apos;s KrbRelayUp (2022) [@github-krbrelayup], which the README describes -- accurately -- as &quot;essentially a universal no-fix local privilege escalation in windows domain environments where LDAP signing is not enforced (the default settings).&quot; [@github-krbrelayup] The attack abuses the &lt;code&gt;msDS-AllowedToActOnBehalfOfOtherIdentity&lt;/code&gt; LDAP attribute: if the attacker can write that attribute on a target computer object, they can mint a Kerberos service ticket &lt;em&gt;as anyone&lt;/em&gt; against the target. Forshaw&apos;s 2022 contribution removed the precondition that the attacker must control a computer account (it used to require a &lt;code&gt;MachineAccountQuota&lt;/code&gt;-bypass); after Forshaw, &lt;em&gt;any&lt;/em&gt; authenticated domain user with write access to the attribute is enough.&lt;/p&gt;

A Kerberos delegation feature in which the resource (server) lists which principals are allowed to delegate to it via the `msDS-AllowedToActOnBehalfOfOtherIdentity` LDAP attribute on the resource&apos;s computer object. RBCD enables S4U2Self and S4U2Proxy chains where the configured principal can request a service ticket *as any user* against the resource, including Domain Administrators. Abuse documented in Wagging the Dog [@shenanigans-wagging-dog], refined in tiraniddo.dev [@tiraniddo-rbcd], and weaponised in KrbRelayUp [@github-krbrelayup].
&lt;h3&gt;8.3 The SeImpersonatePrivilege Potato chain&lt;/h3&gt;
&lt;p&gt;The Potato family [@paragmali-com-on-the] is a chain of escalations from a low-privilege service user (anyone with &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; or &lt;code&gt;SeAssignPrimaryTokenPrivilege&lt;/code&gt;) to NT AUTHORITY\SYSTEM. The chain starts with Hot Potato (Stephen Breen, January 16, 2016) [@foxglove-hot-potato]: NBNS spoofing plus WPAD plus HTTP-to-SMB NTLM relay. The &lt;code&gt;breenmachine&lt;/code&gt; writeup [@foxglove-hot-potato] is verbatim: &quot;Hot Potato (aka: Potato) takes advantage of known issues in Windows to gain local privilege escalation in default configurations, namely NTLM relay (specifically HTTP-&amp;gt;SMB relay) and NBNS spoofing.&quot; [@foxglove-hot-potato]&lt;/p&gt;
&lt;p&gt;Rotten Potato (Breen + Chris Mallz, September 26, 2016) [@foxglove-rotten-potato] replaced the NBNS / WPAD / WSUS triggering with a synthesised DCOM round-trip via &lt;code&gt;CoGetInstanceFromIStorage&lt;/code&gt; against the BITS CLSID &lt;code&gt;4991d34b-80a1-4291-83b6-3328366b9097&lt;/code&gt; over a local TCP port, achieving 100% reliability across Windows versions. Juicy Potato (Andrea Pierini &lt;code&gt;@ohpe&lt;/code&gt; + Claudio Tenaglia &lt;code&gt;@decoder_it&lt;/code&gt;, August 2018) [@github-juicy-potato] made the CLSID a parameter, dropping the BITS-on-port-6666 hardcoding. PrintSpoofer (itm4n, May 2, 2020) [@itm4n-printspoofer] replaced the DCOM coercion with a Print Spooler named-pipe coercion, surviving Microsoft&apos;s Server 2019 / Windows 10 19H1 mitigations against the DCOM-based predecessors.&lt;/p&gt;

The Windows token privilege that allows a process to call `ImpersonateLoggedOnUser` against a token obtained from another security context. Service accounts (`NT AUTHORITY\NETWORK SERVICE`, IIS app pool identities, MSSQL service accounts) hold this privilege by default. As `decoder_it` first observed [@itm4n-printspoofer], if you have `SeAssignPrimaryToken` or `SeImpersonate` privilege, you are SYSTEM: combine it with a coerced inbound NTLM authentication from `NT AUTHORITY\SYSTEM` to a local listener, and `CreateProcessWithToken` finishes the chain.
&lt;p&gt;The Potato chain exploits &lt;em&gt;tokens&lt;/em&gt;, not credentials. The chain of Hot / Rotten / Juicy / PrintSpoofer / RoguePotato / GodPotato has been continuous since January 2016 because every link in the chain abuses a Windows OS feature (DCOM marshalling, RPC, named-pipe impersonation, Print Spooler, COM activation through alternative interfaces) that has a legitimate use case Microsoft cannot remove. Credential Guard does not protect tokens; Credential Guard protects credentials.&lt;/p&gt;
&lt;h3&gt;8.4 Plaintext-secret protocols and supplied credentials&lt;/h3&gt;
&lt;p&gt;&quot;When Credential Guard is enabled, NTLMv1, MS-CHAPv2, Digest, and CredSSP can&apos;t use the signed-in credentials&quot; [@ms-cg-howitworks], but they &lt;em&gt;can&lt;/em&gt; still be used with prompted or saved credentials. In every such case the cleartext password (or a symmetric secret derived from it) is supplied to &lt;code&gt;lsass.exe&lt;/code&gt; from outside the trustlet, so the trustlet has nothing to protect at the moment of use.&lt;/p&gt;
&lt;p&gt;The considerations page [@ms-cg-considerations] names PEAP-MSCHAPv2 / EAP-MSCHAPv2 WiFi and VPN configurations as the most consequential remaining surface in 2026: a corporate WiFi or VPN endpoint that authenticates users with MS-CHAPv2 still cracks under the same offline tradecraft as the original NTLMv1 attacks, because the protocol itself uses MD4 + DES against the user&apos;s NT hash. The recommendation: &quot;organizations move away from passwords to other authentication methods, such as Windows Hello for Business, FIDO 2 security keys, or smart cards&quot; [@ms-cg-considerations], or migrate the WiFi / VPN endpoint to certificate-based PEAP-TLS / EAP-TLS.&lt;/p&gt;
&lt;h3&gt;8.5 Out-of-LSA credential storage&lt;/h3&gt;
&lt;p&gt;Four storage locations are out of scope for Credential Guard by Microsoft&apos;s own design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generic Credential Manager entries&lt;/strong&gt; -- web passwords, browser-stored credentials, the Windows Credential Manager&apos;s &quot;Web Credentials&quot; tab. &quot;Generic credentials, such as user names and passwords that you use to sign in websites, aren&apos;t protected since the applications require your clear-text password.&quot; [@ms-cg-considerations]&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-Microsoft Security Support Providers.&lt;/strong&gt; &quot;Some non-Microsoft Security Support Providers (SSPs and APs) might not be compatible with Credential Guard because it doesn&apos;t allow non-Microsoft SSPs to ask for password hashes from LSA. ... For example, using the KerbQuerySupplementalCredentialsMessage API isn&apos;t supported.&quot; [@ms-cg-considerations] Third-party SSPs that depend on hash retrieval through that API simply break under Credential Guard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Active Directory database on domain controllers.&lt;/strong&gt; &quot;Credential Guard doesn&apos;t protect the Active Directory database running on Windows Server domain controllers.&quot; [@ms-cg-howitworks] The most-attacked LSASS on the network -- &lt;code&gt;lsass.exe&lt;/code&gt; on the domain controller, holding &lt;code&gt;NTDS.dit&lt;/code&gt; and the &lt;code&gt;krbtgt&lt;/code&gt; long-term key -- is explicitly out of Credential Guard&apos;s scope. Microsoft&apos;s stated rationale is that domain controllers do not benefit from the same isolation, because the entire AD database is, by design, available to the LSA process on a DC.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential-input pipelines such as Remote Desktop Gateway and Just-In-Time admin access tooling&lt;/strong&gt;, where the typed cleartext is supplied to &lt;code&gt;lsass.exe&lt;/code&gt; over an inbound network protocol and is in clear at the moment of arrival.&lt;/li&gt;
&lt;/ul&gt;

Doesn&apos;t prevent an attacker with malware on the PC from using the privileges associated with any credential. We recommend using dedicated PCs for high value accounts. -- Microsoft Learn, *How Credential Guard works* [@ms-cg-howitworks]

flowchart LR
    CG[&quot;What CG closes:&lt;br /&gt;Long-term key in lsass.exe memory&quot;]
    R1[&quot;Pass-the-Challenge:&lt;br /&gt;trustlet RPC output&lt;br /&gt;(Lyak, Dec 2022)&quot;]
    R2[&quot;Credential use without theft:&lt;br /&gt;Kerberoast, AS-REP Roast,&lt;br /&gt;RBCD / KrbRelayUp&quot;]
    R3[&quot;Token impersonation:&lt;br /&gt;Hot/Rotten/Juicy/PrintSpoofer&lt;br /&gt;SeImpersonatePrivilege&quot;]
    R4[&quot;Plaintext protocols:&lt;br /&gt;NTLMv1, MS-CHAPv2, Digest,&lt;br /&gt;CredSSP&quot;]
    R5[&quot;Out-of-LSA storage:&lt;br /&gt;Web creds, non-MS SSPs,&lt;br /&gt;NTDS.dit on DCs&quot;]
    CG -.does not close.-&amp;gt; R1
    CG -.does not close.-&amp;gt; R2
    CG -.does not close.-&amp;gt; R3
    CG -.does not close.-&amp;gt; R4
    CG -.does not close.-&amp;gt; R5
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The trustlet is the storage layer; the agent is the use layer; an attacker who controls the agent can request derived material the trustlet was never going to refuse. This is the structural reason Credential Guard was never going to close the use surface.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Five classes documented; five worked examples named. What is &lt;em&gt;not yet&lt;/em&gt; documented -- the open problems where the research is still in progress -- is what the next section walks.&lt;/p&gt;
&lt;h2&gt;9. Open problems&lt;/h2&gt;
&lt;p&gt;Five things the Credential Guard architecture has not yet closed. One of them is structural; four are deployment frontiers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9.1 Trustlet IUM API surface fuzzing.&lt;/strong&gt; Pass-the-Challenge proved one corner of the agent-callable RPC surface is exploitable when fed inputs the developer did not anticipate (the static &lt;code&gt;1122334455667788&lt;/code&gt; challenge whose responses are pre-computed in commercial cracking tables). The systematic audit of every IUM API entry point -- there are not many; the trustlet is small -- has not been published. A blue-team-friendly fuzzer that exercises the channel from a controlled VTL0 agent against a controlled VTL1 target is on the public to-do list of several research groups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9.2 Domain-controller LSASS / &lt;code&gt;NTDS.dit&lt;/code&gt; / &lt;code&gt;krbtgt&lt;/code&gt; protection.&lt;/strong&gt; Microsoft documents the DC carve-out as out of scope [@ms-cg-howitworks]. An architectural fix would require a DC-resident trustlet model that can answer Kerberos AS-REP and TGS-REP queries against the entire &lt;code&gt;NTDS.dit&lt;/code&gt; without compromising AD replication semantics. That model is not on the public roadmap, and the practical recommendation -- the dedicated-Tier-0-PAW model from the &lt;em&gt;Mitigating Pass-the-Hash&lt;/em&gt; v2 playbook [@ms-lsa-protection] -- still applies in 2026.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9.3 TGT and service-ticket lifetime in &lt;code&gt;lsass.exe&lt;/code&gt; after the trustlet mints them.&lt;/strong&gt; Pass-the-Ticket on the agent recovers the &lt;em&gt;current&lt;/em&gt; TGT or service ticket from &lt;code&gt;lsass.exe&lt;/code&gt; memory. Credential Guard isolates the long-term key; it does not isolate the per-session derived material that the agent has to hold to send on the wire. The trustlet&apos;s TGT protection [@ms-cg-howitworks] is verbatim: the long-term-key path is closed, the service-ticket path is not. A 2026 attacker with &lt;code&gt;SeDebugPrivilege&lt;/code&gt; who dumps &lt;code&gt;lsass.exe&lt;/code&gt; recovers the &lt;em&gt;Kerberos service tickets&lt;/em&gt; even though they cannot recover the underlying NTOWF.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9.4 Pass-the-Cookie / token-lift class against derived material.&lt;/strong&gt; Microsoft Entra Primary Refresh Token (PRT) [@ms-entra-prt] cookies issued by the trustlet to the agent become bearer tokens until the session ends. Per-token device binding raises the bar (the cookie is bound to a TPM-bound device key, so use of the cookie outside the device is detectable by the cloud), but it does not close the class for an attacker who has on-device persistence and can replay the cookie from the same device.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9.5 Compatibility and observability frontier.&lt;/strong&gt; The third-party SSP / MS-CHAPv2 / CredSSP behaviour-change surface keeps showing up in real-estate compatibility reports. Microsoft&apos;s &lt;em&gt;Considerations&lt;/em&gt; page [@ms-cg-considerations] is updated routinely; the practical operational pattern in 2026 is &quot;enable Credential Guard via Intune in audit mode for 30 days, harvest the compatibility errors, fix or replace the affected SSPs, then promote to enforce.&quot; That pattern is now well-trodden but the per-estate inventory is real work.&lt;/p&gt;
&lt;p&gt;Open problems are interesting; daily practice is more interesting. What does a 2026 administrator, researcher, red-team operator, and detection engineer actually do with Credential Guard?&lt;/p&gt;
&lt;h2&gt;10. Practical guide&lt;/h2&gt;
&lt;p&gt;Four audiences; four operational checklists. Each is short because each builds on a section we have already walked.&lt;/p&gt;
&lt;h3&gt;For an administrator or platform engineer&lt;/h3&gt;
&lt;p&gt;Verify Credential Guard is running using the three supported surfaces [@ms-cg-configure]: &lt;code&gt;(Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard).SecurityServicesRunning&lt;/code&gt; should contain &lt;code&gt;1&lt;/code&gt;; &lt;code&gt;msinfo32&lt;/code&gt; should list &quot;Credential Guard&quot; under Virtualization-based Security Services Running; the System event log should show WinInit Event 13. Deploy via Intune Settings Catalog or GPO with &quot;Enabled without lock&quot; [@ms-cg-configure]. Inventory NTLMv1, MS-CHAPv2, Digest, CredSSP, and non-Microsoft SSP usage &lt;em&gt;before&lt;/em&gt; enabling, because those are the protocols that will lose SSO under Credential Guard.&lt;/p&gt;

On a Credential-Guard-on box, the following one-liner returns `True`:&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;(Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard).SecurityServicesRunning -contains 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;1&lt;/code&gt; corresponds to Credential Guard in the &lt;code&gt;SecurityServicesRunning&lt;/code&gt; bitmask [@ms-cg-configure]. Pair with the System event log filter &lt;code&gt;EventID=13, Source=Wininit&lt;/code&gt; to confirm the boot-time launch event. Use these to verify, not Task Manager: Microsoft explicitly disrecommends the Task Manager check.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &quot;Credential Guard should be enabled before a device is joined to a domain or before a domain user signs in for the first time. If Credential Guard is enabled after domain join, the user and device secrets may already be compromised.&quot; [@ms-cg-configure] On a default-on Windows 11 22H2+ deployment this is automatic; on legacy estates being migrated, it requires a re-image cycle.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;For a security researcher&lt;/h3&gt;
&lt;p&gt;The verifiable trustlet artefacts are: the &lt;code&gt;.tpolicy&lt;/code&gt; PE section in &lt;code&gt;LsaIso.exe&lt;/code&gt;; the &lt;code&gt;s_IumPolicyMetadata&lt;/code&gt; export; the dual-EKU signature with the IUM EKU 1.3.6.1.4.1.311.10.3.37 visible in the certificate chain. The IUM-side enumeration approach is &lt;code&gt;NtQuerySystemInformation&lt;/code&gt; with the &lt;code&gt;SystemIsolatedUserModeInformation&lt;/code&gt; class. Quarkslab&apos;s IUM-debugging walkthrough [@quarkslab-falcon-ium] documents the nested-virt setup (VMware L1 + Hyper-V L2), the GDB-stub attach on &lt;code&gt;hvix64.exe&lt;/code&gt;, the patch on &lt;code&gt;SecureKernel!SkpsIsProcessDebuggingEnabled&lt;/code&gt; to force-return 1, and the walk to &lt;code&gt;SecureKernel.exe&lt;/code&gt; from &lt;code&gt;HvCallVtlReturn&lt;/code&gt; at hypercall ID 0x12. Lyak&apos;s PassTheChallenge methodology [@github-passthechallenge] is the canonical worked example for the agent-side trustlet RPC interaction.&lt;/p&gt;
&lt;h3&gt;For a red-team operator&lt;/h3&gt;
&lt;p&gt;Assume the long-term hash and TGT are not in &lt;code&gt;lsass.exe&lt;/code&gt;. Assume the trustlet&apos;s RPC output is. The 2026 LPE / credential playbook focuses on credential &lt;em&gt;use&lt;/em&gt; (Kerberoast against weak service-account passwords; AS-REP Roast against accounts with &lt;code&gt;DONT_REQ_PREAUTH&lt;/code&gt;; RBCD via KrbRelayUp [@github-krbrelayup]; AD CS misconfigurations -- the ESC1-ESC11 enumeration), token impersonation (the PrintSpoofer / Potato chain [@itm4n-printspoofer]), and supplied-credential capture (keylogger on the prompt path). The companion NTLMless article in this series [@paragmali-ntlmless] covers the protocol-removal frontier that closes the Pass-the-Challenge NTLMv1 leg specifically.&lt;/p&gt;
&lt;h3&gt;For a detection engineer&lt;/h3&gt;
&lt;p&gt;WinInit Events 15, 16, and 17 (&quot;Credential Guard configured but did not run&quot;) are a high-fidelity &quot;attacker disabled Credential Guard&quot; detection -- if the box is supposed to be Credential-Guard-on and the boot-time event logs show one of those IDs, something blocked the trustlet from launching. ETW Microsoft-Antimalware-Engine plus AMSI [@ms-amsi] on &lt;code&gt;lsass.exe&lt;/code&gt; security-package load is the high-fidelity detection surface for Pass-the-Challenge-style injection (the attacker has to load a security-package DLL into &lt;code&gt;lsass.exe&lt;/code&gt; to get the established ALPC channel). On the network side, a &lt;code&gt;crack.sh&lt;/code&gt; submission carrying the static &lt;code&gt;1122334455667788&lt;/code&gt; challenge is an indicator of Pass-the-Challenge cracking activity by an internal user.&lt;/p&gt;
&lt;p&gt;Three misconceptions about Credential Guard get asked in every defensive-architecture review. The FAQ that follows resolves them, with primary citations for each answer.&lt;/p&gt;
&lt;h2&gt;11. Frequently asked questions&lt;/h2&gt;
&lt;p&gt;These are the seven questions that come up in every Credential Guard architectural review. Each answer is grounded in a primary source.&lt;/p&gt;

No. The *long-lived* NTLM hash and the Kerberos *long-term key* are unstealable from VTL0 memory; the per-session Kerberos service tickets are not protected, though the TGT is [@ms-cg-howitworks]. Pass-the-Ticket on the agent recovers the current session&apos;s tickets even on a Credential-Guard-on box. The architectural pivot is &quot;long-lived secret out of the agent&apos;s memory,&quot; not &quot;no secret in the agent&apos;s memory.&quot;

Yes. The dump succeeds; the contents are different. Where the NT hash field used to hold the bytes of the hash, [the field now holds the literal string `[LSA Isolated Data]` followed by an opaque ciphertext](https://github.com/ly4k/PassTheChallenge), with embedded `Context Handle`, `Proxy Info`, and `DPAPI` GUID fields. The encrypted blob is AES-GCM-wrapped by the trustlet under a key the agent does not hold. The dump is a statement of architecture, not a statement of failure.

No. Kerberoasting [@attack-kerberoast] cracks the service account&apos;s NT hash from a TGS-REP delivered over the wire by the KDC. No memory of the Credential-Guard-protected box is ever read. The mitigation for Kerberoasting is strong service-account passwords (or moving to managed service accounts with auto-rotated 240-character passwords), not Credential Guard.

No. The Potato chain [@itm4n-printspoofer] (Hot, Rotten, Juicy, PrintSpoofer, RoguePotato, GodPotato) escalates from a service user with `SeImpersonatePrivilege` to NT AUTHORITY\SYSTEM by impersonating a coerced inbound SYSTEM authentication. The chain operates on *tokens*, not *credentials*. Credential Guard protects credentials.

No, by design. Microsoft documents the carve-out verbatim: &quot;Enabling Credential Guard on domain controllers isn&apos;t recommended. Credential Guard doesn&apos;t provide any added security to domain controllers, and can cause application compatibility issues on domain controllers.&quot; [@ms-cg-overview] The most-attacked LSASS on the network -- on the domain controller, holding `NTDS.dit` and the `krbtgt` long-term key -- is explicitly out of scope. The mitigations for DC LSASS are physical and procedural: dedicated Tier-0 Privileged Access Workstations, no general-purpose interactive logon, and the *Mitigating Pass-the-Hash* v2 playbook [@ms-lsa-protection].

No. PPL is kernel-enforced inside the NT TCB; Credential Guard is a VTL1 trustlet outside it. itm4n&apos;s writeup is explicit: &quot;LSA Protection tends to be confused with Credential Guard, which is completely different ... Credential Guard and LSA Protection are actually complementary.&quot; [@itm4n-runasppl] Both should be enabled. PPL raises the bar for opportunistic attackers; Credential Guard moves the secret across a TCB boundary that an NT-kernel-mode attacker cannot cross.

Because the application chains through a protocol that cannot use Credential-Guard-protected credentials. &quot;When Credential Guard is enabled, NTLMv1, MS-CHAPv2, Digest, and CredSSP can&apos;t use the signed-in credentials&quot; [@ms-cg-howitworks]; third-party SSPs that depend on `KerbQuerySupplementalCredentialsMessage` simply break under Credential Guard [@ms-cg-considerations]. The remediation is to migrate the application to a supported protocol (Kerberos with AES, certificate-based EAP-TLS / PEAP-TLS, or Hello-for-Business per-application keys).
&lt;p&gt;The empty hash from §1 is, in 2026, a property of every domain-joined Windows 11 22H2+ box on the planet. Eleven years of &lt;code&gt;lsass.exe&lt;/code&gt; extraction history made the architectural pivot inevitable; eight years of trustlet maturation have made the pivot the default. What remains -- the use surface, the protocol surface, the token surface, the typed-credential surface, the third-party-SSP surface -- is the next eleven years of work.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;credential-guard-lsass-isolation-the-canonical-vbs-trustlet&quot; keyTerms={[
  { term: &quot;lsass.exe&quot;, definition: &quot;The user-mode Windows service that handles authentication and (until Credential Guard) held every long-lived credential in process memory. The agent in the agent / trustlet split.&quot; },
  { term: &quot;LsaIso.exe&quot;, definition: &quot;The Credential Guard trustlet (Trustlet ID 1) running in VTL1, holding the NTOWF and Kerberos long-term keys behind a hypervisor boundary.&quot; },
  { term: &quot;NTOWF&quot;, definition: &quot;The MD4 of the UTF-16-LE password. Functionally equivalent to the password for any NTLM-speaking service; the canonical Pass-the-Hash credential.&quot; },
  { term: &quot;Pass-the-Hash&quot;, definition: &quot;An attack class in which the client proves possession of the NTOWF without knowing the cleartext password. Originally published by Paul Ashton in 1997.&quot; },
  { term: &quot;Pass-the-Challenge&quot;, definition: &quot;Lyak&apos;s December 2022 attack class against Credential Guard: inject into lsass.exe, call NtlmIumCalculateNtResponse against the static challenge 1122334455667788, submit to crack.sh, recover the NT hash in ~30 seconds.&quot; },
  { term: &quot;PPL (Protected Process Light)&quot;, definition: &quot;A signer-level NT-kernel-enforced isolation mechanism that prevents non-PPL callers from opening lsass.exe for memory read. Bypassable from kernel via mimidrv.sys and from userland (on legacy Windows) via PPLdump.&quot; },
  { term: &quot;Trustlet&quot;, definition: &quot;A Microsoft-signed binary loaded by the Secure Kernel into VTL1 user mode at boot. Identified by Trustlet ID; constrained by Signature Level 12 and the dual-EKU (WSCV + IUM) signature.&quot; },
  { term: &quot;VTL0 / VTL1&quot;, definition: &quot;Virtual Trust Levels enforced by the Hyper-V hypervisor. VTL0 = Normal World (NT kernel + user-mode); VTL1 = Secure World (Secure Kernel + IUM trustlets).&quot; },
  { term: &quot;IUM (Isolated User Mode)&quot;, definition: &quot;The Ring-3 user mode component of VTL1; the address space in which trustlets execute.&quot; },
  { term: &quot;ALPC&quot;, definition: &quot;Advanced Local Procedure Call: the Windows IPC primitive used to carry the LSA_ISO_RPC_SERVER channel between lsass.exe in VTL0 and LsaIso.exe in VTL1.&quot; },
  { term: &quot;Kerberoasting&quot;, definition: &quot;An attack on Kerberos service-account passwords by requesting TGS-REP tickets for arbitrary SPNs and cracking the etype-23 RC4-encrypted portion offline. Disclosed by Tim Medin at DerbyCon 4 (September 2014).&quot; },
  { term: &quot;RBCD (Resource-Based Constrained Delegation)&quot;, definition: &quot;A Kerberos delegation feature abused via the msDS-AllowedToActOnBehalfOfOtherIdentity LDAP attribute to mint service tickets as any user against a target. Refined by Forshaw 2022; weaponised in KrbRelayUp.&quot; },
  { term: &quot;SeImpersonatePrivilege&quot;, definition: &quot;The Windows token privilege held by service accounts that allows ImpersonateLoggedOnUser. The basis of the entire Potato escalation family.&quot; },
  { term: &quot;DPAPI&quot;, definition: &quot;The Windows per-user secret-protection API. The DPAPI GUID in the [LSA Isolated Data] block ties the trustlet record to the on-disk per-user master-key chain.&quot; },
  { term: &quot;VSM master key&quot;, definition: &quot;A symmetric key generated and stored only in VTL1, sealed by the TPM under PCR-bound policy, that wraps any persistent state the trustlets need to survive reboots.&quot; }
]} questions={[
  { q: &quot;Why did Microsoft conclude in 2014 that no patch could fix the LSASS-extraction class?&quot;, a: &quot;Because the secret is in a process whose memory is governed by the same NT kernel an attacker with administrator can subvert. Generations 0-4 add layers (PPL, WDigest disable, Protected Users, Tier-0 isolation) inside the NT-kernel TCB; only changing the TCB (moving the secret to a kernel the attacker cannot reach) addresses the structural problem.&quot; },
  { q: &quot;Distinguish LSA Protection (RunAsPPL) from Credential Guard.&quot;, a: &quot;PPL is NT-kernel-enforced; the same kernel an attacker is trying to subvert. Credential Guard is hypervisor-enforced; the secret moves to a separate kernel (the Secure Kernel / VTL1) the NT-kernel attacker cannot cross. They are complementary, not substitutes.&quot; },
  { q: &quot;What is in the encrypted blob field of a Pypykatz [LSA Isolated Data] dump on a Credential-Guard-on box?&quot;, a: &quot;An AES-GCM-wrapped serialised record containing (among other fields) the NTOWF, with a length-prefixed header (verbatim hex prefix a000000000000000080000006400000001000000010100000100000036) and an embedded ASCII tag 4e746c6d48617368 = NtlmHash that names the wrapped field. The wrap key is held only in VTL1 by LsaIso.exe.&quot; },
  { q: &quot;Walk the Pass-the-Challenge attack flow end to end.&quot;, a: &quot;Inject SecurityPackage.dll into lsass.exe; extract Context Handle from the Pypykatz dump; call NtlmIumCalculateNtResponse over the established ALPC channel with the static challenge 1122334455667788; receive the trustlet-computed NTLMv1 response; submit to crack.sh; receive the NT hash in approximately 30 seconds. The trustlet is faithful; the agent&apos;s request is the attack surface.&quot; },
  { q: &quot;Why is Pass-the-Challenge a class property of the agent / trustlet split rather than a Microsoft bug?&quot;, a: &quot;If lsass.exe could not call the trustlet, the trustlet would be useless (no path from wire challenge to response). If lsass.exe can call the trustlet, an attacker who compromises lsass.exe inherits the channel. Closing the gap structurally requires moving protocol code into the trustlet, which would dramatically expand the trustlet TCB and is not on Microsoft&apos;s roadmap.&quot; },
  { q: &quot;Name three things Credential Guard explicitly does not protect.&quot;, a: &quot;Domain controllers (NTDS.dit, krbtgt long-term key); credential use without theft (Kerberoasting, AS-REP Roasting, RBCD); SeImpersonatePrivilege Potato chain (token, not credential); plaintext-secret protocols (NTLMv1, MS-CHAPv2, Digest, CredSSP); out-of-LSA storage (web credentials, non-Microsoft SSPs, Remote Desktop Gateway prompt path).&quot; },
  { q: &quot;Why was default-on Credential Guard shipped without UEFI Lock?&quot;, a: &quot;To allow administrators to disable the feature remotely if a third-party SSP or MS-CHAPv2-dependent workflow breaks. The trade-off is that an attacker who has already obtained the privilege required to flip the registry value can also disable Credential Guard. Microsoft chose the operational reality of remote-disable convenience over the in-principle attacker-disable hardening.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>credential-guard</category><category>lsaiso</category><category>vbs-trustlets</category><category>lsass</category><category>mimikatz</category><category>pass-the-challenge</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>