<?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: mimikatz</title><description>Posts tagged mimikatz.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 07 Jun 2026 04:13:12 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/mimikatz/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>DPAPI and DPAPI-NG: The Credential Vault Under Everything</title><link>https://paragmali.com/blog/dpapi-and-dpapi-ng-the-credential-vault-under-everything/</link><guid isPermaLink="true">https://paragmali.com/blog/dpapi-and-dpapi-ng-the-credential-vault-under-everything/</guid><description>A 25-year tour of Windows Data Protection API: the four-stage classic chain, the 2012 DPAPI-NG redesign, the KDS root key, and the five structural ceilings the design cannot close.</description><pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Classic DPAPI (Windows 2000, February 17, 2000 [@wikipedia-windows2000]) wraps every per-user Windows secret -- browser cookies, WiFi keys, EFS file keys, Outlook passwords, Credential Manager entries -- in a four-stage chain rooted at the user&apos;s password.** DPAPI-NG (Windows 8 / Server 2012) [@wikipedia-winserver2012] replaces the (user, machine) decryption boundary with a protection descriptor [@ms-protection-descriptors] and rides the Microsoft Key Distribution Service [@ms-gmsa-overview] to give every domain controller in the forest the same per-(group, period) key without inter-DC sync. Group Managed Service Accounts, delegated Managed Service Accounts [@ms-dmsa-overview], Windows Hello for Business on TPM-less devices, and Credential Guard&apos;s isolated-secret persistence all sit on top. The 2022-2025 Golden gMSA [@semperis-golden-gmsa], Golden dMSA [@semperis-golden-dmsa], and Chromium app-bound encryption [@thn-app-bound] disclosures all admit the same thing: DPAPI&apos;s structural ceilings (user-context binding, single-password derivation, KDS-root-key irrotability, hibernation, domain-backup-key concentration) are the design, not bugs.
&lt;h2&gt;1. A Thumb Drive, a Profile Directory, and Three Mimikatz Commands&lt;/h2&gt;
&lt;p&gt;A DFIR analyst slides a stolen laptop&apos;s drive into a write-blocker. The volume mounts because BitLocker&apos;s recovery key was already in the corporate KMS. Six files in &lt;code&gt;C:\Users\alice\AppData\Roaming\Microsoft\Protect\S-1-5-21-...&lt;/code&gt; -- five GUIDs and a &lt;code&gt;Preferred&lt;/code&gt; pointer -- are the only thing standing between the analyst and a decade of Alice&apos;s saved Windows secrets.&lt;/p&gt;
&lt;p&gt;Three Mimikatz [@mimikatz-dpapi-wiki] commands later (&lt;code&gt;dpapi::masterkey /in:&amp;amp;lt;GUID&amp;amp;gt; /sid:S-1-5-21-... /password:&amp;lt;known&amp;gt;&lt;/code&gt;, then &lt;code&gt;dpapi::cred /in:&amp;lt;credfile&amp;gt; /masterkey:&amp;lt;unwrapped&amp;gt;&lt;/code&gt;, then &lt;code&gt;dpapi::chrome /in:&quot;Login Data&quot;&lt;/code&gt;) the analyst has Alice&apos;s saved RDP password to the production jump host, her Microsoft 365 session cookie, the home WiFi PSK, the KeePass &quot;Windows User Account&quot; master password, and the EFS keys for her protected documents (each item is itemised with primary citations in §5&apos;s eleven-row credential-vault inventory). No kernel exploit. No live login. Just one (account-password, SID) pair recovered offline from last week&apos;s NTDS.dit backup.&lt;/p&gt;
&lt;p&gt;The trick that makes that scene possible is older than most working engineers. It shipped in Windows 2000 GA on February 17, 2000 [@wikipedia-windows2000], it has been the same shape for 25 years, and its single-secret design assumption has been public and tractable since February 3, 2010 [@bursztein-talk-page]. The trick has a name: the &lt;strong&gt;Data Protection API&lt;/strong&gt;, or DPAPI.&lt;/p&gt;
&lt;p&gt;This article walks the API end-to-end at the level of detail an academic survey would, but with the working-engineer&apos;s framing the topic deserves. We open the four-stage classic-DPAPI chain at the SHA-1-of-NT-hash-into-PBKDF2-with-the-SID-as-UTF-16LE-salt level. We open the 2012 DPAPI-NG redesign [@ms-cng-dpapi] and the Microsoft Key Distribution Service&apos;s L0/L1/L2 derivation chain [@ms-gkdi-landing] at the same level of precision.&lt;/p&gt;
&lt;p&gt;We name the four production consumers that ride the new chain in 2026: gMSAs, dMSAs, Windows Hello for Business [@ms-whfb-howitworks] software-KSP credentials, and Credential Guard [@paragmali-com-the-en]&apos;s isolated-secret persistence. We name the five structural ceilings the 2022 Golden gMSA [@semperis-golden-gmsa], 2024 Chromium app-bound encryption [@google-security-blog-app-bound], and 2025 Golden dMSA [@semperis-golden-dmsa] disclosures all admit out loud. And we close with what a 2026 practitioner -- developer, defender, red-teamer, platform engineer -- actually does with all of it.&lt;/p&gt;
&lt;p&gt;A note on adjacent topics: the companion &lt;em&gt;Credential Guard&lt;/em&gt; article in this series covers the LSAISO trustlet&apos;s isolation boundary; the companion &lt;em&gt;VBS Trustlets&lt;/em&gt; [@paragmali-com-secure-kernel] article covers the trustlet model itself; the &lt;em&gt;TPM in Windows&lt;/em&gt; [@paragmali-com-the-c] article covers TPM-bound key storage providers; the &lt;em&gt;BitLocker on Windows&lt;/em&gt; [@paragmali-com-limits-of] article covers full-volume encryption; the &lt;em&gt;NTLMless&lt;/em&gt; [@paragmali-com-in-windows] article covers Kerberoasting and Golden Ticket disambiguation. Where we touch those topics, we touch them briefly and refer out -- the goal here is to make DPAPI&apos;s chain legible from &lt;code&gt;CryptProtectData&lt;/code&gt; all the way to the four-phase &lt;code&gt;GoldenDMSA&lt;/code&gt; pipeline.&lt;/p&gt;
&lt;p&gt;If you can read those six files in Alice&apos;s &lt;code&gt;Protect&lt;/code&gt; directory and you have her password&apos;s SHA-1 hash, you have everything Windows ever encrypted for her. The next eleven sections explain why -- and why the 2012 redesign that was supposed to fix it produced a new ceiling that, by 2022, turned out to be even harder to live with.&lt;/p&gt;
&lt;h2&gt;2. Why Windows 2000 Needed a Credential Vault: Generation 0 and Generation 1&lt;/h2&gt;
&lt;p&gt;Three years before DPAPI shipped, an attacker with a logged-in user&apos;s session could read every Internet Explorer auto-complete password, every Outlook Express account password, every saved-FTP credential, and every dial-up RAS phonebook entry on the machine -- without breaking any cryptography. The late-1990s reversing tradition (the original &lt;code&gt;pwdump&lt;/code&gt;, &lt;em&gt;L0phtCrack&lt;/em&gt;, Cain &amp;amp; Abel&apos;s &quot;Protected Storage PassView,&quot; the ad-hoc Outlook Express and IE 4 form-fill stealers documented across Bugtraq and ntbugtraq mailing lists at the time) defeated all of it uniformly. The &quot;encryption&quot; applications used was honoured by the OS, faithfully, for the attacker&apos;s process as for the legitimate one -- because there was no system primitive that distinguished one from the other. Each application baked its own key into the binary; every reverser who pulled the binary apart pulled the key out with it.&lt;/p&gt;

The system-provided per-user and per-machine secret-storage primitive that ships in every Windows release from Windows 2000 onward [@wikipedia-dpapi]. The classic API surface is two flat-C functions -- `CryptProtectData` [@ms-cryptprotectdata] and `CryptUnprotectData` -- that take a plaintext, an optional caller entropy parameter, and return a self-contained opaque BLOB. The cryptographic chain inside those two functions is rooted at the user&apos;s login password and is what every &quot;encrypted&quot; Windows secret of the next 25 years sits on top of.
&lt;p&gt;If the attacker is the user&apos;s session, what does &quot;encrypt this for the user&quot; even mean? That is the question every Generation 0 design dodged and every modern credential-vault design has to answer head-on. The 1990s answer (XOR-with-a-baked-in-key) and Microsoft&apos;s first attempt at a real system primitive (Protected Storage / PStore) both missed the same point in different ways.&lt;/p&gt;
&lt;h3&gt;Generation 1: Protected Storage&lt;/h3&gt;
&lt;p&gt;Protected Storage shipped with Internet Explorer 4 and stayed in the OS until Microsoft formally deprecated it. The &lt;code&gt;pstore.dll&lt;/code&gt; [@ms-pstore] item taxonomy is a four-tuple &lt;code&gt;(Key, Type, Subtype, Name)&lt;/code&gt; -- a folder hierarchy of named secret entries. The API was the first system-level secret store on Windows that any application could use without writing its own key derivation; the conceptual contribution survived even after the implementation was abandoned.&lt;/p&gt;
&lt;p&gt;PStore had two ideas the post-Vista world kept and one it dropped. The two it kept: secrets live in a &lt;em&gt;system primitive&lt;/em&gt;, not in each application; secrets are addressed by &lt;em&gt;name&lt;/em&gt;, not by raw key handle. The one it dropped: an &lt;em&gt;Authenticode access-rule&lt;/em&gt; clause that would have bound a stored item to the signing identity of the application that created it. No application ever used the access-rule clause in production. Microsoft&apos;s developer notes are blunt about how the story ended: &lt;em&gt;&quot;Pstore uses an older implementation of data protection. Developers are strongly encouraged to take advantage of the stronger data protection provided by the &lt;code&gt;CryptProtectData&lt;/code&gt; [@ms-cryptprotectdata] and &lt;code&gt;CryptUnprotectData&lt;/code&gt; functions&quot;&lt;/em&gt;; PStore is &lt;em&gt;&quot;only available for read-only operations in Windows Server 2008 and Windows Vista, but may be unavailable in subsequent versions.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The PStore code-identity-pinning idea (Authenticode access rules) was abandoned in 2000 and re-invented twenty-six years later by Chromium 2024 app-bound encryption, which we will reach in §10.3.&lt;/p&gt;
&lt;h3&gt;What survived into DPAPI&lt;/h3&gt;
&lt;p&gt;The Generation 2 design that shipped with Windows 2000 in February 2000 made four moves at once. It kept the two PStore ideas worth keeping (&quot;system-level, not per-application&quot; and &quot;secret addressed by structured name&quot;). It dropped the unused Authenticode access-rule clause. It pushed the cryptographic chain down to a key derived directly from the user&apos;s login password. And it added a domain-recovery sidecar (the BackupKey Remote Protocol [@ms-bkrp-landing], which we open in §5) so managed enterprises would adopt it.&lt;/p&gt;
&lt;p&gt;The canonical first public design document [@learn-microsoft-com-versions-ms995355vmsdn10]) -- NAI Labs / Network Associates&apos; &lt;em&gt;&quot;Windows Data Protection&quot;&lt;/em&gt; whitepaper, MSDN ms995355, October 2001 -- is unambiguous about the layering: &lt;em&gt;&quot;DPAPI initially generates a strong key called a MasterKey, which is protected by the user&apos;s password. DPAPI uses a standard cryptographic process called Password-Based Key Derivation, described in PKCS #5, to generate a key from the password.&quot;&lt;/em&gt; And: &lt;em&gt;&quot;DPAPI is a password-based data protection service. It requires a password to provide protection.&quot;&lt;/em&gt;Some secondary sources attribute a &quot;Microsoft Windows Data Protection API&quot; whitepaper to Niels Ferguson at &lt;code&gt;niels.ferguson.com/research/dpapi.html&lt;/code&gt;. That URL has been TCP-unreachable for years, has zero Wayback captures across four candidate variants, and the Wikipedia &lt;em&gt;Niels Ferguson&lt;/em&gt; article [@wikipedia-niels-ferguson] lists no DPAPI publication for him -- his named Microsoft paper is the 2006 BitLocker / Elephant-diffuser paper, not anything DPAPI-related. The verifiable Microsoft-blessed first design document is the NAI Labs ms995355 whitepaper [@learn-microsoft-com-versions-ms995355vmsdn10]).&lt;/p&gt;
&lt;p&gt;The two-function flat-C API Microsoft shipped with Windows 2000 in February 2000 is what every Windows secret of the next 25 years has been encrypted with. The four-stage chain it hides behind those two function names is what we open up next.&lt;/p&gt;
&lt;h2&gt;3. The Four-Stage Chain: How &lt;code&gt;CryptProtectData&lt;/code&gt; Actually Encrypts&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;CryptProtectData&lt;/code&gt; [@ms-cryptprotectdata] call goes in with a plaintext buffer and an optional entropy parameter; out comes a self-contained opaque BLOB whose header adds roughly 100-150 bytes to the plaintext (the exact size depends on the algorithm choice and the master-key GUID encoding; the field-by-field BLOB layout is documented in the Bursztein-Picod 2010 paper [@bursztein-paper-pdf] and parsed by the Mimikatz &lt;code&gt;dpapi::blob&lt;/code&gt; [@mimikatz-dpapi-wiki] module). There is no &lt;code&gt;pszProvider&lt;/code&gt; argument, no &lt;code&gt;hKey&lt;/code&gt;, no algorithm choice exposed to the caller. Behind those two parameters is a four-stage cryptographic chain that has been the same shape for a quarter-century. Each stage takes the previous stage&apos;s output and one new input; the &lt;em&gt;only&lt;/em&gt; secret in the entire chain that an offline attacker has to guess is the user&apos;s password.&lt;/p&gt;

flowchart TD
    Password[&quot;User password&quot;] --&amp;gt; NTHash[&quot;NT hash&lt;br /&gt;(MD4 of UTF-16LE password)&quot;]
    NTHash --&amp;gt; Sha1NT[&quot;SHA-1(NT hash)&quot;]
    SID[&quot;User SID&lt;br /&gt;(UTF-16LE)&quot;] --&amp;gt; PBKDF2
    Sha1NT --&amp;gt; PBKDF2[&quot;Stage 1: PBKDF2&lt;br /&gt;(HMAC-SHA1 / HMAC-SHA512)&quot;]
    PBKDF2 --&amp;gt; PreKey[&quot;Pre-key&quot;]
    MK[&quot;Stage 2: Master key&lt;br /&gt;(64 random bytes)&quot;] --&amp;gt;|encrypted under| AESCBC[&quot;version-cipher wrap (AES-CBC on Win7+)&quot;]
    PreKey --&amp;gt; AESCBC
    AESCBC --&amp;gt; MKFile[&quot;%APPDATA%/Microsoft/Protect/&amp;lt;SID&amp;gt;/&amp;lt;GUID&amp;gt;&quot;]
    MK --&amp;gt; SessionKey[&quot;Stage 3: Session key&lt;br /&gt;HMAC(MK, salt || entropy)&quot;]
    SessionKey --&amp;gt; Wrap[&quot;Stage 4: BLOB wrap&lt;br /&gt;(3DES or AES-256, salt, HMAC)&quot;]
    Plaintext[&quot;Plaintext&quot;] --&amp;gt; Wrap
    Wrap --&amp;gt; Blob[&quot;DPAPI BLOB&quot;]
&lt;h3&gt;Stage 1: Pre-key derivation&lt;/h3&gt;
&lt;p&gt;The pre-key is a function of three values. The user-account password (or its NT-hash equivalent, supplied by &lt;code&gt;LSASS&lt;/code&gt; to the local DPAPI provider) is hashed with SHA-1; the SHA-1 result is fed into PBKDF2 as the input keying material; the user&apos;s security identifier (SID) [@learn-microsoft-com-versions-ms995355vmsdn10]) UTF-16LE-encoded is the salt; and a Windows-version-dependent iteration count completes the call. The output is a fixed-width pre-key that Stage 2 will use to wrap the master key.&lt;/p&gt;
&lt;p&gt;The chain has changed &lt;em&gt;parameters&lt;/em&gt; across Windows versions but has kept the four-stage &lt;em&gt;shape&lt;/em&gt; since 2000. The Passcape master-key analysis table [@passcape-master-key-analysis] records the migration verbatim:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Windows version&lt;/th&gt;
&lt;th&gt;Symmetric cipher&lt;/th&gt;
&lt;th&gt;HMAC&lt;/th&gt;
&lt;th&gt;PBKDF2 iterations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Windows 2000&lt;/td&gt;
&lt;td&gt;RC4&lt;/td&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows XP&lt;/td&gt;
&lt;td&gt;3DES&lt;/td&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;4 000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows Vista&lt;/td&gt;
&lt;td&gt;3DES&lt;/td&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;24 000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows 7&lt;/td&gt;
&lt;td&gt;AES-256&lt;/td&gt;
&lt;td&gt;SHA-512&lt;/td&gt;
&lt;td&gt;5 600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows 10 / 11&lt;/td&gt;
&lt;td&gt;AES-256&lt;/td&gt;
&lt;td&gt;SHA-512&lt;/td&gt;
&lt;td&gt;8 000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The shift from PBKDF2-HMAC-SHA1 / 3DES (Windows 2000 -- Vista) to PBKDF2-HMAC-SHA512 / AES-256 (Windows 7 onward) happened at Windows 7, not Windows 10; Bursztein and Picod&apos;s 2010 USENIX WOOT paper [@usenix-woot10] documented the SHA-1/3DES era through Windows 7 (the Black Hat DC 2010 talk and WOOT paper covered XP&apos;s 4,000-iteration regime and Vista&apos;s 24,000-iteration regime; Windows 7&apos;s PBKDF2-SHA512/AES-256 shift had only recently shipped when the research was finalised).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 8 000 iterations of HMAC-SHA-512 is not strong against a modern wordlist attack on a leaked NT hash. Cumulative updates can raise the iteration count further; the actual count is recorded in the master-key file&apos;s header and is not implicit. When you read someone&apos;s master key, read the iteration count from the file -- do not assume.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Stage 2: The master key&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;master key&lt;/em&gt; is a 64-byte cryptographically random secret; one is generated per user, on first use of DPAPI, and a fresh one is generated every 90 days by default. Master keys live as files inside &lt;code&gt;%APPDATA%\Microsoft\Protect\&amp;amp;lt;SID&amp;amp;gt;\&amp;amp;lt;GUID&amp;amp;gt;&lt;/code&gt;, where the GUID is the master-key identifier embedded in every BLOB that uses it. The file begins with a service header (containing the iteration count and algorithm IDs) followed by four distinct slots: (1) the user-master-key blob (encrypted under the Stage 1 pre-key with the version-dependent cipher -- RC4 on Windows 2000, 3DES on XP through Vista, AES-256-CBC on Windows 7 and later); (2) the local-encryption-key slot; (3) the local-backup-key (Windows 2000) or CREDHIST GUID (Windows XP and later); and (4) the domain-backup-key blob [@ms-bkrp-landing] (encrypted to the DC&apos;s backup public key, see §5).&lt;/p&gt;

A 64-byte random secret per user, persisted as a file under `%APPDATA%\Microsoft\Protect\&amp;lt;SID&amp;gt;\&amp;lt;GUID&amp;gt;`, encrypted under a pre-key derived from the user&apos;s password and SID. Every DPAPI BLOB the user ever creates is wrapped under a session key derived from one of these master keys. Master keys rotate every 90 days by default per the NAI Labs design document [@learn-microsoft-com-versions-ms995355vmsdn10]) and the Passcape master-key analysis [@passcape-master-key-analysis], but old master keys remain on disk so old BLOBs can still be decrypted.
&lt;h3&gt;Stage 3: The per-call session key&lt;/h3&gt;
&lt;p&gt;For every call to &lt;code&gt;CryptProtectData&lt;/code&gt;, DPAPI generates a fresh per-blob salt, computes an HMAC of the master key with the salt and the optional caller entropy, and uses that HMAC as the session key for the actual symmetric encryption. The session key is never stored; it is derivable from (master key, salt, entropy) at unwrap time per the Bursztein-Picod 2010 paper [@bursztein-paper-pdf] §3.3 and the Mimikatz &lt;code&gt;dpapi::blob&lt;/code&gt; parser [@mimikatz-dpapi-wiki]. The salt is in the BLOB header; the entropy, if any, must be supplied by the caller at unwrap time.&lt;/p&gt;
&lt;h3&gt;Stage 4: The BLOB wrap&lt;/h3&gt;
&lt;p&gt;The output BLOB is a self-describing structure with a fixed header. The provider GUID &lt;code&gt;{df9d8cd0-1501-11d1-8c7a-00c04fc297eb}&lt;/code&gt; identifies it as a classic-DPAPI blob; the master-key GUID names the master key under which it was wrapped; an &lt;code&gt;algCrypt&lt;/code&gt; algorithm identifier records which symmetric cipher was used (&lt;code&gt;0x6603&lt;/code&gt; for &lt;code&gt;CALG_3DES&lt;/code&gt; on legacy builds, &lt;code&gt;0x6610&lt;/code&gt; for &lt;code&gt;CALG_AES_256&lt;/code&gt; on later builds); the salt, ciphertext, and HMAC fill the rest. The Mimikatz &lt;code&gt;dpapi&lt;/code&gt; module wiki [@mimikatz-dpapi-wiki] documents the verbatim field layout that every offline DPAPI tool parses to this day.&lt;/p&gt;

The `algCrypt` field in the DPAPI BLOB header is a CryptoAPI algorithm identifier from `wincrypt.h`: `0x6603` is `CALG_3DES` (the historical default, encoded as `ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_3DES`); `0x6610` is `CALG_AES_256` (used on Windows 7 and later, encoded as `ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256`). The provider GUID `{df9d8cd0-1501-11d1-8c7a-00c04fc297eb}` is the magic constant that marks every classic-DPAPI BLOB and is the same value the Mimikatz `dpapi::blob` module [@mimikatz-dpapi-wiki] and the Bursztein-Picod 2010 paper [@bursztein-paper-pdf] §3.3.1 print when they parse one.
&lt;p&gt;{`&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Parse --&amp;gt; Cert&amp;amp;#123;&quot;CERTIFICATE=&quot;&amp;amp;#125;
Parse --&amp;gt; Local&amp;amp;#123;&quot;LOCAL=&quot;&amp;amp;#125;
Parse --&amp;gt; Web&amp;amp;#123;&quot;WEBCREDENTIALS=&quot;&amp;amp;#125;
Sid --&amp;gt; KSP[&quot;Microsoft Key Protection Provider&amp;lt;br/&amp;gt;+ kdssvc.dll [MS-GKDI]&quot;]
Cert --&amp;gt; CertKSP[&quot;Cert&apos;s KSP&amp;lt;br/&amp;gt;(TPM-backed possible)&quot;]
Local --&amp;gt; LocalProv[&quot;Microsoft Key Protection Provider&amp;lt;br/&amp;gt;(LOCAL=user / LOCAL=machine)&quot;]
Web --&amp;gt; Broker[&quot;Microsoft Client Key Protection Provider&amp;lt;br/&amp;gt;(credential broker)&quot;]
KSP --&amp;gt; Wrap
CertKSP --&amp;gt; Wrap
LocalProv --&amp;gt; Wrap
Broker --&amp;gt; Wrap
Wrap[&quot;Derive wrapping key,&amp;lt;br/&amp;gt;encrypt content&quot;] --&amp;gt; Blob[&quot;Self-describing blob&amp;lt;br/&amp;gt;(descriptor + provider info&amp;lt;br/&amp;gt;+ key id + ciphertext + HMAC)&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output blob is self-describing per the protected-data-format reference [@ms-protected-data-format]: descriptor, provider info, key identifier, ciphertext, HMAC. Any device with a CNG implementation that can satisfy the descriptor decrypts. There is no out-of-band key shipping. There is no &quot;encrypt the blob and ship the key separately.&quot; The descriptor &lt;em&gt;is&lt;/em&gt; the key-distribution policy.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; DPAPI-NG separates &lt;em&gt;who can decrypt&lt;/em&gt; (the descriptor) from &lt;em&gt;where the key material lives&lt;/em&gt; (the provider). The descriptor is the contract; the provider is the implementation. This is the structural innovation that lets a blob protected on one machine be decrypted on another without any application-layer key-management code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The CNG DPAPI overview page lists only &lt;em&gt;two&lt;/em&gt; principal classes -- AD group and web credentials -- whereas the protection-descriptors page enumerates three (adding the certificate-store class). Both are correct: the certificate descriptor maps to a different provider, hence the two-principal framing on the higher-level overview page and the three-keyword framing on the descriptors-grammar page.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;LOCAL=user&lt;/code&gt; and &lt;code&gt;CERTIFICATE=&lt;/code&gt; cases are interesting but mostly variations on themes that classic DPAPI or the Windows certificate store could already do. The case that required Microsoft to ship a new DC-side daemon -- the case that turned DPAPI-NG into the substrate for gMSAs [@ms-gmsa-overview], dMSAs [@ms-dmsa-overview], Hello for Business, and Credential Guard&apos;s persistence layer -- is the &lt;code&gt;SID=&lt;/code&gt; AD-group descriptor. The next section opens its substrate: the Microsoft Key Distribution Service and the &lt;code&gt;[MS-GKDI]&lt;/code&gt; protocol.&lt;/p&gt;
&lt;h2&gt;7. The Microsoft Key Distribution Service: How &lt;code&gt;[MS-GKDI]&lt;/code&gt; Computes the Same Group Key on Every DC Without Talking to Any of Them&lt;/h2&gt;
&lt;p&gt;Imagine a forest with seven writable domain controllers. A laptop in Singapore protects a DPAPI-NG blob with &lt;code&gt;SID=S-1-5-21-...XYZ&lt;/code&gt; (some AD group). Three months later, a phone in Seoul -- a member of the same group, on a different DC -- needs to decrypt it. Neither DC has ever heard of the blob. Both DCs derive &lt;em&gt;exactly the same group key&lt;/em&gt; and hand it to the requesting member. No inter-DC synchronisation. No key-distribution code in either application. The mechanism is one forest-wide root key plus a deterministic key-derivation function.&lt;/p&gt;

The DC-side daemon that ships in every writable domain controller from Windows Server 2012 onward [@wikipedia-winserver2012]. Implements the [`[MS-GKDI]` Group Key Distribution Protocol](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a) (current revision class 10.0, April 2024) and is the substrate for every DPAPI-NG `SID=` blob, every gMSA password, every dMSA password, and the TPM-less software-KSP path of Windows Hello for Business. The service has one job: given a (group, time-period) request from a member computer, derive and return the per-(group, period) key from a single forest-wide root key.
&lt;h3&gt;Provisioning the root key&lt;/h3&gt;
&lt;p&gt;Every forest needs exactly one KDS root key before any DPAPI-NG &lt;code&gt;SID=&lt;/code&gt; consumer can use it. The PowerShell cmdlet &lt;code&gt;Add-KdsRootKey&lt;/code&gt; [@ms-add-kdsrootkey] is the provisioning entry point. The Microsoft Learn page is verbatim about the constraints: &lt;em&gt;&quot;The Add-KdsRootKey cmdlet generates a new root key for the Microsoft Group Key Distribution Service (KdsSvc) within Active Directory... It is required to run this only once per forest.&quot;&lt;/em&gt; The default &lt;code&gt;EffectiveTime&lt;/code&gt; is &lt;em&gt;&quot;10 days after the current date&quot;&lt;/em&gt; to allow Active Directory replication to converge across all writable DCs before any consumer tries to derive against the new key.The &lt;code&gt;Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))&lt;/code&gt; override is for &lt;em&gt;single-DC test forests only&lt;/em&gt;. Production forests should let the 10-day default replicate; using the back-dated override means the first consumer to call into the KDS may target a DC that has not yet received the new root key from replication.&lt;/p&gt;
&lt;p&gt;The root key lives at &lt;code&gt;CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,&amp;lt;forest-DN&amp;gt;&lt;/code&gt; and carries four attributes that downstream offensive-research tools enumerate: &lt;code&gt;msKds-RootKeyData&lt;/code&gt; (the actual key bytes), &lt;code&gt;msKds-KDFAlgorithmID&lt;/code&gt; (the KDF identifier, currently SP800-108 HMAC-SHA512), &lt;code&gt;msKds-KDFParam&lt;/code&gt; (the KDF parameter block), and &lt;code&gt;msKds-PrivateKeyLength&lt;/code&gt;. These four attributes, together, are everything a deterministic-KDF derivation needs to recompute every group key the forest will ever produce.&lt;/p&gt;

The single forest-wide secret that anchors every per-(group, period) key the Microsoft Key Distribution Service will ever derive, for every DPAPI-NG `SID=` blob, every gMSA, every dMSA, every Hello-for-Business software-KSP container, and every Credential-Guard isolated-secret persistence wrap. Provisioned with `Add-KdsRootKey` [@ms-add-kdsrootkey], exactly once per forest. Stored as four attributes (`msKds-RootKeyData`, `msKds-KDFAlgorithmID`, `msKds-KDFParam`, `msKds-PrivateKeyLength`) under `CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,`. Currently, Microsoft documents no rotation procedure [@ms-cng-dpapi-backup-keys]; see §9.3.
&lt;h3&gt;The L0 / L1 / L2 derivation chain&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;[MS-GKDI]&lt;/code&gt; specification&lt;/a&gt; (current revision class 10.0, April 23 2024) describes the protocol. Internally, the KDS computes a three-level derivation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L0 seed key&lt;/strong&gt;: derived from the root key and a label that includes the period-in-hours-thousands tier and the group-related input, via a NIST SP 800-108 KDF in counter mode using HMAC-SHA-512 (see NIST SP 800-108r1 [@nist-sp-800-108r1], August 2022).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L1 seed key&lt;/strong&gt;: derived from L0 with a second-tier label.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L2 seed key&lt;/strong&gt;: derived from L1 with the third-tier label. This is the actual symmetric key the DPAPI-NG blob&apos;s content key wraps under (or the seed for a per-period group ECDH key pair, in the public-key DPAPI-NG mode).&lt;/li&gt;
&lt;/ul&gt;

The first level of the [`[MS-GKDI]`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a) three-tier derivation chain. Computed deterministically from the KDS root key and a label that combines the time-period tier (in units of `period-in-hours / 1000`) and a group-related input. The whole point of the chain is that any DC that holds the same root key, given the same label, derives the same L0 seed key without coordination with any other DC -- which is also the whole reason a single root-key compromise compromises every key the forest will ever derive.

flowchart TD
    Root[&quot;KDS root key&lt;br /&gt;(forest-wide secret)&quot;] --&amp;gt; L0[&quot;L0 seed key&lt;br /&gt;(period_hours / 1000 + group input)&quot;]
    L0 --&amp;gt; L1[&quot;L1 seed key&lt;br /&gt;(second-tier label)&quot;]
    L1 --&amp;gt; L2[&quot;L2 seed key&lt;br /&gt;(third-tier label)&quot;]
    L2 --&amp;gt; Symmetric[&quot;Per-(group, period)&lt;br /&gt;symmetric key OR&lt;br /&gt;ECDH key-pair seed&quot;]
    KDF[&quot;SP800-108 counter-mode&lt;br /&gt;KDF, HMAC-SHA-512&quot;] -.derives.-&amp;gt; L0
    KDF -.derives.-&amp;gt; L1
    KDF -.derives.-&amp;gt; L2
&lt;h3&gt;The &lt;code&gt;GetKey&lt;/code&gt; round trip&lt;/h3&gt;
&lt;p&gt;A member computer&apos;s local KDS-NG provider calls the &lt;code&gt;GetKey&lt;/code&gt; RPC (the primary opnum of &lt;code&gt;[MS-GKDI]&lt;/code&gt;) against any reachable writable DC. The DC computes the L0/L1/L2 chain on demand and returns the per-(group, period) key material. No inter-DC synchronisation is needed because the KDF is deterministic.&lt;/p&gt;

sequenceDiagram
    participant Member as &quot;Member computer (Microsoft Key Protection Provider)&quot;
    participant DC as &quot;Nearest writable DC (kdssvc.dll)&quot;
    participant Root as Root key (AD-replicated)
    Member-&amp;gt;&amp;gt;DC: GetKey(group_sid, period_id)
    DC-&amp;gt;&amp;gt;Root: Read msKds-RootKeyData + KDF params
    Root--&amp;gt;&amp;gt;DC: Root-key material
    DC-&amp;gt;&amp;gt;DC: Compute L0(period, group) -&amp;gt; L1 -&amp;gt; L2
    DC--&amp;gt;&amp;gt;Member: Per-(group, period) key material
    Member-&amp;gt;&amp;gt;Member: Wrap (or unwrap) DPAPI-NG blob

Because the KDF is deterministic by design -- this is exactly the security property NIST SP 800-108r1 [@nist-sp-800-108r1] §5 establishes -- an attacker who reads the four root-key attributes once can derive every per-(group, period) key the KDS will ever produce, *for that root key*, without further DC interaction. The same property that lets a Singapore laptop and a Seoul phone derive the same key without talking to each other lets an attacker who reads the root key derive every gMSA password the forest will ever issue. This is the same property that makes Golden gMSA [@semperis-golden-gmsa] work in §10.
&lt;p&gt;Every DC in the forest runs the same &lt;code&gt;kdssvc.dll&lt;/code&gt; over the same root key with the same KDF; every authorised member of the named group can ask any DC for the per-(group, period) key and receive the same answer. The architecture is elegant. It is also, by structural necessity, the architecture that makes a one-shot read of the root key into a one-shot compromise of every key the forest will ever derive. Hold that thought; it is what §10 is built on. First we look at what actually rides on this substrate today.&lt;/p&gt;
&lt;h2&gt;8. The Four Things That Ride DPAPI-NG in 2026&lt;/h2&gt;
&lt;p&gt;One protocol, four production consumers. The same KDS root key that protects a &lt;code&gt;SID=&lt;/code&gt; DPAPI-NG blob is also the root from which every gMSA password, every dMSA password, every TPM-less Windows Hello private key, and every Credential Guard isolated NT-hash is derived.&lt;/p&gt;

A Windows-Server-2012-introduced Active Directory account class whose password is computed automatically by the Microsoft Key Distribution Service [@ms-gmsa-overview] on the DC, rotated every 30 days, 256 bytes long, and shared across multiple service hosts via the `msDS-GroupMSAMembership` SDDL gate. From the Microsoft Learn overview verbatim: *&quot;For a gMSA, the domain controller computes the password on the key that the Key Distribution Services provides, along with other attributes of the gMSA.&quot;* `Install-ADServiceAccount` on each member computer caches the derivation locally so the service can boot under the account.

flowchart TD
    Root[&quot;KDS root key&quot;] --&amp;gt; Chain[&quot;[MS-GKDI] L0/L1/L2 chain&quot;]
    Chain --&amp;gt; GMSA[&quot;gMSA password&lt;br /&gt;(30-day rotation,&lt;br /&gt;256 bytes)&quot;]
    Chain --&amp;gt; DMSA[&quot;dMSA password&lt;br /&gt;(machine-bound,&lt;br /&gt;Server 2025)&quot;]
    Chain --&amp;gt; WHfB[&quot;WHfB software-KSP&lt;br /&gt;(TPM-less devices,&lt;br /&gt;SID + device descriptor)&quot;]
    Chain --&amp;gt; CG[&quot;Credential Guard&lt;br /&gt;LsaIso-isolated secret&lt;br /&gt;(trustlet identity descriptor)&quot;]
&lt;h3&gt;8.1 Group Managed Service Accounts&lt;/h3&gt;
&lt;p&gt;gMSAs shipped in Windows Server 2012 (GA September 2012). The model: one AD account, multiple service hosts, no admin-managed password rotation, no per-service password file. The Microsoft Learn overview [@ms-gmsa-overview] is verbatim about the chain: &lt;em&gt;&quot;The Microsoft Key Distribution Service (kdssvc.dll) lets you securely obtain the latest key or a specific key with a key identifier for an Active Directory account... For a gMSA, the domain controller computes the password on the key that the Key Distribution Services provides, along with other attributes of the gMSA.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The constructed attribute that surfaces the password to authorised member computers is &lt;code&gt;msDS-ManagedPassword&lt;/code&gt; [@ms-ms-adts-managedpassword]; it is computed on demand by the DC from the KDS chain when an authorised member queries it. The authorisation gate is the &lt;code&gt;msDS-GroupMSAMembership&lt;/code&gt; security descriptor on the gMSA object: only principals whose SIDs satisfy that SDDL get the password back. Rotation is every 30 days. The password length is 256 bytes -- &lt;em&gt;&quot;a randomly generated password of 256 bytes, making it infeasible to crack&quot;&lt;/em&gt; per the Semperis Golden gMSA write-up [@semperis-golden-gmsa], which is true &lt;em&gt;if and only if&lt;/em&gt; the KDS root key is intact. We come back to that &lt;em&gt;if and only if&lt;/em&gt; in §10.&lt;/p&gt;
&lt;h3&gt;8.2 Delegated Managed Service Accounts&lt;/h3&gt;
&lt;p&gt;dMSAs shipped in Windows Server 2025 (GA November 1, 2024 [@wikipedia-winserver2025]). The same KDS chain; a different authorisation gate. Rather than binding to AD group membership, dMSA authentication binds to &lt;em&gt;machine identity&lt;/em&gt;: the Microsoft Learn overview [@ms-dmsa-overview] describes dMSAs as &lt;em&gt;&quot;a machine account with managed and fully randomized keys, while disabling original service account passwords. Authentication for dMSA is linked to the device identity, which means that only specified machine identities mapped in Active Directory (AD) can access the account.&quot;&lt;/em&gt; The &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; attribute carries a machine-binding component. Microsoft&apos;s marketing framing positions dMSA as the &quot;Kerberoast-immune&quot; replacement for static service accounts.&lt;/p&gt;
&lt;p&gt;The Server 2025 dMSA design has its own §10 footnote: the July 2025 Semperis Golden dMSA disclosure [@semperis-golden-dmsa] found that the &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; time-component has predictable structure with only ~1 024 plausible values, making offline brute-forcing tractable once the KDS root key is in hand.&lt;/p&gt;
&lt;h3&gt;8.3 Windows Hello for Business software-KSP credentials&lt;/h3&gt;
&lt;p&gt;The Hello for Business &lt;em&gt;how it works&lt;/em&gt; page [@ms-whfb-howitworks] describes the credential as a per-user per-device asymmetric key pair. On TPM-equipped devices the private key lives in the TPM and DPAPI-NG is not in the wrap path. On TPM-less devices (the structural worst case) the WHfB private key sits in a CNG software Key Storage Provider container persisted as a DPAPI-NG-protected file [@ms-whfb-howitworks] whose protection descriptor binds it to the user SID + device. The &lt;em&gt;TPM in Windows&lt;/em&gt; article in this series covers the TPM-bound case; here we record only that the software-KSP fallback rides on DPAPI-NG and inherits the KDS root-key dependency for the SID-bound branch.&lt;/p&gt;

A non-hardware-bound CNG key-storage provider that persists key material in DPAPI-NG-protected files in the user profile. Used by Windows Hello for Business on TPM-less devices [@ms-whfb-howitworks] and as the fallback when no hardware-bound KSP (TPM, smart card, Secure Enclave equivalent) is available. The structural worst case for the WHfB credential, because the private key lives in a file the OS can read in any user-context process, wrapped under a DPAPI-NG blob whose protection descriptor reduces back to the KDS root key for SID-anchored bindings.
&lt;h3&gt;8.4 Credential Guard isolated-secret persistence&lt;/h3&gt;
&lt;p&gt;The companion &lt;em&gt;Credential Guard&lt;/em&gt; article in this series covers &lt;code&gt;LsaIso.exe&lt;/code&gt; and the LSAISO trustlet&apos;s isolation boundary in depth; what matters here is that the trustlet&apos;s persistence layer is DPAPI-NG. &lt;code&gt;LsaIso.exe&lt;/code&gt;, running in VTL1 IUM, stores the isolated NT one-way function outputs and Kerberos session keys in DPAPI-NG blobs whose protection descriptor binds them to the trustlet&apos;s own identity. The VSM master key (TPM-bound on TPM-2.0 systems per Microsoft Learn&apos;s Credential Guard [@ms-credential-guard] overview, which describes how Credential Guard &quot;uses Virtualization-based security (VBS) [@ms-vbs] to isolate secrets&quot;) is what the trustlet seals its DPAPI-NG protection state under across reboots. The end result is that even though the Credential Guard model puts the credential outside &lt;code&gt;lsass.exe&lt;/code&gt;, the &lt;em&gt;persistence&lt;/em&gt; of that isolated secret rides on the same KDS-rooted DPAPI-NG chain every other consumer in this section uses.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consumer&lt;/th&gt;
&lt;th&gt;Rotation&lt;/th&gt;
&lt;th&gt;Authorisation gate&lt;/th&gt;
&lt;th&gt;On-disk artefact&lt;/th&gt;
&lt;th&gt;Recovery story&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;gMSA&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;td&gt;&lt;code&gt;msDS-GroupMSAMembership&lt;/code&gt; SDDL&lt;/td&gt;
&lt;td&gt;Cached on member via &lt;code&gt;Install-ADServiceAccount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recompute via KDS at any time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dMSA&lt;/td&gt;
&lt;td&gt;Managed by KDS&lt;/td&gt;
&lt;td&gt;Machine identity in AD&lt;/td&gt;
&lt;td&gt;Cached on member; &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; carries machine binding&lt;/td&gt;
&lt;td&gt;Recompute via KDS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WHfB software-KSP&lt;/td&gt;
&lt;td&gt;Per-credential lifetime&lt;/td&gt;
&lt;td&gt;User SID + device&lt;/td&gt;
&lt;td&gt;DPAPI-NG-wrapped key container in user profile&lt;/td&gt;
&lt;td&gt;New enrollment if lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LsaIso DPAPI-NG persistence&lt;/td&gt;
&lt;td&gt;Boot-cycle bound&lt;/td&gt;
&lt;td&gt;Trustlet identity&lt;/td&gt;
&lt;td&gt;Trustlet-managed VTL1 store, sealed under VSM master key&lt;/td&gt;
&lt;td&gt;Re-derive on next logon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Every shipping Windows credential primitive that &lt;em&gt;just works&lt;/em&gt; across multiple devices, multiple service hosts, or multiple cold-boot cycles -- without per-application key-management code -- is sitting on the same KDS root key. The architectural bet is that the root key never leaks. The next two sections are about what happens when it does.&lt;/p&gt;
&lt;h2&gt;9. The Five Structural Ceilings DPAPI Cannot Close&lt;/h2&gt;
&lt;p&gt;A reader who has followed the chain through §§3-8 has earned the right to a sharp question: where does this whole architecture &lt;em&gt;fail&lt;/em&gt;? Not where does it have bugs (it has very few cryptographic ones); where does the &lt;em&gt;design&lt;/em&gt; draw a line that no implementation patch can move? There are exactly five such lines.&lt;/p&gt;
&lt;h3&gt;9.1 The user-context ceiling&lt;/h3&gt;
&lt;p&gt;Any process running as the user can call &lt;code&gt;CryptUnprotectData&lt;/code&gt; [@ms-cryptprotectdata] or &lt;code&gt;NCryptUnprotectSecret&lt;/code&gt; [@ms-ncryptprotectsecret] against any blob the user could decrypt. The chain binds the secret to the &lt;em&gt;user identity&lt;/em&gt;, not the &lt;em&gt;consuming process identity&lt;/em&gt;. This is the structural reason every modern browser-cookie-stealer family (Lokibot, Vidar, RedLine, Lumma, StealC) works against DPAPI-protected Chrome state keys; it is also the reason Google introduced Chrome app-bound encryption [@thn-app-bound] in July 2024 &lt;em&gt;outside&lt;/em&gt; DPAPI. Will Harris of the Chrome security team is verbatim about why the patch had to live outside DPAPI rather than inside it: &lt;em&gt;&quot;On Windows, Chrome uses the Data Protection API (...). However, the DPAPI does not protect against malicious applications able to execute code as the logged in user -- which info-stealers take advantage of.&quot;&lt;/em&gt;&lt;/p&gt;

On Windows, Chrome uses the Data Protection API (DPAPI). However, the DPAPI does not protect against malicious applications able to execute code as the logged in user -- which info-stealers take advantage of.
-- Will Harris, Chrome security team, July 2024
&lt;h3&gt;9.2 The single-password-derivation ceiling&lt;/h3&gt;
&lt;p&gt;The classic-DPAPI master-key wrap is &lt;code&gt;PBKDF2-HMAC-SHA512(SHA1(NT-hash), SID-as-UTF16LE, ~8000 iterations)&lt;/code&gt; in the modern (Windows 10/11) era per the Passcape table [@passcape-master-key-analysis]. 8 000 iterations of HMAC-SHA-512 is not strong against a modern wordlist attack on a leaked NT hash. The structural limit is &lt;em&gt;the user&apos;s password&lt;/em&gt;, not the cryptographic primitive. The KDF parameter is tunable; the &lt;em&gt;single secret in the chain&lt;/em&gt; is not -- a strong password makes the chain strong, a weak password makes the chain weak, and there is no architectural way to recover from a weak password short of re-deriving every user&apos;s master key under a stronger one.&lt;/p&gt;
&lt;h3&gt;9.3 The KDS-root-key irrotability ceiling&lt;/h3&gt;
&lt;p&gt;Once a KDS root key is in production use, rotating it would invalidate every gMSA &lt;code&gt;msDS-ManagedPassword&lt;/code&gt;, every dMSA password, every &lt;code&gt;SID=&lt;/code&gt; blob, every Hello-for-Business software-KSP container, and every Credential-Guard isolated-secret blob ever produced under it. Microsoft&apos;s documented mitigation is &lt;em&gt;preventative&lt;/em&gt; (a system access control list on &lt;code&gt;msKds-RootKeyData&lt;/code&gt;), not recoverable. This is the same structural ceiling that the Microsoft Learn DPAPI-backup-keys page [@ms-cng-dpapi-backup-keys] admits for the older &lt;code&gt;[MS-BKRP]&lt;/code&gt; keys, in identical language: &lt;em&gt;&quot;There currently is no officially supported way of changing or rotating these DPAPI backup keys on the domain controllers.&quot;&lt;/em&gt; Burn-the-forest-and-rebuild for &lt;code&gt;[MS-BKRP]&lt;/code&gt;; SACL-the-attribute-and-hope for KDS root keys.&lt;/p&gt;
&lt;h3&gt;9.4 The hibernation / S4 ceiling&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CryptProtectMemory&lt;/code&gt; / &lt;code&gt;CryptUnprotectMemory&lt;/code&gt; [@ms-cryptprotectmemory] provide an in-memory scrub primitive that scopes a secret across same-process / cross-process / cross-session lifetimes. They cannot scrub RAM written to &lt;code&gt;hiberfil.sys&lt;/code&gt; on suspend-to-disk. On resume the master key is in plaintext in the page cache. The structural defence is BitLocker on the system volume (the &lt;em&gt;BitLocker on Windows&lt;/em&gt; article in this series covers full-volume encryption end-to-end); within DPAPI itself the ceiling cannot move because the OS has to be able to resume.&lt;/p&gt;
&lt;h3&gt;9.5 The domain-backup-key concentration ceiling&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;[MS-BKRP]&lt;/code&gt;&lt;/a&gt; backup key is the recoverability story for the password-reset case -- and it is &lt;em&gt;also&lt;/em&gt; the structural reason any DA who can dump LSASS on a writable DC has every user&apos;s master key in the forest. &lt;code&gt;mimikatz &quot;lsadump::backupkeys /system:dc.contoso.local /export&quot;&lt;/code&gt; is the canonical primitive, documented in the Mimikatz DPAPI wiki [@mimikatz-dpapi-wiki]. The architectural answer (HSM-backing the DC&apos;s RSA private key) is not in Microsoft&apos;s mainline guidance; the recommendation when these keys are compromised [@ms-cng-dpapi-backup-keys] is the burn-the-forest-and-rebuild line we just quoted.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; These five ceilings are not bugs. They are the design&apos;s price for the design&apos;s other guarantees. The user-context ceiling buys ubiquitous adoption. The single-password ceiling buys a usable recovery path. The KDS-root-key ceiling buys cross-DC determinism. The hibernation ceiling buys process performance and resumability. The domain-backup-key ceiling buys enterprise recovery. Every one of the next five years&apos; DPAPI incidents will hit one of these five ceilings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The 2022-2025 disclosures are not five surprises. They are five different attackers naming five different ceilings out loud. The next section walks the named incidents one ceiling at a time.&lt;/p&gt;
&lt;h2&gt;10. The 2022-2025 Residual Class: Golden gMSA, Golden dMSA, and Chromium App-Bound Encryption&lt;/h2&gt;
&lt;p&gt;In March 2022, Yuval Gordon (Microsoft Security Researcher, via Semperis blog) published &lt;em&gt;Introducing the Golden GMSA Attack&lt;/em&gt; [@semperis-golden-gmsa] and the &lt;code&gt;GoldenGMSA&lt;/code&gt; [@goldengmsa-repo] C# tool. In July 2024, Google announced Chrome&apos;s app-bound encryption [@google-security-blog-app-bound]. In July 2025, Adi Malyanker (also Semperis) published &lt;em&gt;Golden dMSA: What Is dMSA Authentication Bypass?&lt;/em&gt; [@semperis-golden-dmsa] and the &lt;code&gt;GoldenDMSA&lt;/code&gt; [@goldendmsa-repo] tool, mirrored on PR Newswire with a July 16, 2025 dateline [@prnewswire-semperis-dmsa]. Three disclosures in three years, three different ceilings -- but the same underlying pattern: each one is an admission that the structural ceiling cannot be patched inside DPAPI.&lt;/p&gt;

flowchart LR
    C1[&quot;§9.1 user-context&quot;] --&amp;gt; Chromium[&quot;Chromium app-bound encryption&lt;br /&gt;(July 2024, Will Harris)&quot;]
    C2[&quot;§9.3 KDS-root-key irrotability&quot;] --&amp;gt; GGMSA[&quot;Golden gMSA&lt;br /&gt;(March 2022, Y. Gordon)&quot;]
    C2 --&amp;gt; GDMSA[&quot;Golden dMSA&lt;br /&gt;(July 2025, A. Malyanker)&quot;]
    C3[&quot;§9.5 domain-backup-key&quot;] --&amp;gt; MimikatzBKK[&quot;mimikatz lsadump::backupkeys&quot;]
    C4[&quot;§9.4 hibernation / S4&quot;] --&amp;gt; BL[&quot;BitLocker article cross-link&quot;]
&lt;h3&gt;10.1 Golden gMSA (Yuval Gordon, Microsoft / Semperis blog, March 2022)&lt;/h3&gt;
&lt;p&gt;Targets the §9.3 KDS-root-key irrotability ceiling. Gordon is verbatim about the two-step nature of the attack: &lt;em&gt;&quot;An attacker with high privileges can obtain all the ingredients for generating the password of any gMSA in the domain at any time with two steps: Retrieve several attributes from the KDS root key in the domain... Use the GoldenGMSA tool to generate the password of any gMSA associated with the key, without a privileged account.&quot;&lt;/em&gt; Step 1 is a one-shot read of the KDS root key attributes. Step 2 is offline derivation. There is no further DC interaction, ever, because the &lt;a href=&quot;https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;[MS-GKDI]&lt;/code&gt; chain is deterministic&lt;/a&gt; by NIST SP 800-108r1 design.&lt;/p&gt;
&lt;p&gt;The defensive answer is in the GoldenGMSA repository [@goldengmsa-repo]: &lt;em&gt;&quot;configure a SACL on the KDS root key objects for everyone reading the msKds-RootKeyData attribute. Once the system access control list (SACL) is configured, any attempt to dump the key data of a KDS root key will generate security event 4662 on the DC where the object type is msKds-ProvRootKey and the account name is not a DC.&quot;&lt;/em&gt; Plus the cross-trust SACL on &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; with property GUID &lt;code&gt;{0e78295a-c6d3-0a40-b491-d62251ffa0a6}&lt;/code&gt;. This is a &lt;em&gt;detective&lt;/em&gt; control, not a &lt;em&gt;recovery&lt;/em&gt; control. Once the root key has been read, every gMSA password the forest has ever issued or will ever issue under that root key is offline-derivable.&lt;/p&gt;

An attacker with high privileges can obtain all the ingredients for generating the password of any gMSA in the domain at any time with two steps.
-- Yuval Gordon, Microsoft / Semperis blog, March 2022
&lt;p&gt;The Golden gMSA SACL detects same-trust reads only. Cross-trust reads of &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; from a forest the auditing forest does not control are the documented detection blind-spot. If your gMSA-using forest has a one-way trust from a forest you do not own, you cannot see its reads.&lt;/p&gt;
&lt;h3&gt;10.2 Golden dMSA (Adi Malyanker, Semperis, July 2025)&lt;/h3&gt;
&lt;p&gt;Targets the §9.3 ceiling, plus a fresh dMSA-specific surface. Malyanker is verbatim about the structural flaw: &lt;em&gt;&quot;a critical design flaw: a structure that&apos;s used for the password-generation computation contains predictable time-based components with only 1,024 possible combinations, making brute-force password generation computationally trivial.&quot;&lt;/em&gt; The four-phase pipeline is enumerated in the GoldenDMSA repository [@goldendmsa-repo]: &lt;em&gt;&quot;Phase 1: Key Material Extraction (pre requirement of the attack) -- Dump the KDS Root Key from the DC. Phase 2: Enumerate dMSA accounts ... Phase 3: ManagedPasswordID guessing ... Phase 4: Password Generation.&quot;&lt;/em&gt; The tool exposes commands &lt;code&gt;wordlist&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;kds&lt;/code&gt;, &lt;code&gt;bruteforce&lt;/code&gt;, &lt;code&gt;compute&lt;/code&gt;, and &lt;code&gt;convert&lt;/code&gt; -- the operational vocabulary the four-phase pipeline needs.&lt;/p&gt;
&lt;p&gt;Semperis&apos; own rating is MODERATE with the explicit caveat &lt;em&gt;&quot;to exploit it, attackers must possess a KDS root key available only to only the most privileged accounts: root Domain Admins, Enterprise Admins, and SYSTEM.&quot;&lt;/em&gt; That is exactly the §9.3 ceiling re-stated. The novelty in dMSA is the 1 024-combination time-component flaw -- a design weakness on top of the structural ceiling, not a substitute for it.&lt;/p&gt;
&lt;h3&gt;10.3 Chromium app-bound encryption (Will Harris, Google Chrome, July 30, 2024)&lt;/h3&gt;
&lt;p&gt;Targets the §9.1 user-context ceiling. The Google Security Blog announcement [@google-security-blog-app-bound] describes a COM-elevation service that wraps the Chrome state key with both DPAPI &lt;em&gt;and&lt;/em&gt; a per-binary identity check the COM service enforces. The verbatim quote, mirrored via The Hacker News [@thn-app-bound]: &lt;em&gt;&quot;Because the app-bound service is running with system privileges, attackers need to do more than just coax a user into running a malicious app. Now, the malware has to gain system privileges, or inject code into Chrome, something that legitimate software shouldn&apos;t be doing.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The architecturally important word is &lt;em&gt;&quot;app-bound.&quot;&lt;/em&gt; Chromium&apos;s response &lt;em&gt;to the user-context ceiling&lt;/em&gt; lives outside DPAPI. DPAPI itself is unchanged. The 2024 patch is application-layer code-identity pinning -- exactly what Generation-1 Protected Storage&apos;s abandoned Authenticode-access-rule clause was supposed to be in 2000, exactly what Apple Keychain [@apple-platform-security-keychain] has shipped on macOS for over two decades, exactly what the §11 wishlist asks for.&lt;/p&gt;
&lt;h3&gt;10.4 The recurring pattern&lt;/h3&gt;
&lt;p&gt;Each disclosure does &lt;em&gt;not&lt;/em&gt; break a cryptographic primitive. Each is a re-statement of &quot;the design&apos;s ceiling is the design&apos;s ceiling.&quot; The defensive answers are &lt;em&gt;detection&lt;/em&gt; (SACL audit; cross-trust read alerting; binary-identity check) and &lt;em&gt;workaround at a higher layer&lt;/em&gt; (the COM-elevation service Chrome wraps DPAPI in), never &lt;em&gt;cryptographic strengthening of DPAPI itself&lt;/em&gt;. The 2026 reader&apos;s job is to recognise which ceiling each new incident hits.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Disclosure&lt;/th&gt;
&lt;th&gt;Ceiling hit&lt;/th&gt;
&lt;th&gt;Tool reference&lt;/th&gt;
&lt;th&gt;Defensive answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;td&gt;Golden gMSA (Y. Gordon, Microsoft / Semperis blog)&lt;/td&gt;
&lt;td&gt;§9.3 KDS irrotability&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GoldenGMSA&lt;/code&gt; [@goldengmsa-repo]&lt;/td&gt;
&lt;td&gt;SACL on &lt;code&gt;msKds-RootKeyData&lt;/code&gt;; Event 4662&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024&lt;/td&gt;
&lt;td&gt;Chromium app-bound encryption (W. Harris, Google)&lt;/td&gt;
&lt;td&gt;§9.1 user-context&lt;/td&gt;
&lt;td&gt;Chrome 127 release notes [@chromereleases-127-stable]&lt;/td&gt;
&lt;td&gt;COM-elevation per-binary identity check, outside DPAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;Golden dMSA (A. Malyanker, Semperis)&lt;/td&gt;
&lt;td&gt;§9.3 + dMSA &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; predictability&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GoldenDMSA&lt;/code&gt; [@goldendmsa-repo]&lt;/td&gt;
&lt;td&gt;SACL plus monitoring &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; brute-force&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;By 2026 the pattern is clear: DPAPI&apos;s structural ceilings produce a steady drip of disclosures, each defensively answerable but none cryptographically fixable inside DPAPI itself. The open question is what the &lt;em&gt;successor&lt;/em&gt; would even look like -- and that is the next section.&lt;/p&gt;
&lt;h2&gt;11. Open Problems&lt;/h2&gt;
&lt;p&gt;A few sharp open problems remain at the design layer -- problems that a future Generation 4 of the credential-vault tradition would have to solve. None of them is in Microsoft&apos;s published roadmap as of 2026.&lt;/p&gt;
&lt;h3&gt;KDS root-key rotation&lt;/h3&gt;
&lt;p&gt;A hybrid wrap-then-re-wrap-on-first-decrypt-under-new-root scheme could in principle restore rotation without invalidating existing blobs: every consumer would carry a tag indicating which root-key generation last unwrapped it; on first unwrap under a generation-N+1 root the system would re-wrap the consumer-side cache. No standard or product implements this today; no public proposal revises &lt;a href=&quot;https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;[MS-GKDI]&lt;/code&gt;&lt;/a&gt; to add it.&lt;/p&gt;
&lt;h3&gt;Code-identity pinning&lt;/h3&gt;
&lt;p&gt;Apple Keychain [@apple-platform-security-keychain] enforces &lt;em&gt;&quot;keychain items can be shared only between apps from the same developer ... enforced through code signing, provisioning profiles, and the Apple Developer Program.&quot;&lt;/em&gt; DPAPI / DPAPI-NG have no equivalent. A &lt;code&gt;CALLER_ATTRIBUTION=Publisher:&amp;lt;wdac-rule-hash&amp;gt;&lt;/code&gt; descriptor would, in principle, give Windows the same property -- the SHA-256 hash of the WDAC rule [@paragmali-com-app-ide] that authorises the calling binary, baked into the descriptor, checked at unwrap time. No one is shipping it. The 2024 Chromium app-bound encryption response [@thn-app-bound] is the application-layer workaround that proves the design gap is real.&lt;/p&gt;
&lt;h3&gt;Post-quantum migration&lt;/h3&gt;
&lt;p&gt;DPAPI-NG today uses RSA or ECDH key transport via CMS enveloped-content format (&lt;code&gt;CERTIFICATE=&lt;/code&gt; with RSA private keys, &lt;code&gt;SID=&lt;/code&gt; for group descriptors) per the protected-data-format [@ms-protected-data-format] reference. Both wrap algorithms are vulnerable to Shor&apos;s algorithm on a sufficiently large quantum computer, and the persistent on-disk blob format is the harvest-now-decrypt-later target -- a framing surfaced across NIST&apos;s broader post-quantum migration corpus, including the NIST PQC project page [@nist-pqc-project] and the linked NIST IR 8547 migration-timeline document. The migration story for the symmetric chain is comparatively easy (the SP800-108 KDF is parameterised; HMAC-SHA-512 has no quantum-cliff weakness for the relevant key sizes). The migration story for the public-key wrap is the hard part. NIST published FIPS 203 (ML-KEM) [@nist-fips-203] and FIPS 204 (ML-DSA) [@nist-fips-204] in August 2024; OpenSSH 9.9 [@openssh-9-9-release] (September 2024) and TLS deployments shipped hybrid post-quantum key exchange. Windows added experimental post-quantum TLS support in 2024 but has not yet announced ML-KEM CNG-DPAPI providers; see the companion &lt;em&gt;Post-Quantum Cryptography on Windows&lt;/em&gt; [@paragmali-com-year-migrati] article in this series for the broader Windows migration story. The shape that fits DPAPI&apos;s ceiling-laden design is hybrid wrap-then-re-wrap-on-first-decrypt: a hybrid wrap (&lt;code&gt;RSA-OAEP || ML-KEM&lt;/code&gt; or &lt;code&gt;ECDH || ML-KEM&lt;/code&gt;) -- protect under (RSA+ML-KEM) or (ECDH+ML-KEM) today; let consumers re-wrap to the new combiner on first unwrap; phase out the classical half on a long horizon -- is the only forward-compatible answer; Microsoft&apos;s published DPAPI-NG protection-providers [@ms-protection-providers] have not yet announced one.&lt;/p&gt;
&lt;h3&gt;Credential roaming for Entra-joined-only / unmanaged-device estates&lt;/h3&gt;
&lt;p&gt;Classic DPAPI&apos;s cross-device story has always been &quot;use roaming profiles&quot; (deprecated) or &quot;use the &lt;a href=&quot;https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;[MS-BKRP]&lt;/code&gt;&lt;/a&gt; backup key&quot; (admin-recovery, not user-driven). DPAPI-NG&apos;s &lt;code&gt;SID=&lt;/code&gt; solves it cleanly for AD-joined estates but the modern Entra-only estate has no native equivalent -- the &lt;code&gt;LOCAL=user&lt;/code&gt; descriptor is per-machine. An &lt;code&gt;ENTRAGROUP=&amp;lt;object-id&amp;gt;&lt;/code&gt; descriptor that resolved through Entra ID Token Service the way &lt;code&gt;SID=&lt;/code&gt; resolves through KDS would close the gap. No public roadmap announces this.&lt;/p&gt;
&lt;h3&gt;Hibernation / S4&lt;/h3&gt;
&lt;p&gt;BitLocker [@wikipedia-bitlocker] on the system volume defends the disk; suspending fully to ROM (or refusing S4 entirely) defends the in-RAM master key. Hardware-bound key derivation (TPM-released-only-while-PCRs-stable) would close more of the gap; the &lt;em&gt;TPM in Windows&lt;/em&gt; article in this series covers TPM-bound primitives that approximate this property.&lt;/p&gt;
&lt;p&gt;Every one of these is a &lt;em&gt;design&lt;/em&gt; gap -- a property the architecture would need a new primitive to satisfy. None is on Microsoft&apos;s announced roadmap. The architecture we have is the architecture we will have for at least the next five years; the practitioner&apos;s job is to know which ceilings their estate is exposed to and how to detect each of them.&lt;/p&gt;
&lt;h2&gt;12. Practical Guide and Closing&lt;/h2&gt;
&lt;p&gt;The four-audience guide for the 2026 practitioner.&lt;/p&gt;
&lt;h3&gt;12.1 For a developer&lt;/h3&gt;
&lt;p&gt;Use &lt;code&gt;CryptProtectData&lt;/code&gt; / &lt;code&gt;CryptUnprotectData&lt;/code&gt; [@ms-cryptprotectdata] for per-user-on-this-device secrets. Pass &lt;code&gt;pOptionalEntropy&lt;/code&gt; to &lt;em&gt;bind&lt;/em&gt; the blob to a per-application secret -- but understand it is security-by-obscurity, not a code-identity check; any reader of the SharpDPAPI source [@sharpdpapi-readme] who knows your constant entropy can reproduce the unprotect call as the user.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;NCryptProtectSecret&lt;/code&gt; [@ms-ncryptprotectsecret] with the appropriate descriptor for cross-device or multi-principal cases. &lt;code&gt;LOCAL=user&lt;/code&gt; mirrors classic DPAPI on a single machine. &lt;code&gt;SID=&amp;lt;group-sid&amp;gt;&lt;/code&gt; reaches AD groups via KDS. &lt;code&gt;CERTIFICATE=HashID:&amp;lt;sha1&amp;gt;&lt;/code&gt; reaches a named certificate (TPM-backed for the high-security case). Use the WebAuthn / FIDO2 path for &lt;em&gt;authentication&lt;/em&gt; secrets; do not store passwords in DPAPI when WHfB / passkey paths are available.&lt;/p&gt;
&lt;p&gt;{`
// Returns the appropriate DPAPI-NG protection-descriptor string for a use case.
// Reference: learn.microsoft.com windows win32 seccng protection-descriptors
function descriptorFor(useCase, ctx) {
  switch (useCase) {
    case &quot;single-device-single-user&quot;:
      return &quot;LOCAL=user&quot;;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;case &quot;ad-group&quot;:
  // Multiple machines, AD-authorized group members can decrypt.
  return &quot;SID=&quot; + ctx.groupSid;

case &quot;tpm-backed-cert&quot;:
  // Decrypter must hold the named certificate&apos;s private key (TPM-bound KSP).
  return &quot;CERTIFICATE=HashID:&quot; + ctx.certThumbprintSha1;

case &quot;web-credential&quot;:
  // Resolves through the Windows credential broker.
  return &quot;WEBCREDENTIALS=&quot; + ctx.credName;

default:
  throw new Error(&quot;Unknown use case: &quot; + useCase);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  }
}&lt;/p&gt;
&lt;p&gt;console.log(descriptorFor(&quot;single-device-single-user&quot;));
console.log(descriptorFor(&quot;ad-group&quot;, { groupSid: &quot;S-1-5-21-...-5101&quot; }));
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;pOptionalEntropy&lt;/code&gt; parameter to &lt;code&gt;CryptProtectData&lt;/code&gt; lets you tag a blob with an extra secret the unwrap call must supply. It does not bind the blob to a process or a publisher. If your &quot;entropy&quot; is a constant compiled into your binary, every reverse-engineer who reads the binary can reproduce your unprotect call as the user. For real per-application separation today, use the Chromium 2024 pattern [@thn-app-bound]: wrap your DPAPI / DPAPI-NG blob in a SYSTEM-elevated COM service that enforces a per-binary identity check.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;12.2 For a defender or DFIR analyst&lt;/h3&gt;
&lt;p&gt;Triage the credential-vault inventory in §5 first. The high-value paths are &lt;code&gt;%APPDATA%\Microsoft\Protect\&amp;amp;lt;SID&amp;amp;gt;\&lt;/code&gt;, &lt;code&gt;%APPDATA%\Microsoft\Protect\CREDHIST&lt;/code&gt;, &lt;code&gt;%APPDATA%\Microsoft\Credentials\&lt;/code&gt;, &lt;code&gt;%LOCALAPPDATA%\Microsoft\Vault\&lt;/code&gt;, the Chromium / Edge profile databases, and the AD &lt;code&gt;CN=Master Root Keys,...&lt;/code&gt; container.&lt;/p&gt;
&lt;p&gt;The SACL guidance from the GoldenGMSA repository [@goldengmsa-repo] is the only detective control today: &lt;em&gt;&quot;configure a SACL on the KDS root key objects for everyone reading the msKds-RootKeyData attribute. Once the system access control list (SACL) is configured, any attempt to dump the key data of a KDS root key will generate security event 4662 on the DC where the object type is msKds-ProvRootKey and the account name is not a DC.&quot;&lt;/em&gt; Plus the cross-trust SACL on &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; with property GUID &lt;code&gt;{0e78295a-c6d3-0a40-b491-d62251ffa0a6}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Tooling: Mimikatz [@mimikatz-dpapi-wiki] &lt;code&gt;dpapi::*&lt;/code&gt; modules, SharpDPAPI [@sharpdpapi-readme], DPAPIck [@bursztein-talk-page] (Bursztein-Picod 2010), GoldenGMSA [@goldengmsa-repo], GoldenDMSA [@goldendmsa-repo], and the Volatility [@volatility3-repo] &lt;code&gt;lsadump&lt;/code&gt; / &lt;code&gt;cachedump&lt;/code&gt; / &lt;code&gt;hashdump&lt;/code&gt; plugins for live-memory extraction of &lt;code&gt;DPAPI_SYSTEM&lt;/code&gt; and the other LSA secrets that seed SYSTEM-context master-key derivation.&lt;/p&gt;
&lt;p&gt;{`&lt;/p&gt;
Walk a synthetic profile-directory layout and emit a DPAPI-relevant triage report.
&lt;p&gt;import os
from collections import defaultdict&lt;/p&gt;
Synthetic profile layout for demonstration only.
&lt;p&gt;synthetic = {
    &quot;Users/alice/AppData/Roaming/Microsoft/Protect/S-1-5-21-1234-1001/Preferred&quot;: &quot;8KB&quot;,
    &quot;Users/alice/AppData/Roaming/Microsoft/Protect/S-1-5-21-1234-1001/0d4a...&quot;: &quot;740B&quot;,
    &quot;Users/alice/AppData/Roaming/Microsoft/Protect/CREDHIST&quot;: &quot;176B&quot;,
    &quot;Users/alice/AppData/Roaming/Microsoft/Credentials/abcdef...&quot;: &quot;300B&quot;,
    &quot;Users/alice/AppData/Local/Microsoft/Vault/4BF4C442-9B8A-41A0-B380-DD4A704DDB28/Policy.vpol&quot;: &quot;180B&quot;,
    &quot;Users/alice/AppData/Local/Google/Chrome/User Data/Default/Cookies&quot;: &quot;4MB&quot;,
    &quot;Users/alice/AppData/Local/Google/Chrome/User Data/Local State&quot;: &quot;12KB&quot;,
}&lt;/p&gt;
&lt;p&gt;categories = {
    &quot;Master keys&quot;:          &quot;/Microsoft/Protect/S-1-&quot;,
    &quot;CREDHIST chain&quot;:       &quot;/Protect/CREDHIST&quot;,
    &quot;Credential Manager&quot;:   &quot;/Microsoft/Credentials/&quot;,
    &quot;Windows Vault&quot;:        &quot;/Microsoft/Vault/&quot;,
    &quot;Chrome state key&quot;:     &quot;/Google/Chrome/User Data/Local State&quot;,
    &quot;Chrome cookies&quot;:       &quot;/Google/Chrome/User Data/Default/Cookies&quot;,
}&lt;/p&gt;
&lt;p&gt;report = defaultdict(list)
for path, size in synthetic.items():
    for label, marker in categories.items():
        if marker in path:
            report[label].append((path, size))&lt;/p&gt;
&lt;p&gt;for label, items in report.items():
    print(&quot;==&quot;, label)
    for path, size in items:
        print(&quot;  &quot;, path, &quot;(&quot; + size + &quot;)&quot;)
`}&lt;/p&gt;
&lt;h3&gt;12.3 For a red-team operator&lt;/h3&gt;
&lt;p&gt;The chain of primitives most-commonly used (verbatim from the harmj0y operational guide [@harmj0y-operational-guide] and the Mimikatz wiki [@mimikatz-dpapi-wiki]):&lt;/p&gt;

The operational vocabulary, in order of dependency:&lt;ol&gt;
&lt;li&gt;&lt;code&gt;mimikatz &quot;sekurlsa::dpapi&quot;&lt;/code&gt; -- enumerate cached master keys from a live &lt;code&gt;lsass.exe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mimikatz &quot;dpapi::masterkey /in:&amp;lt;MK&amp;gt; /sid:&amp;amp;lt;SID&amp;amp;gt; /password:&amp;lt;known&amp;gt;&quot;&lt;/code&gt; -- unwrap a master-key file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mimikatz &quot;dpapi::cred /in:&amp;lt;credfile&amp;gt;&quot;&lt;/code&gt; -- decrypt a Credential Manager entry.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mimikatz &quot;lsadump::backupkeys /system:dc.contoso.local /export&quot;&lt;/code&gt; -- export the &lt;a href=&quot;https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;[MS-BKRP]&lt;/code&gt;&lt;/a&gt; RSA private key from a writable DC.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SharpDPAPI triage /pvk:key.pvk&lt;/code&gt; -- offline triage with the domain backup key.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SharpChrome cookies /pvk:key.pvk&lt;/code&gt; -- decrypt Chrome / Edge cookies offline.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GoldenGMSA gmsainfo&lt;/code&gt; then &lt;code&gt;GoldenGMSA compute -k &amp;lt;root-key-guid&amp;gt; -s &amp;lt;gmsa-sid&amp;gt; -m &amp;lt;managed-password-id&amp;gt;&lt;/code&gt; -- offline gMSA password derivation per the March 2022 disclosure [@semperis-golden-gmsa].&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GoldenDMSA wordlist&lt;/code&gt; / &lt;code&gt;bruteforce&lt;/code&gt; / &lt;code&gt;compute&lt;/code&gt; -- the four-phase Server 2025 dMSA pipeline per the July 2025 disclosure [@semperis-golden-dmsa].&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;The post-Credential-Guard scope reality: LSASS-isolated NT-hashes are gone (the &lt;em&gt;Credential Guard&lt;/em&gt; article in this series covers what &lt;code&gt;LsaIso.exe&lt;/code&gt; actually computes); on-disk DPAPI master keys, Chrome cookies, and Vault credentials are still there. The credential-vault inventory in §5 is the operator&apos;s map; the §10 disclosure list is the operator&apos;s playbook.&lt;/p&gt;
&lt;h3&gt;12.4 For a platform or identity engineer&lt;/h3&gt;
&lt;p&gt;Provision the KDS root key carefully. Use the &lt;code&gt;Add-KdsRootKey&lt;/code&gt; [@ms-add-kdsrootkey] default 10-day &lt;code&gt;EffectiveTime&lt;/code&gt; for production forests so AD replication converges before any consumer derives against the new key; the &lt;code&gt;-EffectiveTime ((Get-Date).AddHours(-10))&lt;/code&gt; override is for single-DC test forests only, never production.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The Golden gMSA defensive answer is detective, not preventive. Configure the &lt;code&gt;msKds-RootKeyData&lt;/code&gt; SACL &lt;em&gt;before&lt;/em&gt; any production gMSA exists, so every read of the root-key attributes generates Security Event 4662 and you have a baseline of &quot;DC accounts only, no humans, ever.&quot; Add the &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; cross-trust audit on day 1 too. After-the-fact SACL provisioning leaves a window during which the key may have been read silently.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For Server 2025 dMSA, monitor the &lt;code&gt;msDS-ManagedPasswordId&lt;/code&gt; [@ms-ms-adts-managedpassword] brute-force surface until Microsoft addresses the time-component predictability the Golden dMSA disclosure [@semperis-golden-dmsa] named.&lt;/p&gt;
&lt;p&gt;For Hello for Business, prefer the TPM-bound KSP (&lt;code&gt;MS_PLATFORM_CRYPTO_PROVIDER&lt;/code&gt;); the software-KSP DPAPI-NG fallback [@ms-whfb-howitworks] is the structural worst case (per §8.3) and inherits the KDS root-key dependency on every TPM-less device.&lt;/p&gt;
&lt;p&gt;Cross-platform context: Apple Keychain [@apple-platform-security-keychain] reaches a stronger upper bound (Secure-Enclave-bound + code-identity-pinned via the Apple Developer Program); GNOME libsecret [@libsecret-reference] covers the analogous Linux primitive over the Secret Service D-Bus interface. Neither is a drop-in replacement; both have shapes worth borrowing if Microsoft ever publishes the Generation-4 design.&lt;/p&gt;
&lt;h3&gt;12.5 The closing reflection&lt;/h3&gt;
&lt;p&gt;The credential vault under everything has a single-sentence summary in 2026: classic DPAPI is as strong as the user&apos;s password; DPAPI-NG is as strong as the KDS root key&apos;s life-cycle SOP is; both architectures admit ceilings the cryptography cannot move. The literacy a practitioner needs is the ability to recognise which ceiling any new incident hits. Twelve sections later, you have it.&lt;/p&gt;
&lt;h2&gt;Frequently Asked Questions&lt;/h2&gt;

No. Credential Guard [@ms-credential-guard] protects LSA-isolated secrets only. Chrome cookies live in the user&apos;s profile under DPAPI / DPAPI-NG and are decryptable by any process running as the user, exactly as before. The Chromium 2024 app-bound encryption [@thn-app-bound] is a per-process workaround for the §9.1 user-context ceiling, not a fix inside DPAPI itself.

A web reset of a *consumer* Microsoft Account password does not append to CREDHIST and does not benefit from [`[MS-BKRP]`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-bkrp/) backup -- there is no domain in the consumer scenario. Master keys encrypted under the previous password become irrecoverable for consumer Microsoft Accounts, full stop. Domain-joined enterprise users escape this because the domain backup key still works.

True if the KDS root key [@ms-add-kdsrootkey] is intact -- the Semperis write-up [@semperis-golden-gmsa] records the *&quot;randomly generated password of 256 bytes, making it infeasible to crack&quot;* claim. False under the Golden gMSA / Golden dMSA [@semperis-golden-dmsa] assumption that any DA / SYSTEM on a DC can read `msKds-RootKeyData`. A 256-byte random password is irrelevant if the attacker can derive it offline.

No. DPAPI-NG [@ms-cng-dpapi] is a redesign whose protection model is *descriptor-based* (multi-principal, multi-device) rather than *user-and-machine-bound*. The two APIs coexist; classic DPAPI is still the default for `CryptProtectData` [@ms-cryptprotectdata] callers, and DPAPI-NG is the path for `NCryptProtectSecret` [@ms-ncryptprotectsecret] callers.

No. On TPM-less devices the WHfB private key sits in a CNG software-KSP container persisted as a DPAPI-NG blob whose protection descriptor binds it to user SID + device, per the Hello for Business architecture [@ms-whfb-howitworks]. The TPM-bound case is the preferred deployment; the software-KSP fallback is the structural worst case and inherits the §9.3 KDS root-key dependency.

No. `CryptProtectMemory` [@ms-cryptprotectmemory] scrubs in-memory secrets between same-process / cross-process / cross-session lifetimes but cannot prevent the OS from writing the page-protected RAM into `hiberfil.sys` on suspend-to-disk. BitLocker on the system volume is the structural defence (the *BitLocker on Windows* article in this series covers full-volume encryption end-to-end).

No. It broke the *secrecy of DPAPI&apos;s design*. The 2010 disclosure [@usenix-woot10] made the master-key chain public and tractable for offline forensics; it did not weaken the cryptography. The &quot;break&quot; was always structural -- DPAPI is as strong as the user&apos;s password is. The two-author byline is Bursztein and Picod (Black Hat DC 2010, USENIX WOOT 10), not &quot;Bursztein, Picod and Aussel.&quot;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;dpapi-and-dpapi-ng-the-credential-vault-under-everything&quot; keyTerms={[
  { term: &quot;DPAPI&quot;, definition: &quot;The Data Protection API; the per-user / per-machine secret-storage primitive in every Windows release from Windows 2000 onward.&quot; },
  { term: &quot;Master key&quot;, definition: &quot;A 64-byte random secret per user, stored under %APPDATA%/Microsoft/Protect/&amp;lt;SID&amp;gt;/&amp;lt;GUID&amp;gt;, encrypted under a pre-key derived from the user&apos;s password and SID.&quot; },
  { term: &quot;[MS-BKRP] BackupKey Remote Protocol&quot;, definition: &quot;The LSASS-hosted RPC interface that lets a member computer dual-wrap its master key under both the user password pre-key and a DC RSA backup public key; the canonical universal-decryption primitive available to Domain Admins.&quot; },
  { term: &quot;CREDHIST&quot;, definition: &quot;The previous-password hash chain stored in %APPDATA%/Microsoft/Protect/CREDHIST; one entry per self-initiated password change; broken by administrative reset and consumer-Microsoft-Account web reset.&quot; },
  { term: &quot;Protection descriptor (DPAPI-NG)&quot;, definition: &quot;The DPAPI-NG self-describing string (SID, SDDL, LOCAL, WEBCREDENTIALS, CERTIFICATE) that names the set of principals permitted to remove protection from a blob.&quot; },
  { term: &quot;Microsoft Key Distribution Service (kdssvc.dll)&quot;, definition: &quot;The DC-side daemon that implements the [MS-GKDI] protocol and computes per-(group, period) keys deterministically from a single forest-wide root key.&quot; },
  { term: &quot;KDS root key&quot;, definition: &quot;The single forest-wide secret that anchors every per-(group, period) key the KDS will ever derive; provisioned exactly once per forest with Add-KdsRootKey; documented as having no rotation procedure.&quot; },
  { term: &quot;Group Managed Service Account (gMSA)&quot;, definition: &quot;A Server-2012-introduced AD account whose 256-byte password is derived from the KDS chain and rotated every 30 days, gated by the msDS-GroupMSAMembership SDDL.&quot; },
  { term: &quot;Software KSP&quot;, definition: &quot;The non-hardware-bound CNG Key Storage Provider that persists key material as DPAPI-NG-protected files; used as the WHfB fallback on TPM-less devices.&quot; },
  { term: &quot;Golden gMSA / Golden dMSA&quot;, definition: &quot;The 2022 / 2025 Semperis offline-derivation attacks that compute any gMSA / dMSA password the forest will ever issue, given a one-shot read of the four KDS root-key attributes.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>dpapi</category><category>dpapi-ng</category><category>kds</category><category>gmsa</category><category>credential-guard</category><category>mimikatz</category><category>golden-gmsa</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><item><title>&quot;Can This Code Do This?&quot; -- Twenty-Five Years of Attacks on the Windows Access-Control Model</title><link>https://paragmali.com/blog/windows-access-control-25-years-of-attacks/</link><guid isPermaLink="true">https://paragmali.com/blog/windows-access-control-25-years-of-attacks/</guid><description>How a single kernel function, SeAccessCheck, decides every Windows operation -- and how Mimikatz, the Potato lineage, and seventy UAC bypasses each attack one of its inputs.</description><pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate><content:encoded>
Windows answers the question *can this code do this?* with one kernel function, `SeAccessCheck`, evaluated against five inputs: a security descriptor, an access token, a desired-access mask, a generic-mapping table, and any previously-granted access. The function and its inputs have not structurally changed since July 27, 1993. Every famous Windows local-privilege-escalation tool of the last twenty-five years -- Mimikatz, JuicyPotato and seven other Potatoes, the seventy AutoElevate-redirect methods catalogued in UACMe -- attacks one of those inputs. This article tells that story as one system, names the five structural limits Microsoft has publicly conceded, and explains why Adminless, NTLMless, VBS Trustlets, and Credential Guard are the four non-overlapping ways to close them.
&lt;h2&gt;1. One Question, Billions of Times a Second&lt;/h2&gt;
&lt;p&gt;Open a Windows PowerShell window and run &lt;code&gt;whoami /priv&lt;/code&gt;. Read the column on the right. &lt;code&gt;SeShutdownPrivilege&lt;/code&gt;. &lt;code&gt;SeUndockPrivilege&lt;/code&gt;. &lt;code&gt;SeIncreaseWorkingSetPrivilege&lt;/code&gt;. &lt;code&gt;SeTimeZonePrivilege&lt;/code&gt;. About twenty rows of capabilities, almost all marked &lt;em&gt;Disabled&lt;/em&gt;, on a token that lives inside &lt;code&gt;explorer.exe&lt;/code&gt;&apos;s memory and that the kernel consults billions of times a second.&lt;/p&gt;
&lt;p&gt;Now run &lt;code&gt;icacls C:\Windows\System32\drivers\etc\hosts&lt;/code&gt;. The output reads &lt;code&gt;BUILTIN\Administrators:(F)&lt;/code&gt;, &lt;code&gt;NT AUTHORITY\SYSTEM:(F)&lt;/code&gt;, &lt;code&gt;BUILTIN\Users:(R)&lt;/code&gt;. Six characters per principal, decoded by something inside the kernel called &lt;code&gt;SeAccessCheck&lt;/code&gt;, applied to a data structure called a security descriptor, against a credential called an access token, every time any process anywhere on the machine asks for read access to that single file [@ms-learn-access-control].&lt;/p&gt;
&lt;p&gt;This article is about the model behind those two outputs. A model that has not structurally changed since July 27, 1993, when Windows NT 3.1 shipped from Redmond [@en-wiki-windows-nt-3-1]. A model that every famous Windows local-privilege-escalation tool of the last twenty-five years -- Mimikatz, JuicyPotato, fodhelper.exe, the seventy methods in the open-source UACMe catalogue -- exists to attack [@github-gentilkiwi-mimikatz, @github-ohpe-juicy-potato, @github-hfiref0x-uacme].&lt;/p&gt;
&lt;p&gt;The thesis comes in three convictions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SeAccessCheck&lt;/code&gt; is the answer.&lt;/strong&gt; Every securable Windows operation that touches a securable object resolves through one decision function with one set of inputs [@ms-learn-how-dacls-control-access].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Every famous Windows escalation tool attacks one of those inputs.&lt;/strong&gt; JuicyPotato attacks the token. Mimikatz attacks the privilege list. Fodhelper attacks the elevation flow that produces the token. HiveNightmare attacks the DACL on a single file [@nvd-cve-2021-36934]. The vocabulary scales.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The model has five structural limits its keepers have publicly conceded&lt;/strong&gt; [@msrc-servicing-criteria], and Adminless, NTLMless, VBS Trustlets, and Credential Guard are the four non-overlapping ways to close them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The forecast for the next 14,000 words: ten primitives (the Security Reference Monitor, security identifiers, tokens, security descriptors, DACLs, SACLs, ACEs, privileges, Mandatory Integrity Control, User Account Control), one canonical oracle (&lt;code&gt;SeAccessCheck&lt;/code&gt;), two attack families (the Potato lineage and the UACMe bypass tradition), and four successor architectures (Adminless, NTLMless, VBS Trustlets, Credential Guard).&lt;/p&gt;
&lt;p&gt;If the model has not structurally changed since 1993, why has it taken thirty-three years and seventy bypasses to map its failure modes -- and what does each generation tell us about the next?&lt;/p&gt;
&lt;h2&gt;2. Origins: From Lampson&apos;s Matrix to Cutler&apos;s Kernel (1971-1993)&lt;/h2&gt;
&lt;p&gt;The vocabulary starts in a paper Butler Lampson presented at Princeton in 1971 and the ACM republished in &lt;em&gt;Operating Systems Review&lt;/em&gt; in January 1974. Lampson framed protection as a 2-D matrix: rows index the &lt;em&gt;subjects&lt;/em&gt; (users, processes), columns index the &lt;em&gt;objects&lt;/em&gt; (files, devices, memory pages), and the cell at the intersection holds the &lt;em&gt;operations&lt;/em&gt; the subject is permitted on the object. The matrix is a sparse, mostly-empty table the size of every-process times every-file. No real system has ever stored it that way.&lt;/p&gt;
&lt;p&gt;Two implementation strategies fall out of the formalism. Slice the matrix by row and you get &lt;em&gt;capability lists&lt;/em&gt;: each subject carries a token that names the objects it can touch. Slice by column and you get &lt;em&gt;access-control lists&lt;/em&gt;: each object carries a list of subjects allowed to touch it. Lampson worked through both in the paper. Operating systems built on the second slice came to dominate, partly because hardware in 1971 made unforgeable capabilities expensive, partly because file systems could carry an ACL at the inode without changing every program. Decades later, the gap between the two implementations would still matter; we will reach Norm Hardy&apos;s &quot;Confused Deputy&quot; in a moment.&lt;/p&gt;
&lt;p&gt;The lever that turned theory into Windows came from procurement, not academia. On December 26, 1985, the U.S. Department of Defense published &lt;code&gt;DoD 5200.28-STD&lt;/code&gt;, the &lt;em&gt;Trusted Computer System Evaluation Criteria&lt;/em&gt;, known by the colour of its cover as the Orange Book [@nist-csrc-rainbow]. The Orange Book defined four divisions of trusted-system assurance, and its C2 class -- &quot;Controlled Access Protection&quot; -- made discretionary access control plus auditing a federal procurement floor. The September 30, 1987 Neon Orange Book (NCSC-TG-003) and the July 28, 1987 Tan Book (NCSC-TG-001) elaborated DAC and audit respectively [@nist-csrc-rainbow]. After 1985, no operating system that wanted U.S. federal customers could ship without per-user ACLs and an audit log.&lt;/p&gt;
&lt;p&gt;A year before the Orange Book ossified DAC into procurement, Norm Hardy of the Tymshare / KeyKOS lineage published a three-page paper in &lt;em&gt;Operating Systems Review&lt;/em&gt; that named the structural limit of the entire ACL-shaped class: &quot;The Confused Deputy (or why capabilities might have been invented)&quot; [@wayback-cap-lore-hardy]. Hardy described a privileged compiler that wrote billing records to a system file. A user could trick the compiler into writing the user&apos;s &lt;em&gt;output&lt;/em&gt; file over the billing file, because the compiler used &lt;em&gt;its own&lt;/em&gt; authority on every write and could not distinguish &quot;authority I have&quot; from &quot;authority the caller asked me to use.&quot;&lt;/p&gt;
&lt;p&gt;The Wikipedia summary of the field is exact: &quot;Capability systems protect against the confused deputy problem, whereas access-control list-based systems do not&quot; [@en-wiki-confused-deputy]. Hold this paper. It will come back in Section 10.&lt;/p&gt;

The team that built Windows NT was not assembled in Redmond. David Cutler arrived in October 1988 from Digital Equipment Corporation [@en-wiki-cutler], where he had led VMS and the cancelled Mica successor, and brought with him a fraction of his old DEC team.&lt;p&gt;The cultural import mattered: VAX/VMS, announced October 25, 1977 alongside the VAX-11/780 (V1.0 shipped August 1978) [@en-wiki-openvms], introduced UIC-based file protection and a kernel-mode security architecture, and by the mid-1980s the VAX/VMS line had been evaluated at TCSEC Class C2 [@en-wiki-openvms], by which time the system had been hardened with per-object ACLs, audit channels, and an explicit reference monitor. That C2-hardened VMS was the cultural reference Cutler brought with him to Microsoft. G. Pascal Zachary&apos;s &lt;em&gt;Showstopper!&lt;/em&gt; tells the story of the four-year build of NT 3.1 from that team [@showstopper-zachary].&lt;/p&gt;
&lt;p&gt;The point for this article is narrower. NT 3.1&apos;s nine access-control primitives -- Security Reference Monitor, security identifier, access token, security descriptor, DACL, SACL, ACE, privileges, audit channel -- did not arrive piecemeal. They were specified together, before the first line of &lt;code&gt;SeAccessCheck&lt;/code&gt; was written, against a procurement standard the team intended to clear.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;NT 3.1 released to manufacturing on July 27, 1993 [@en-wiki-windows-nt-3-1]. NT 3.5, released to manufacturing on September 21, 1994 [@en-wiki-nt-3-5], was hardened through Service Pack 3 (June 21, 1995) and was rated by the National Security Agency in July 1995 as complying with TCSEC C2 criteria against the standalone single-user configuration [@en-wiki-nt-3-5]; NT 4.0 Service Pack 6a was evaluated at TCSEC C2 in a standalone (non-networked) configuration, consistent with NT 3.5 SP3. The combination froze the model. Once C2 evaluation was on the books, structural changes to the access-control surface would have required re-evaluation. Federal procurement obligations have kept the structural shape intact for thirty-one years, even after the Department of Defense formally retired TCSEC in favour of the Common Criteria.&lt;/p&gt;
&lt;p&gt;Cutler shipped a model that has answered &quot;can this code do this?&quot; the same way for thirty-three years. What is the actual function -- and what are its inputs?&lt;/p&gt;
&lt;h2&gt;3. The Kernel Oracle: &lt;code&gt;SeAccessCheck&lt;/code&gt; and Its Inputs&lt;/h2&gt;
&lt;p&gt;The function has one signature, one return value, and one job. Microsoft&apos;s Win32 documentation exposes a user-mode mirror called &lt;code&gt;AccessCheck&lt;/code&gt; that lets userland code ask the question without holding a handle, and a kernel routine called &lt;code&gt;SeAccessCheck&lt;/code&gt; that the kernel invokes at every securable operation [@ms-learn-access-control, @ms-learn-access-tokens]. The shape is the same in both directions:&lt;/p&gt;
&lt;p&gt;$$
\textsf{SeAccessCheck}(\textit{SD}, \textit{Token}, \textit{DesiredAccess}) \to (\textit{GrantedAccess}, \textit{Status})
$$&lt;/p&gt;
&lt;p&gt;Three inputs in (a security descriptor, an access token, a requested-access mask), two outputs out (the access mask actually granted, and a &lt;code&gt;STATUS_ACCESS_DENIED&lt;/code&gt; flavour if any). Two more hidden inputs make the kernel signature precise: a &lt;em&gt;generic mapping&lt;/em&gt; table that translates the four generic rights (&lt;code&gt;GENERIC_READ&lt;/code&gt;, &lt;code&gt;GENERIC_WRITE&lt;/code&gt;, &lt;code&gt;GENERIC_EXECUTE&lt;/code&gt;, &lt;code&gt;GENERIC_ALL&lt;/code&gt;) into object-type-specific bits, and a &lt;em&gt;previously-granted-access&lt;/em&gt; mask that the kernel carries forward when an access check happens in two phases. Together: five inputs, one decision, one log entry (the documented kernel routine carries a few additional parameters of kernel-internal bookkeeping -- a synchronization flag, an &lt;code&gt;AccessMode&lt;/code&gt; discriminator that elides the check for kernel-mode callers, and a privileges out-parameter -- which the explanatory five-input model collapses; the user-mode &lt;code&gt;AccessCheck&lt;/code&gt; mirror is closer to the model the article uses).&lt;/p&gt;

The kernel-mode component of the Windows NT executive that performs all access checks against securable objects. It owns `SeAccessCheck` and the audit log generation routines. The SRM is a *subsystem*, not a feature: every other kernel component that needs to grant or deny access calls into it.

The Windows kernel routine that decides whether a thread may perform a requested set of operations on an object. It takes a security descriptor, an access token, a desired-access mask, a per-object-type generic-mapping table, and any previously-granted access. (The documented kernel signature also carries a synchronization flag, an `AccessMode` discriminator that elides the check for kernel-mode callers, and a privileges out-parameter; the five-input model used here is an explanatory simplification that the user-mode `AccessCheck` mirror tracks more closely.) It returns the subset of the desired-access mask that the kernel grants and a status code. Every call site that opens or modifies a securable object eventually reaches this function [@ms-learn-how-dacls-control-access].
&lt;p&gt;The five inputs are not equally exotic. The desired-access mask and the generic-mapping table are bookkeeping that an object type defines once at registration time. The previously-granted-access input is the kernel handing itself a pencil for two-phase access checks, mostly invisible to userland. The two inputs the rest of the article will keep returning to are the security descriptor and the access token.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;security descriptor&lt;/em&gt; is the data structure attached to the object. It carries an owner SID, a primary-group SID, a discretionary access-control list (DACL) of allow / deny / audit entries, and a system access-control list (SACL) holding audit and integrity-label entries [@ms-learn-mic]. The DACL is what &lt;code&gt;icacls&lt;/code&gt; prints. The SACL is what writes Event Log entries when something the descriptor&apos;s writer wanted to watch happens.&lt;/p&gt;
&lt;p&gt;An &lt;em&gt;access token&lt;/em&gt; is the data structure attached to the caller. It names the user (one SID), the user&apos;s groups (a list of SIDs), the privileges the user holds (a list of named superpowers), the integrity level the kernel will compare against the object&apos;s label, and a flag that says whether the token is a &lt;em&gt;primary&lt;/em&gt; token (one per process) or an &lt;em&gt;impersonation&lt;/em&gt; token (one per thread, used to act on a client&apos;s behalf) [@ms-learn-access-tokens].&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s documentation lists the token&apos;s contents almost as bullet points: &quot;The security identifier (SID) for the user&apos;s account; SIDs for the groups of which the user is a member; A logon SID that identifies the current logon session; A list of the privileges held by either the user or the user&apos;s groups; An owner SID; The SID for the primary group; The default DACL; ... Whether the token is a primary or impersonation token; An optional list of restricting SIDs; Current impersonation levels...&quot; [@ms-learn-access-tokens].&lt;/p&gt;
&lt;p&gt;The flow inside &lt;code&gt;SeAccessCheck&lt;/code&gt; is mechanical. The kernel maps &lt;code&gt;DesiredAccess&lt;/code&gt; from generic to specific bits using the type&apos;s mapping table. It checks the integrity label of the object against the integrity level on the token (Mandatory Integrity Control runs &lt;em&gt;before&lt;/em&gt; the DACL walk, a point Section 7 expands). It walks the DACL in order, deny-first, accumulating bits granted by allow ACEs whose SID is in the token&apos;s list. It applies a &lt;em&gt;privilege bypass&lt;/em&gt; short-circuit if the caller holds &lt;code&gt;SeBackupPrivilege&lt;/code&gt;, &lt;code&gt;SeRestorePrivilege&lt;/code&gt;, &lt;code&gt;SeDebugPrivilege&lt;/code&gt;, or one of the other privileges that grants access to whole classes of objects without consulting the DACL. It returns the accumulated &lt;code&gt;GrantedAccess&lt;/code&gt;, or &lt;code&gt;STATUS_ACCESS_DENIED&lt;/code&gt; if the requested bits were not all granted.&lt;/p&gt;

sequenceDiagram
    participant App as User-mode caller
    participant ObjMgr as Object Manager
    participant SRM as Security Reference Monitor
    participant Audit as Audit channel
    App-&amp;gt;&amp;gt;ObjMgr: OpenObject(name, DesiredAccess, ImpersonationToken)
    ObjMgr-&amp;gt;&amp;gt;ObjMgr: Resolve name (\BaseNamedObjects, \Device, \??)
    ObjMgr-&amp;gt;&amp;gt;ObjMgr: Fetch security descriptor from object header
    ObjMgr-&amp;gt;&amp;gt;SRM: SeAccessCheck(SD, Token, DesiredAccess)
    SRM-&amp;gt;&amp;gt;SRM: Map generic rights via type mapping
    SRM-&amp;gt;&amp;gt;SRM: Check mandatory integrity label
    SRM-&amp;gt;&amp;gt;SRM: Privilege-bypass short-circuit (SeBackup, SeRestore, SeDebug)
    SRM-&amp;gt;&amp;gt;SRM: Walk DACL in canonical order, deny-first
    SRM--&amp;gt;&amp;gt;ObjMgr: GrantedAccess + STATUS code
    ObjMgr-&amp;gt;&amp;gt;Audit: Emit SACL audit ACE if matched
    ObjMgr--&amp;gt;&amp;gt;App: HANDLE or STATUS_ACCESS_DENIED
&lt;p&gt;Five inputs. One function. One log entry on the way out. The thesis statement of this article is the consequence: every later section is an attack on one of those inputs. JuicyPotato attacks the token. Mimikatz attacks the privilege list. Fodhelper attacks the elevation flow that produces the token. HiveNightmare attacks the security descriptor on a single file. Conditional ACEs and Dynamic Access Control extend the matrix&apos;s &lt;em&gt;subject&lt;/em&gt; dimension by adding claims to the token. UAC tries to keep the token small by default and only inflate it on demand. Every primitive in the article maps cleanly onto one of &lt;code&gt;SeAccessCheck&lt;/code&gt;&apos;s five inputs, and every famous attack tool in the article maps cleanly onto one primitive.&lt;/p&gt;
&lt;p&gt;The function is fixed. The inputs are five. So how does the kernel actually walk a DACL -- and where does the wrong answer come from?&lt;/p&gt;
&lt;h2&gt;4. The DACL Algorithm and the SID Namespace&lt;/h2&gt;
&lt;p&gt;&quot;Walk the DACL in order.&quot; Six words that have generated hundreds of thousands of misconfigured ACLs since 1993. The Microsoft Learn page that ships the algorithm is short and exact. The system &quot;examines each ACE in sequence until... an access-denied ACE explicitly denies any of the requested access rights to one of the trustees listed in the thread&apos;s access token... one or more access-allowed ACEs for trustees listed in the thread&apos;s access token explicitly grant all the requested access rights... All ACEs have been checked and there is still at least one requested access right that has not been explicitly allowed, in which case, access is implicitly denied&quot; [@ms-learn-how-dacls-control-access].&lt;/p&gt;
&lt;p&gt;Three terminations. Deny terminates with denial. Enough allows terminates with grant. End-of-list with anything left ungranted terminates with denial. Note the asymmetry the algorithm encodes: a single deny anywhere in the DACL kills the request, but an allow has to be paired with explicit coverage of every desired bit. The default is denial.&lt;/p&gt;

The ordered list of access-control entries (ACEs) attached to a securable object&apos;s security descriptor that specifies which trustees are granted or denied which access rights. *Discretionary* means the object&apos;s owner controls the list, in contrast to a mandatory list whose entries the system enforces independent of owner intent.

A single grant, deny, audit, or mandatory-label record inside a DACL or SACL. Each ACE carries an SID identifying the trustee, a 32-bit access mask, a flags byte controlling inheritance, and a type discriminator. Windows defines four primary ACE types (`ACCESS_ALLOWED_ACE`, `ACCESS_DENIED_ACE`, `SYSTEM_AUDIT_ACE`, `SYSTEM_MANDATORY_LABEL_ACE`) plus callback variants for conditional ACEs [@ms-learn-ace-strings].
&lt;p&gt;{`
// Faithful implementation of the algorithm at
// learn.microsoft.com/en-us/windows/win32/secauthz/how-dacls-control-access-to-an-object
// Inputs: token (set of SIDs), DACL (ordered ACEs), desiredAccess mask.
// Output: granted mask + a per-ACE trace of why.&lt;/p&gt;
&lt;p&gt;function seAccessCheck(token, dacl, desiredAccess) {
  let remaining = desiredAccess;       // bits still needed
  let granted = 0;                     // bits accumulated so far
  const trace = [];&lt;/p&gt;
&lt;p&gt;  // NULL DACL grants everything; empty DACL grants nothing.
  if (dacl === null) {
    return { status: &apos;GRANTED (NULL DACL)&apos;, granted: desiredAccess, trace: [&apos;NULL DACL: full access&apos;] };
  }
  if (dacl.length === 0) {
    return { status: &apos;DENIED (empty DACL)&apos;, granted: 0, trace: [&apos;Empty DACL: no access&apos;] };
  }&lt;/p&gt;
&lt;p&gt;  for (let i = 0; i &amp;lt; dacl.length; i++) {
    const ace = dacl[i];
    const sidMatches = token.sids.includes(ace.sid);
    if (!sidMatches) {
      trace.push(`ACE ${i} (${ace.type} ${ace.sid}): SID not in token; skip`);
      continue;
    }
    if (ace.type === &apos;DENY&apos;) {
      const deniedBits = ace.mask &amp;amp; remaining;
      if (deniedBits !== 0) {
        trace.push(`ACE ${i}: DENY hits 0x${deniedBits.toString(16)} -&amp;gt; ACCESS_DENIED`);
        return { status: &apos;DENIED&apos;, granted: 0, trace };
      }
      trace.push(`ACE ${i}: DENY ${ace.sid} 0x${ace.mask.toString(16)} -- no requested bit hit; skip`);
    } else if (ace.type === &apos;ALLOW&apos;) {
      const newBits = ace.mask &amp;amp; remaining;
      granted |= newBits;
      remaining &amp;amp;= ~newBits;
      trace.push(`ACE ${i}: ALLOW grants 0x${newBits.toString(16)}, remaining 0x${remaining.toString(16)}`);
      if (remaining === 0) {
        return { status: &apos;GRANTED&apos;, granted, trace };
      }
    }
  }
  return { status: &apos;DENIED (end of DACL with bits unsatisfied)&apos;, granted: 0, trace };
}&lt;/p&gt;
&lt;p&gt;// Demo: a token with the user&apos;s SID and BUILTIN\Users; DACL with explicit deny + Everyone allow.
const token = { sids: [&apos;S-1-5-21-A-B-C-1001&apos;, &apos;S-1-5-32-545&apos;, &apos;S-1-1-0&apos;] };
const dacl = [
  { type: &apos;DENY&apos;,  sid: &apos;S-1-5-21-A-B-C-1001&apos;, mask: 0x00040000 },  // deny WRITE_DAC
  { type: &apos;ALLOW&apos;, sid: &apos;S-1-1-0&apos;,              mask: 0x00120089 }, // FILE_GENERIC_READ
  { type: &apos;ALLOW&apos;, sid: &apos;S-1-5-32-545&apos;,         mask: 0x001200A9 }, // GENERIC_READ + EXECUTE
];
console.log(seAccessCheck(token, dacl, 0x00040089));
`}&lt;/p&gt;
&lt;p&gt;The runnable above implements three subtleties the prose can flatten. First: the &lt;em&gt;NULL DACL&lt;/em&gt; versus &lt;em&gt;empty DACL&lt;/em&gt; distinction. A descriptor with no DACL at all -- a literal &lt;code&gt;NULL&lt;/code&gt; pointer where the list would be -- grants full access, on the theory that the writer expressed no policy and the kernel will not invent one. A descriptor with a DACL that exists but contains zero ACEs denies everything, because the writer expressed a policy and that policy has no allows. The single most common high-impact misconfiguration in the Windows codebase is code that meant to write the second and wrote the first, or vice versa.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Newly-written code that &quot;creates a file with no protection&quot; almost always wants an empty DACL but ends up with a NULL DACL because of how the SECURITY_DESCRIPTOR initialisation defaults work. Verify with &lt;code&gt;Get-Acl&lt;/code&gt; or &lt;code&gt;icacls&lt;/code&gt; after creation; a NULL DACL surface in &lt;code&gt;icacls&lt;/code&gt; looks like &lt;code&gt;Everyone:(F)&lt;/code&gt; and is almost always a bug, not a feature [@ms-learn-how-dacls-control-access].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Second: ACE &lt;em&gt;order&lt;/em&gt; is the caller&apos;s responsibility. The kernel walks the list in the order it finds it. The &quot;canonical&quot; order Windows expects is four-step, quoted verbatim from the Microsoft Learn reference [@ms-learn-order-of-aces]: &quot;1. All explicit ACEs are placed in a group before any inherited ACEs. 2. Within the group of explicit ACEs, access-denied ACEs are placed before access-allowed ACEs. 3. Inherited ACEs are placed in the order in which they are inherited... 4. For each level of inherited ACEs, access-denied ACEs are placed before access-allowed ACEs.&quot;&lt;/p&gt;
&lt;p&gt;The same page underlines who has to enforce the order: &quot;Functions such as &lt;code&gt;AddAccessAllowedAceEx&lt;/code&gt; and &lt;code&gt;AddAccessAllowedObjectAce&lt;/code&gt; add an ACE to the end of an ACL. It is the caller&apos;s responsibility to ensure that the ACEs are added in the proper order.&quot; If the writer of the DACL hands the kernel an out-of-order list with a deny ACE buried after a wide allow ACE, the deny will be unreachable and the descriptor will silently grant more than the writer intended.&lt;/p&gt;
&lt;p&gt;Third: there is no special case for &quot;Everyone.&quot; The well-known SID &lt;code&gt;S-1-1-0&lt;/code&gt; exists in every token of every process on the machine; an ACE against it applies to every caller. There is no extra logic that says &quot;if this is Everyone, treat it differently from any other group.&quot; James Forshaw made the point with characteristic bluntness in 2020: &quot;don&apos;t forget S-1-1-0, this is NOT A SECURITY BOUNDARY. Lah lah, I can&apos;t hear you!&quot; [@tiraniddo-sharing-logon-session]. The DACL evaluation algorithm does not know &quot;Everyone&quot; is special. It is a SID like any other.&lt;/p&gt;
&lt;p&gt;That makes the SID namespace itself worth a tour. Microsoft documents the structure as a revision number, an &lt;em&gt;identifier authority&lt;/em&gt; (a six-byte field that says which authority issued the SID), a list of sub-authorities, and a final &lt;em&gt;relative identifier&lt;/em&gt; (RID) [@ms-learn-security-identifiers]. Microsoft&apos;s own page on SIDs is precise: &quot;A security identifier (SID) is a unique value of variable length used to identify a trustee... When a SID has been used as the unique identifier for a user or group, it cannot ever be used again to identify another user or group.&quot;&lt;/p&gt;
&lt;p&gt;The well-known SIDs the kernel recognises by name include &lt;code&gt;SYSTEM&lt;/code&gt; (S-1-5-18), &lt;code&gt;LocalService&lt;/code&gt; (S-1-5-19), &lt;code&gt;NetworkService&lt;/code&gt; (S-1-5-20), &lt;code&gt;Everyone&lt;/code&gt; (S-1-1-0), &lt;code&gt;Authenticated Users&lt;/code&gt; (S-1-5-11), and the four Mandatory Integrity Control levels (S-1-16-4096 / 8192 / 12288 / 16384) [@en-wiki-mic, @ms-learn-mic]. Machine-issued SIDs encode the machine&apos;s domain identity in the sub-authorities; domain-issued SIDs encode the domain identity. RID 500 is, by convention, the local Administrator account; RID 501 is the Guest account.&lt;/p&gt;

A variable-length, never-reused identifier for a trustee (user, group, machine, service, or capability) inside the Windows security model. SIDs are encoded in canonical form `S-R-I-S1-S2-...-RID`, where R is a revision number, I is the identifier authority, the Sn are sub-authorities issued by that authority, and RID is the relative identifier. Every ACE references an SID; every token contains a list of them [@ms-learn-security-identifiers].
&lt;p&gt;James Forshaw documented in 2017 that Windows generates the per-service SID for &lt;code&gt;NT SERVICE\&amp;lt;ServiceName&amp;gt;&lt;/code&gt; deterministically: it is the SHA-1 hash of the uppercased service name, formatted into the SID&apos;s sub-authority fields. This is why Windows can refer to running services as security principals without an explicit registration step -- the kernel derives the SID on demand [@tiraniddo-trustedinstaller-blog].&lt;/p&gt;
&lt;p&gt;Two SID families this article will not derive: AppContainer &lt;em&gt;Package SIDs&lt;/em&gt; (S-1-15-2-...) and &lt;em&gt;capability SIDs&lt;/em&gt; (S-1-15-3-...). Both arrived with Windows 8 in 2012 and extend the matrix&apos;s subjects with code identity and capability tokens. The sibling &lt;a href=&quot;https://paragmali.com/blog/windows-app-identity-33-year-reinvention/&quot; rel=&quot;noopener&quot;&gt;App Identity article&lt;/a&gt; in this series carries the canonical derivation, including the Crockford-Base32 PublisherId derivation that produces a Package SID from an MSIX package signature [@app-identity-sibling]. Section 5 of &lt;em&gt;this&lt;/em&gt; article will mention those SIDs in tokens; we will not redefine them here.&lt;/p&gt;
&lt;p&gt;The DACL is half the story. What does the &lt;em&gt;thread&lt;/em&gt; bring to the access check -- and why is the answer &quot;whoever happens to hold the handle&quot;?&lt;/p&gt;
&lt;h2&gt;5. Tokens as Bearer Credentials&lt;/h2&gt;
&lt;p&gt;A token is not a credential the way a password is. A token is a credential the way &lt;em&gt;cash&lt;/em&gt; is: whoever holds it gets the rights. This is the single most important property in the article.&lt;/p&gt;
&lt;p&gt;Microsoft splits tokens into two flavours by purpose [@ms-learn-access-tokens]. A &lt;em&gt;primary token&lt;/em&gt; hangs off a process and represents the security identity that process runs as. Every process has exactly one. An &lt;em&gt;impersonation token&lt;/em&gt; hangs off a thread and lets that thread temporarily act as someone else -- typically a network client whose request the thread is servicing. Tokens are kernel objects with handles, and like every other kernel object the kernel does not care how a process obtained the handle. If the handle resolves to a token in the kernel&apos;s table, the kernel grants the rights the token names.&lt;/p&gt;

A kernel object that names the security identity of a thread or process. It carries the user&apos;s SID, the SIDs of the user&apos;s groups, the privileges the user holds, an integrity level, an integrity-level mandatory policy, an optional list of *restricting* SIDs, a flag distinguishing primary from impersonation tokens, and the impersonation level (Anonymous / Identification / Impersonation / Delegation) [@ms-learn-access-tokens]. The kernel consults a token on every access check for the thread that holds it.

A *primary token* is owned by a process and represents the identity the process runs as; every process has exactly one primary token. An *impersonation token* is owned by a thread and represents an identity the thread is temporarily acting on behalf of -- typically a network client. The primary / impersonation distinction is a discriminator inside the token itself, set when the token is created or duplicated [@ms-learn-access-tokens].
&lt;p&gt;The impersonation flavour acquired its modern shape in Windows 2000. A token&apos;s &lt;em&gt;impersonation level&lt;/em&gt; takes one of four values, ordered from least to most privileged for the impersonator. &lt;em&gt;Anonymous&lt;/em&gt; lets the server know nothing about the client. &lt;em&gt;Identification&lt;/em&gt; lets the server learn the client&apos;s SIDs but not act as the client. &lt;em&gt;Impersonation&lt;/em&gt; lets the server perform local access checks as the client; this is the level a typical RPC server requests. &lt;em&gt;Delegation&lt;/em&gt; lets the server forward the client&apos;s identity onto another machine, useful for multi-hop scenarios but a frequent source of relay-style bugs. Almost every Potato lineage attack consumes an &lt;em&gt;Impersonation&lt;/em&gt;-level token; that is enough to call &lt;code&gt;ImpersonateLoggedOnUser&lt;/code&gt; and run as the client [@itm4n-printspoofer-blog].&lt;/p&gt;
&lt;p&gt;Microsoft documents a third token shape, the &lt;em&gt;restricted token&lt;/em&gt;, that is rare in practice but worth understanding because it is the only place in the model where an explicit deny-list lives on the token itself rather than the descriptor. A restricted token combines three knobs: a list of SIDs converted to &lt;em&gt;deny-only&lt;/em&gt; (their grants count for no allow ACE but their presence still triggers deny ACEs), a list of &lt;em&gt;restricting SIDs&lt;/em&gt; that the access check must independently permit, and a list of privileges removed from the token&apos;s privilege set [@ms-learn-restricted-tokens].&lt;/p&gt;
&lt;p&gt;The kernel runs &lt;code&gt;SeAccessCheck&lt;/code&gt; twice and grants only the intersection: &quot;When a restricted process or thread tries to access a securable object, the system performs two access checks: one using the token&apos;s enabled SIDs, and another using the list of restricting SIDs. Access is granted only if both access checks allow the requested access rights&quot; [@ms-learn-restricted-tokens]. Restricted tokens are operationally niche because the same documentation requires applications using them to &quot;run the restricted application on desktops other than the default desktop. This is necessary to prevent an attack by a restricted application, using &lt;code&gt;SendMessage&lt;/code&gt; or &lt;code&gt;PostMessage&lt;/code&gt;, to unrestricted applications on the default desktop&quot; [@ms-learn-restricted-tokens]. Few applications can spare the desktop overhead.&lt;code&gt;whoami /priv&lt;/code&gt; shows &lt;em&gt;available&lt;/em&gt; privileges, not &lt;em&gt;enabled&lt;/em&gt; privileges. The &lt;code&gt;Enabled&lt;/code&gt; column is the load-bearing one: an available-but-disabled privilege does not affect any access check until the process explicitly enables it via &lt;code&gt;AdjustTokenPrivileges&lt;/code&gt;. The discipline of leaving privileges disabled by default is a defence in depth that depends on the application not having an exploitable bug between disable and use.&lt;/p&gt;
&lt;p&gt;A token also carries flags that drive specific runtime behaviours: a &lt;em&gt;split-token&lt;/em&gt; indicator points at a &lt;em&gt;linked&lt;/em&gt; full-administrator counterpart for the UAC scenario in Section 8; an &lt;em&gt;AppContainer&lt;/em&gt; flag plus a Package SID and capability SIDs name an AppContainer-bound process. In every case, the kernel consults the token by handle and trusts the contents. The kernel does not ask how a process obtained the handle. It asks only what the token says.&lt;/p&gt;
&lt;p&gt;This is the property that organises the rest of the article.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; &lt;strong&gt;The Windows access token is a bearer credential.&lt;/strong&gt; Whichever process holds the handle gets the rights. The kernel does not ask how the handle was obtained; it asks only what the token says. This single property explains the entire Potato lineage, Mimikatz &lt;code&gt;token::elevate&lt;/code&gt;, and most of the privilege-abuse canon. Once you see it, every later attack section in the article becomes the same bug repeated against a different token-acquisition primitive.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the token is a bearer credential, anyone with a way to obtain a SYSTEM token&apos;s handle is SYSTEM. Every Potato in Section 10 will be a different way to provoke a SYSTEM-token handle into the attacker&apos;s process. But the access check has another input that bypasses the DACL entirely. What is it, and which attackers know about it?&lt;/p&gt;
&lt;h2&gt;6. Privileges as a Different Dimension&lt;/h2&gt;
&lt;p&gt;Privileges are not access rights. They are pre-checked superpowers. They live on the token, they bypass the DACL for specific operations, and they are baked into the kernel for those operations alone.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s framing on Learn is exact: &quot;Privileges differ from access rights in two ways: Privileges control access to system resources and system-related tasks, whereas access rights control access to securable objects&quot; [@ms-learn-privileges]. The same page makes the operational consequence clear: &quot;Most privileges are disabled by default&quot; [@ms-learn-privileges]. A process that holds a privilege but has not enabled it (via &lt;code&gt;AdjustTokenPrivileges&lt;/code&gt;) cannot use it. The discipline is &quot;principle of least privilege at the millisecond level&quot; -- the privilege is on the token, but it does nothing until the program explicitly turns it on for the next system call.&lt;/p&gt;

A named, kernel-recognised authority on the access token that lets the holder perform a specific class of operations the DACL evaluation alone would not permit. Privileges include `SeDebugPrivilege` (read/write any process), `SeImpersonatePrivilege` (act on a client&apos;s token), `SeAssignPrimaryTokenPrivilege` (start a process under a token), `SeBackupPrivilege` (read any file regardless of DACL), `SeRestorePrivilege` (write any file regardless of DACL), `SeTcbPrivilege` (act as the operating system), and `SeLoadDriverPrivilege` (load a kernel driver). Most are disabled by default and must be enabled via `AdjustTokenPrivileges` before use [@ms-learn-privileges].
&lt;p&gt;The reason privileges deserve a section of their own is that &lt;em&gt;five of them are equivalent to &quot;I am SYSTEM&quot;&lt;/em&gt; and the other dozen are housekeeping. The five-versus-housekeeping split is the load-bearing audit decision in any Windows hardening review. Step through them.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SeDebugPrivilege&lt;/code&gt; lets the holder open any process for full read and write, including processes running as SYSTEM. The privilege exists so that &lt;code&gt;Visual Studio&lt;/code&gt; and &lt;code&gt;WinDbg&lt;/code&gt; can debug code other users have started. The first move in almost every Mimikatz session is &lt;code&gt;privilege::debug&lt;/code&gt;, which enables the privilege the local administrator already has on the token [@github-gentilkiwi-mimikatz, @en-wiki-mimikatz]. Once enabled, the next Mimikatz command opens &lt;code&gt;lsass.exe&lt;/code&gt; and reads the credentials.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; lets the holder accept any token offered by a client and act as that client. The privilege is held by every Windows service account by default -- IIS, SQL Server, the Print Spooler, scheduled tasks, Docker containers, Citrix, the entire population of background services that need to handle authenticated requests. &lt;em&gt;This is the load-bearing privilege for the Potato lineage&lt;/em&gt; [@itm4n-printspoofer-blog]. Section 10 will spend twenty paragraphs on what a service account holding &lt;code&gt;SeImpersonate&lt;/code&gt; can be tricked into doing; the privilege is the entry condition.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SeAssignPrimaryTokenPrivilege&lt;/code&gt; lets the holder launch a new process under any primary token. Combined with &lt;code&gt;SeImpersonate&lt;/code&gt;, the pair gives an attacker the entire token-replay attack: get an impersonation handle to a SYSTEM token, then call &lt;code&gt;CreateProcessAsUser&lt;/code&gt; to run a command as SYSTEM. itm4n quotes decoder_it on the operational consequence.&lt;/p&gt;

if you have SeAssignPrimaryToken or SeImpersonate privilege, you are SYSTEM. -- decoder_it, quoted by itm4n in the PrintSpoofer disclosure [@itm4n-printspoofer-blog]
&lt;p&gt;&lt;code&gt;SeBackupPrivilege&lt;/code&gt; lets the holder read any file regardless of DACL, on the theory that backup software has to. &lt;code&gt;SeRestorePrivilege&lt;/code&gt; is the symmetric write privilege. The two together mean that a process holding both can rewrite any file on the machine, including service binaries.&lt;/p&gt;
&lt;p&gt;The 2021 &lt;em&gt;HiveNightmare&lt;/em&gt; / &lt;em&gt;SeriousSAM&lt;/em&gt; vulnerability (CVE-2021-36934) is the worked example of what happens when the model assumes nobody but the backup process has read access to a sensitive file and the assumption breaks. The NVD description is exact: &quot;An elevation of privilege vulnerability exists because of overly permissive Access Control Lists (ACLs) on multiple system files, including the Security Accounts Manager (SAM) database&quot; [@nvd-cve-2021-36934]. The DACL on the live &lt;code&gt;\Windows\System32\config\SAM&lt;/code&gt; was correct; the DACL on the &lt;em&gt;Volume Shadow Copy&lt;/em&gt; mirror was overly permissive enough that any local user could read the SAM hashes through the shadow-copy device path.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s fix required not just a patch but a manual operator step: &quot;After installing this security update, you must manually delete all shadow copies of system files, including the SAM database, to fully mitigate this vulnerability&quot; [@nvd-cve-2021-36934]. A patch alone could not erase the historical shadow copies that already had the wrong DACL.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SeTcbPrivilege&lt;/code&gt; lets the holder &quot;act as the operating system&quot; -- it is the privilege that grants identity to the kernel itself. Held only by &lt;code&gt;LocalSystem&lt;/code&gt; services in well-administered environments. A non-system process that somehow acquired &lt;code&gt;SeTcb&lt;/code&gt; is, in effect, indistinguishable from the kernel.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SeLoadDriverPrivilege&lt;/code&gt; lets the holder load a kernel driver. By itself the privilege is harmless, because the loaded driver still has to be properly signed for HVCI / Driver Signature Enforcement to accept it. Combined with a known-vulnerable signed driver, however, the privilege becomes the entry point for &lt;em&gt;bring-your-own-vulnerable-driver&lt;/em&gt; (BYOVD) attacks: load a benign-looking but exploitable signed driver, then use its bug to execute arbitrary kernel code. Two worked examples bracket the class.&lt;/p&gt;
&lt;p&gt;The kernel-read/write half of the class is best illustrated by Micro-Star&apos;s &lt;code&gt;RTCore64.sys&lt;/code&gt; (CVE-2019-16098 [@nvd-cve-2019-16098]), the MSI Afterburner driver that &quot;allows any authenticated user to read and write to arbitrary memory, I/O ports, and MSRs&quot; [@nvd-cve-2019-16098]. In October 2022, the threat actors behind the BlackByte ransomware weaponised the primitive at scale [@sophos-blackbyte]: the dropper loaded &lt;code&gt;RTCore64.sys&lt;/code&gt;, then walked the kernel&apos;s &lt;code&gt;PspCreateProcessNotifyRoutine&lt;/code&gt; callback array and zeroed every entry, blinding every registered process-creation callback before the encryption stage ran.&lt;/p&gt;
&lt;p&gt;Sophos&apos;s October 4, 2022 disclosure named the technique exactly: &quot;We found a sophisticated technique to bypass security products by abusing a known vulnerability in the legitimate vulnerable driver RTCore64.sys. The evasion technique supports disabling a whopping list of over 1,000 drivers on which security products rely to provide protection&quot; [@sophos-blackbyte].&lt;/p&gt;
&lt;p&gt;The kernel-code-execution half of the class is GIGABYTE&apos;s &lt;code&gt;gdrv.sys&lt;/code&gt; (CVE-2018-19320 [@nvd-cve-2018-19320]). The NVD description states the primitive directly: &quot;The GDrv low-level driver in GIGABYTE APP Center v1.05.21 and earlier... exposes ring0 memcpy-like functionality that could allow a local attacker to take complete control of the affected system&quot; [@nvd-cve-2018-19320]. A signed &lt;code&gt;IOCTL&lt;/code&gt; accepts an attacker-supplied source pointer, destination pointer, and length, and copies kernel memory at ring 0 -- a write-what-where primitive that the attacker can compose with the read-anywhere primitive of &lt;code&gt;RTCore64.sys&lt;/code&gt; to mint arbitrary kernel code execution.&lt;/p&gt;
&lt;p&gt;CISA added GIGABYTE Multiple Products to the Known Exploited Vulnerabilities Catalog on October 24, 2022 with a remediation due date of November 14, 2022, citing in-the-wild exploitation [@nvd-cve-2018-19320]. The U.S. federal-civilian executive branch had two weeks to remediate; the rest of the install base did not.&lt;/p&gt;
&lt;p&gt;Microsoft&apos;s structural answer is the Microsoft-recommended driver blocklist, enabled by default on every device since the Windows 11 2022 update [@ms-learn-driver-blocklist]. The Learn page is exact about coverage: the blocklist targets drivers with &quot;known security vulnerabilities that an attacker could exploit to elevate privileges in the Windows kernel&quot;, and explicitly catches drivers whose behaviours &quot;circumvent the Windows Security Model&quot; [@ms-learn-driver-blocklist].&lt;/p&gt;
&lt;p&gt;Both &lt;code&gt;RTCore64.sys&lt;/code&gt; and &lt;code&gt;gdrv.sys&lt;/code&gt; appear on the blocklist; the ride from disclosure to default-on enforcement was four years for &lt;code&gt;RTCore64&lt;/code&gt;, four years for &lt;code&gt;gdrv&lt;/code&gt;, and the same arc applies to every member of the class. Honourable mention: &lt;code&gt;aswArPot.sys&lt;/code&gt; (CVE-2022-26522 / CVE-2022-26523 [@sentinelone-avast-avg]) shows the same pattern from a security-product driver, with SentinelLabs reporting &quot;two high severity flaws in Avast and AVG... that went undiscovered for years affecting dozens of millions of users&quot; before the silent fix [@sentinelone-avast-avg].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; On a Windows machine, holding any of &lt;code&gt;SeDebugPrivilege&lt;/code&gt;, &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt;, &lt;code&gt;SeAssignPrimaryTokenPrivilege&lt;/code&gt;, &lt;code&gt;SeBackupPrivilege&lt;/code&gt;, or &lt;code&gt;SeRestorePrivilege&lt;/code&gt; is operationally indistinguishable from being SYSTEM. The other privileges in the standard token (the long tail of &lt;code&gt;SeShutdown&lt;/code&gt;, &lt;code&gt;SeIncreaseWorkingSet&lt;/code&gt;, &lt;code&gt;SeTimeZone&lt;/code&gt;, &lt;code&gt;SeChangeNotify&lt;/code&gt;, &lt;code&gt;SeUndock&lt;/code&gt;, &lt;code&gt;SeIncreaseQuota&lt;/code&gt;...) are housekeeping. Audit the holders of the five accordingly: any non-LocalSystem-equivalent account that holds them is a target.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The DACL is the load-bearing thing for &lt;em&gt;file&lt;/em&gt; access, but exactly &lt;em&gt;one bad DACL&lt;/em&gt; on a sensitive file ends the model. HiveNightmare proved that the cost of getting a single security descriptor wrong on a single file is the entire credential database. The general rule the lesson encodes: every primitive in Section 3&apos;s input list has at least one production example where misuse of that primitive alone dropped the security model to zero.&lt;/p&gt;
&lt;p&gt;Discretionary access control assumes the principal -- the user -- is the right unit of authorisation. By 2006, exploitable user-mode code had proven the principal was wrong. What did Microsoft do?&lt;/p&gt;
&lt;h2&gt;7. Mandatory Integrity Control: Stapling No-Write-Up onto DAC&lt;/h2&gt;
&lt;p&gt;November 2006. Vista releases to manufacturing, and for the first time in Windows history, the kernel&apos;s access check fires &lt;em&gt;before&lt;/em&gt; the DACL walk -- on something other than the user&apos;s identity. The new layer is &lt;em&gt;Mandatory Integrity Control&lt;/em&gt; (MIC), and it adds a four-level lattice to every securable object in the system.&lt;/p&gt;
&lt;p&gt;Microsoft Learn frames MIC compactly. &quot;MIC uses integrity levels and mandatory policy to evaluate access. Security principals and securable objects are assigned integrity levels that determine their levels of protection or access. For example, a principal with a low integrity level cannot write to an object with a medium integrity level, even if that object&apos;s DACL allows write access to the principal&quot; [@ms-learn-mic]. The same page enumerates the levels: &quot;Windows defines four integrity levels: low, medium, high, and system&quot; [@ms-learn-mic]. The levels are encoded as four well-known SIDs: Low (S-1-16-4096), Medium (S-1-16-8192), High (S-1-16-12288), System (S-1-16-16384) [@en-wiki-mic].&lt;/p&gt;

A Windows kernel mechanism, introduced with Vista (released to manufacturing November 8, 2006; consumer general availability January 30, 2007 [@en-wiki-windows-vista]), that adds an integrity-level check to `SeAccessCheck`. Each securable object carries an integrity label inside its SACL; each access token carries an integrity level. An access whose direction violates the configured *mandatory policy* (typically *no-write-up*) is denied at the integrity check before the DACL walk runs. MIC is the mandatory layer the Windows DAC model historically lacked [@ms-learn-mic].

A linearly-ordered tag (Low / Medium / High / System) carried on every Windows process token and every securable object. The kernel uses the relative ordering to enforce mandatory policy independent of the object&apos;s DACL. The four well-known SIDs are S-1-16-4096 (Low), S-1-16-8192 (Medium), S-1-16-12288 (High), and S-1-16-16384 (System) [@en-wiki-mic].
&lt;p&gt;The default policy is &lt;em&gt;no-write-up&lt;/em&gt;. A process at integrity level $L$ cannot modify an object at integrity level greater than $L$, regardless of what the DACL says. Microsoft&apos;s example is the load-bearing one: a process running at Low IL cannot write to a Medium-IL object even if the DACL says Everyone has full control. The integrity check fires &lt;em&gt;before&lt;/em&gt; the DACL walk; if the integrity check denies, the DACL is not consulted [@ms-learn-mic].&lt;/p&gt;
&lt;p&gt;The integrity label is stored as a &lt;code&gt;SYSTEM_MANDATORY_LABEL_ACE&lt;/code&gt; inside the SACL, not the DACL [@ms-learn-system-mandatory-label-ace]. The mask field on the label ACE encodes which directions of access the policy forbids: &lt;code&gt;SYSTEM_MANDATORY_LABEL_NO_WRITE_UP (0x1)&lt;/code&gt;, &lt;code&gt;SYSTEM_MANDATORY_LABEL_NO_READ_UP (0x2)&lt;/code&gt;, and &lt;code&gt;SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP (0x4)&lt;/code&gt; [@ms-learn-system-mandatory-label-ace].&lt;/p&gt;
&lt;p&gt;Storing the label in the SACL is a deliberate choice with one operational consequence: tools that copy the DACL but not the SACL silently drop the integrity label. The most common consequence is a Low-IL file getting copied to a new location and emerging with no integrity label, which defaults to Medium and unintentionally raises the object&apos;s protection. The opposite mistake -- a Medium-IL file losing its label and dropping to Low -- is the more dangerous one.&lt;/p&gt;
&lt;p&gt;The no-write-up mask is the one to memorise, because it is the policy almost every label uses. When a Low-IL caller tries to act on a Medium-IL object, the kernel denies any access whose mapped result contains write-class bits, including the standard-rights &lt;code&gt;WRITE_DAC&lt;/code&gt; (bit 18 of the 32-bit ACCESS_MASK [@ms-learn-access-mask]) and &lt;code&gt;WRITE_OWNER&lt;/code&gt; (bit 19), the type-generic &lt;code&gt;DELETE&lt;/code&gt; (bit 16), and the file-specific &lt;code&gt;FILE_WRITE_DATA&lt;/code&gt; (0x2), &lt;code&gt;FILE_APPEND_DATA&lt;/code&gt; (0x4), &lt;code&gt;FILE_WRITE_EA&lt;/code&gt; (0x10), and &lt;code&gt;FILE_WRITE_ATTRIBUTES&lt;/code&gt; (0x100) [@ms-learn-file-access-rights].&lt;/p&gt;
&lt;p&gt;The presence of &lt;code&gt;FILE_APPEND_DATA&lt;/code&gt; in that list matters operationally: a careless reader of the spec might assume that &quot;append&quot; semantics escape the no-write-up rule because they do not modify existing bytes. They do not. MIC denies both write modes, so log-only append handlers cannot be used as a write-up channel into a higher-IL object.&lt;/p&gt;
&lt;p&gt;A second rule completes the model: &lt;em&gt;process integrity inheritance&lt;/em&gt;. When a process is created, the kernel assigns it the &lt;em&gt;minimum&lt;/em&gt; of the user&apos;s integrity level and the file&apos;s integrity level [@ms-learn-mic]. A medium-IL user running a low-IL executable gets a low-IL process. This is the rule that lets Internet Explorer 7 run at Low even when launched from a Medium user session.&lt;/p&gt;
&lt;p&gt;The first MIC consumer was IE7 &lt;em&gt;Protected Mode&lt;/em&gt;, which shipped with Windows Vista RTM (released to manufacturing November 8, 2006) [@wayback-skywing-uninformed-v8a6, @en-wiki-windows-vista]. (IE7 standalone for Windows XP, released October 18, 2006 [@en-wiki-ie7], runs without Protected Mode -- the feature depends on Vista&apos;s MIC kernel layer.) Skywing&apos;s &lt;em&gt;Uninformed&lt;/em&gt; Volume 8 Article 6, &quot;Getting Out of Jail: Escaping Internet Explorer Protected Mode,&quot; is the first public reverse-engineering of the implementation.&lt;/p&gt;
&lt;p&gt;Skywing&apos;s framing remains the most-cited primer on the subject: &quot;With the introduction of Windows Vista, Microsoft has added a new form of mandatory access control to the core operating system. Internally known as &apos;integrity levels&apos;, this new addition to the security manager allows security controls to be placed on a per-process basis. This is different from the traditional model of per-user security controls used in all prior versions of Windows NT&quot; [@wayback-skywing-uninformed-v8a6]. IE7&apos;s Protected Mode pattern -- a Low-IL worker that does the dangerous parsing, paired with a Medium-IL broker that performs the system-changing operations on the worker&apos;s behalf -- became the template Windows would later generalise into AppContainer.&lt;em&gt;User Interface Privilege Isolation&lt;/em&gt; (UIPI) is the window-message gate that uses MIC at the desktop. A Low-IL window cannot send &lt;code&gt;SendMessage&lt;/code&gt; or &lt;code&gt;PostMessage&lt;/code&gt; traffic to a Medium-IL or higher window. UIPI is the reason you cannot click-jack a UAC consent prompt from a normally-running browser process: the consent prompt runs at High IL, the browser runs at Medium [@en-wiki-mic].&lt;/p&gt;
&lt;p&gt;Windows 8 generalised the MIC pattern into &lt;em&gt;AppContainer&lt;/em&gt;. An AppContainer process gets a fresh Low-IL token plus an &lt;em&gt;AppContainer&lt;/em&gt; flag, a Package SID that identifies the app, and a list of capability SIDs the app declared in its manifest. Microsoft Learn states the resulting isolation directly: &quot;Windows ensures that processes running with a low integrity level cannot obtain access to a process which is associated with an app container&quot; [@ms-learn-mic]. The Package SID and capability SID derivations are the subject of the App Identity sibling article in this series; we will not redefine them here [@app-identity-sibling].&lt;/p&gt;
&lt;p&gt;MIC fixed the integrity boundary inside the kernel. But the same year shipped a separate retrofit for a different problem: why was the consumer admin running every clicked-on &lt;code&gt;.exe&lt;/code&gt; with full administrative authority? And why did Microsoft refuse to call its answer a security boundary?&lt;/p&gt;
&lt;h2&gt;8. UAC, the Split-Token, and the Bypass Tradition&lt;/h2&gt;
&lt;p&gt;Vista&apos;s &lt;em&gt;User Account Control&lt;/em&gt; is the most famous Windows security retrofit, and the only one whose own keepers explicitly published a document declaring it not a security boundary [@msrc-servicing-criteria]. The mechanism is precise. The bypass tradition is enormous. The classification is honest.&lt;/p&gt;
&lt;p&gt;The mechanism first. Microsoft&apos;s documentation on how UAC works gives the verbatim recipe [@ms-learn-uac]: &quot;When an administrator logs on, two separate access tokens are created for the user: a standard user access token and an administrator access token. The standard user access token... contains the same user-specific information as the administrator access token, but the administrative Windows privileges and SIDs are removed... is used to display the desktop by executing the process &lt;code&gt;explorer.exe&lt;/code&gt;. &lt;code&gt;Explorer.exe&lt;/code&gt; is the parent process from which all other user-initiated processes inherit their access token. As a result, all apps run as a standard user unless a user provides consent or credentials to approve an app to use a full administrative access token.&quot;&lt;/p&gt;

A Windows mechanism, introduced with Vista (released to manufacturing November 8, 2006 [@en-wiki-windows-vista]), in which an administrative user receives two linked tokens at logon: a *filtered* Medium-IL token without administrative privileges or SIDs, used by `explorer.exe` and every process descended from it, and a *full* High-IL administrative counterpart that the kernel hands out only after the user clicks through a consent prompt or supplies credentials. The two tokens reference each other through the `LinkedToken` field. UAC is a UX-and-default-behaviour mechanism, not an enforced security boundary [@ms-learn-uac, @msrc-servicing-criteria].
&lt;p&gt;The split-token mechanism produces three elevation triggers. First, an executable can declare its required level in its manifest via the &lt;code&gt;requestedExecutionLevel&lt;/code&gt; element (&lt;code&gt;asInvoker&lt;/code&gt;, &lt;code&gt;highestAvailable&lt;/code&gt;, or &lt;code&gt;requireAdministrator&lt;/code&gt;). Second, certain Microsoft-signed binaries appear on an &lt;em&gt;AutoElevate&lt;/em&gt; whitelist that the kernel consults; processes on the whitelist transparently get the full token without prompting. Third, COM components can be marked elevatable via the &lt;em&gt;COM Elevation Moniker&lt;/em&gt;, which lets code instantiate &lt;code&gt;Elevation:Administrator!new:{guid}&lt;/code&gt; (or &lt;code&gt;Elevation:Highest!new:{guid}&lt;/code&gt;) to obtain a High-IL administrator COM caller -- not a SYSTEM caller; the moniker&apos;s supported run levels are &lt;code&gt;Administrator&lt;/code&gt; and &lt;code&gt;Highest&lt;/code&gt; [@ms-learn-com-elevation-moniker]. Method 41 (Oddvar Moe&apos;s &lt;code&gt;ICMLuaUtil&lt;/code&gt; construction) is the canonical worked example.&lt;/p&gt;
&lt;p&gt;The classification next. Microsoft&apos;s &lt;em&gt;Security Servicing Criteria for Windows&lt;/em&gt; defines a security boundary as the logical separation between security domains with different trust levels, and gives the kernel-mode / user-mode separation as the canonical example [@msrc-servicing-criteria]. The criteria document then enumerates which boundaries Microsoft commits to servicing. UAC and admin-to-kernel are not on the enumerated list.&lt;/p&gt;

A security boundary provides a logical separation between the code and data of security domains with different levels of trust... the separation between kernel mode and user mode is a classic [...] security boundary. Microsoft software depends on multiple security boundaries to isolate devices on the network, virtual machines, and applications on a device. -- Microsoft Security Servicing Criteria for Windows [@msrc-servicing-criteria]
&lt;p&gt;The &quot;outside the enumerated list&quot; classification has a concrete consequence: bypasses of UAC are not eligible for the same security-update treatment a kernel-mode-to-user-mode bypass would get. Mitigations are issued per-redirect, when an attacker&apos;s specific path becomes operationally noisy enough to warrant attention. The seventy-plus methods catalogued in UACMe are the empirical consequence.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; &lt;strong&gt;UAC was never a security boundary.&lt;/strong&gt; The seventy-plus methods catalogued in UACMe are not bugs in UAC. They are the formal consequence of UAC&apos;s classification. Once you recognise that UAC is a UX-and-default-behaviour mechanism rather than an enforced boundary, the bypass tradition is legible as a feature being used as designed and the structural arc to Adminless makes sense.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The bypass canon. Walk it generation by generation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Method 1 -- Leo Davidson, 2009.&lt;/strong&gt; Davidson&apos;s &lt;em&gt;Windows 7 UAC Whitelist&lt;/em&gt; writeup is the genealogical root of the UAC bypass tradition [@pretentiousname-davidson, @github-hfiref0x-uacme]. He noticed that &lt;code&gt;sysprep.exe&lt;/code&gt; is on the AutoElevate whitelist and that, when launched from &lt;code&gt;C:\Windows\System32\sysprep\&lt;/code&gt;, it loads several DLLs from the working directory. Use the &lt;code&gt;IFileOperation&lt;/code&gt; COM interface (which the elevator treats as AutoApprove, enabling the file copy without prompting) to drop a malicious &lt;code&gt;cryptbase.dll&lt;/code&gt; into &lt;code&gt;%SystemRoot%\System32\sysprep\&lt;/code&gt;. Then trigger &lt;code&gt;sysprep.exe&lt;/code&gt; -- which is on the AutoElevate whitelist -- and the auto-elevated process loads the attacker&apos;s DLL, and the attacker has a High-IL full administrative token.&lt;/p&gt;
&lt;p&gt;Davidson&apos;s writeup quotes himself bluntly: &quot;This works against the RTM (retail) and RC1 versions of Windows 7&quot; [@pretentiousname-davidson]. UACMe Method 1 records the technique with structured metadata: &quot;Author: Leo Davidson / Type: Dll Hijack / Method: IFileOperation / Target(s): \system32\sysprep\sysprep.exe / Component(s): cryptbase.dll / Implementation: ucmStandardAutoElevation / Works from: Windows 7 (7600) / Fixed in: Windows 8.1 (9600)&quot; [@github-hfiref0x-uacme].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Method 25 -- Matt Nelson (enigma0x3), August 15, 2016.&lt;/strong&gt; Seven years after Davidson, Nelson published &quot;Fileless UAC Bypass Using &lt;code&gt;eventvwr.exe&lt;/code&gt; and Registry Hijacking&quot; [@enigma0x3-eventvwr-blog]. The bypass replaces the file-system DLL hijack with a registry redirect. Nelson noticed that &lt;code&gt;eventvwr.exe&lt;/code&gt;, an AutoElevated binary, queries &lt;code&gt;HKCU\Software\Classes\mscfile\shell\open\command&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; &lt;code&gt;HKCR\mscfile\shell\open\command&lt;/code&gt; to find the command to run for the &lt;code&gt;mscfile&lt;/code&gt; ProgID. His verbatim observation: &quot;From the output, it appears that &apos;eventvwr.exe&apos;, as a high integrity process, queries both HKCU and HKCR registry hives to start mmc.exe&quot; [@enigma0x3-eventvwr-blog]. HKCU is writable by the standard user; the user writes a malicious command line under that key, runs &lt;code&gt;eventvwr.exe&lt;/code&gt;, and the auto-elevated process happily executes the user-supplied command line. The first &lt;em&gt;fileless&lt;/em&gt; UAC bypass.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Method 33 -- winscripting.blog, 2017.&lt;/strong&gt; Same primitive, different target. The &lt;code&gt;fodhelper.exe&lt;/code&gt; binary is on the AutoElevate whitelist and queries &lt;code&gt;HKCU\Software\Classes\ms-settings\shell\open\command&lt;/code&gt; to launch the Settings app. UACMe records the credit precisely: &quot;Author: winscripting.blog / Type: Shell API / Method: Registry key manipulation / Target(s): \system32\fodhelper.exe / Component(s): Attacker defined / Implementation: ucmShellRegModMethod / Works from: Windows 10 TH1 (10240) / Fixed in: unfixed&quot; [@github-hfiref0x-uacme]. &lt;em&gt;Fixed in: unfixed.&lt;/em&gt; This is what &quot;outside the enumerated list&quot; looks like in practice: nine years after Method 1 and a year after Method 25 demonstrated the underlying class, the registry-redirect template was still being applied to fresh AutoElevate targets and shipping unmitigated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Method 41 -- Oddvar Moe.&lt;/strong&gt; The COM elevation moniker route. UACMe records: &quot;Author: Oddvar Moe / Type: Elevated COM interface / Method: ICMLuaUtil&quot; [@github-hfiref0x-uacme]. Instantiate the &lt;code&gt;CMLuaUtil&lt;/code&gt; COM object via the elevation moniker from a Medium-IL process, get back a High-IL COM caller, and call its &lt;code&gt;ShellExec&lt;/code&gt; method to run an attacker command line at High IL. The seam is the COM moniker registry&apos;s &lt;code&gt;Elevation\Enabled&lt;/code&gt; key, which marks specific CLSIDs as elevation-capable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Method 31 (sdclt), 29, 34, ...&lt;/strong&gt; The pattern repeats. Matt Nelson&apos;s &lt;code&gt;sdclt.exe&lt;/code&gt; variants exploit the backup-restore UI&apos;s registry lookups. Forshaw&apos;s &lt;code&gt;schtasks&lt;/code&gt; variant exploits the scheduled-task COM interface. The UACMe README enumerates the lot with the laconic one-liner &quot;Defeating Windows User Account Control by abusing built-in Windows AutoElevate backdoor&quot; [@github-hfiref0x-uacme]. Most of the methods reduce to the same primitive: an AutoElevated Microsoft-signed binary performs a lookup that the standard user can redirect, the standard user supplies an attacker-controlled answer, and the auto-elevated binary executes attacker-controlled work.&lt;/p&gt;

flowchart TD
    A[User Medium-IL process] --&amp;gt; B[Write attacker command line into HKCU writable seam: registry key, file system path, scheduled-task XML]
    B --&amp;gt; C[Trigger AutoElevated Microsoft-signed binary: sysprep.exe / eventvwr.exe / fodhelper.exe / sdclt.exe / etc.]
    C --&amp;gt; D[AutoElevate flag honoured by kernel]
    D --&amp;gt; E[Binary launched at High IL with full administrative token]
    E --&amp;gt; F[Binary performs lookup: HKCU registry / DLL search path / scheduled-task definition]
    F --&amp;gt; G[Lookup returns attacker-supplied content]
    G --&amp;gt; H[High-IL process executes attacker work]

Mark Russinovich&apos;s June 2007 *TechNet Magazine* cover story, &quot;Inside Windows Vista User Account Control,&quot; is the canonical practitioner walkthrough of the split-token model and is preserved on the Wayback Machine [@wayback-russinovich-uac-technet]. Russinovich opens by naming the misunderstanding: &quot;User Account Control (UAC) is an often misunderstood feature in Windows Vista... In this article I discuss the problems UAC solves and describe the architecture and implementation of its component technologies.&quot; The framing throughout the article is that UAC&apos;s purpose is to create the *expectation* that consumer software would run as a standard user, and to push the developer community to refactor away from gratuitous administrator requirements. That framing -- UAC as a UX and migration mechanism -- is consistent with the eventual MSRC servicing-criteria position: not a defended boundary, but a behaviour gate.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Microsoft&apos;s servicing-criteria position means a UAC bypass that does not also cross a serviced security boundary is not, by policy, eligible for a security update. Mitigations land when the operational footprint of a particular bypass becomes large enough to justify one. Track UAC mitigations by KB number, not by feature description; consult the UACMe README&apos;s &lt;code&gt;Fixed in:&lt;/code&gt; field as the institutional memory [@github-hfiref0x-uacme, @msrc-servicing-criteria].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;UAC bypasses redirect the elevation flow that produces a token. A different attack family takes the result and steals tokens from already-elevated SYSTEM services. Where do those attacks come from, and why are they all the same bug?&lt;/p&gt;
&lt;h2&gt;9. The Object Manager and the Lookup Surface&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SeAccessCheck&lt;/code&gt; evaluates a security descriptor it has been handed. Who hands it the descriptor?&lt;/p&gt;
&lt;p&gt;The kernel&apos;s &lt;em&gt;Object Manager&lt;/em&gt; does, after walking a name. Every named kernel object lives somewhere in a hierarchical namespace rooted at &lt;code&gt;\&lt;/code&gt;. Devices live under &lt;code&gt;\Device&lt;/code&gt;. Synchronisation primitives live under &lt;code&gt;\BaseNamedObjects&lt;/code&gt;. Pre-resolved DLL names live under &lt;code&gt;\KnownDlls&lt;/code&gt;. Per-session subtrees live under &lt;code&gt;\Sessions&lt;/code&gt;. The DOS device prefix &lt;code&gt;\GLOBAL??&lt;/code&gt; (and its session-local sibling &lt;code&gt;\??&lt;/code&gt;) holds drive-letter symbolic links into the device tree. When a process calls &lt;code&gt;OpenObject&lt;/code&gt;, the Object Manager parses the name, walks the tree, and returns the object whose security descriptor &lt;code&gt;SeAccessCheck&lt;/code&gt; will then evaluate.&lt;/p&gt;
&lt;p&gt;The kernel performs &lt;em&gt;the lookup&lt;/em&gt; before &lt;em&gt;the access check&lt;/em&gt;. This sequencing creates a parallel attack surface that bypasses &lt;code&gt;SeAccessCheck&lt;/code&gt; entirely. If the attacker can influence the name resolution -- redirect a &lt;code&gt;\??\&lt;/code&gt; symbolic link, plant a junction in NTFS that re-targets a directory traversal, hardlink a low-privilege file at a path the kernel will trust because of the parent directory&apos;s descriptor -- then by the time &lt;code&gt;SeAccessCheck&lt;/code&gt; runs, it is being asked about a different object than the original code path intended to open.&lt;/p&gt;
&lt;p&gt;The HiveNightmare lookup path is the canonical worked example. The exploit reads the SAM database not via &lt;code&gt;\Windows\System32\config\SAM&lt;/code&gt; (which has a tight DACL) but via &lt;code&gt;\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy*\Windows\System32\config\SAM&lt;/code&gt;. That path resolves through the Object Manager&apos;s &lt;code&gt;\GLOBAL??&lt;/code&gt; symbolic link, into the device tree, into a Volume Shadow Copy mirror of the original volume, into a &lt;em&gt;copy&lt;/em&gt; of &lt;code&gt;SAM&lt;/code&gt; whose DACL was, until the August 2021 patch, readable by any authenticated local user (BUILTIN\Users) [@nvd-cve-2021-36934].&lt;/p&gt;
&lt;p&gt;The lookup-phase attack class is wider than file-system shadow copies. &lt;em&gt;Object Manager symbolic links&lt;/em&gt; and &lt;em&gt;NTFS hard links&lt;/em&gt; both produce the same primitive: the kernel resolves a name through an attacker-influenced redirect and ends up evaluating &lt;code&gt;SeAccessCheck&lt;/code&gt; against a different security descriptor than the calling code intended.&lt;/p&gt;
&lt;p&gt;CVE-2020-0668, the Service Tracing elevation-of-privilege bug Clément Labro disclosed in February 2020, is the textbook symbolic-link case [@itm4n-cve-2020-0668-blog]. The Service Tracing infrastructure under &lt;code&gt;HKLM\SOFTWARE\Microsoft\Tracing&lt;/code&gt; is user-writable, and several SYSTEM services -- &lt;code&gt;IKEEXT&lt;/code&gt;, &lt;code&gt;RasMan&lt;/code&gt;, the Update Session Orchestrator service -- consult those keys to find a tracing log path. When the log file exceeds the configured &lt;code&gt;MaxFileSize&lt;/code&gt;, the service renames it from &lt;code&gt;MODULE.LOG&lt;/code&gt; to &lt;code&gt;MODULE.OLD&lt;/code&gt;, deleting any existing &lt;code&gt;MODULE.OLD&lt;/code&gt; first.&lt;/p&gt;
&lt;p&gt;itm4n&apos;s exploit is exactly the one his blog post names: &quot;All you need to do is set the target directory as a mountpoint to the &lt;code&gt;\RPC Control&lt;/code&gt; object directory and then create two symbolic links: A symbolic link from &lt;code&gt;MODULE.LOG&lt;/code&gt; to a file you own; A symbolic link from &lt;code&gt;MODULE.OLD&lt;/code&gt; to any file on the file system&quot; [@itm4n-cve-2020-0668-blog]. The mountpoint reroutes the kernel&apos;s name resolution into an Object Manager directory the attacker controls; the two symlinks reroute the rename operation; the SYSTEM service ends up performing an arbitrary file move with kernel authority. Forshaw&apos;s &lt;code&gt;googleprojectzero/symboliclink-testing-tools&lt;/code&gt; repository [@github-projectzero-symlink-tools] provides the primitive library the exploit consumes -- &lt;code&gt;CreateSymlink&lt;/code&gt;, &lt;code&gt;CreateMountPoint&lt;/code&gt;, &lt;code&gt;BaitAndSwitch&lt;/code&gt; -- and the repository is, in effect, the institutional library every Object Manager lookup-phase attack of the past decade has linked against.&lt;/p&gt;
&lt;p&gt;The NTFS hard-link case predates the symbolic-link case by half a decade. James Forshaw&apos;s December 2015 Project Zero post, &quot;Between a Rock and a Hard Link,&quot; is the canonical primary source [@projectzero-blog-rockandhardlink]. Forshaw observes that hard links have been a feature of NTFS &quot;since it was originally designed&quot;, and that their relevance to local privilege escalation is exactly the lookup-vs-access-check sequencing this section describes: &quot;Why are hard links useful for local privilege escalation? One type of vulnerability is exploited by a file planting attack, where a privilege service tries to write to a file in a known location&quot; [@projectzero-blog-rockandhardlink].&lt;/p&gt;
&lt;p&gt;The worked example Forshaw walks is CVE-2015-4481, a Mozilla Maintenance Service hard-link primitive: any user can write a status log to &lt;code&gt;C:\ProgramData\Mozilla\logs\maintenanceservice.log&lt;/code&gt;, and during the service&apos;s pre-write &lt;code&gt;BackupOldLogs&lt;/code&gt; rename a brief window opens in which the attacker can replace the about-to-be-written log path with a hard link to an arbitrary system file. The service&apos;s subsequent write -- which it performs with its own SYSTEM authority -- ends up overwriting the system file. The DACL on the source file (the Mozilla log directory) was correct; the DACL on the destination file (the system binary) was correct; the kernel arrived at the destination by resolving a hard-link name the attacker had planted, and &lt;code&gt;SeAccessCheck&lt;/code&gt; saw only the destination DACL, not the planted-link DACL [@projectzero-blog-rockandhardlink]. Microsoft&apos;s MS15-115 mitigation tightened the kernel&apos;s hard-link semantics for sandboxed callers (the kernel&apos;s &lt;code&gt;NtSetInformationFile&lt;/code&gt; now requires &lt;code&gt;FILE_WRITE_ATTRIBUTES&lt;/code&gt; on the target handle when the caller&apos;s token has the sandboxed-token flag, matching what the user-mode &lt;code&gt;CreateHardLink&lt;/code&gt; wrapper had always opened the target with). The fix closes the sandboxed-process branch of the bug class but, as Forshaw notes, does nothing for the Maintenance Service vulnerability itself, which is exploited by a non-sandboxed local user; the structural fix is to write the log to a directory the user cannot create files in, not to enforce a hard-link mask -- a structural fix to the lookup phase, not the access-check phase.&lt;/p&gt;
&lt;p&gt;The two examples generalise the rule. The kernel resolves names &lt;em&gt;before&lt;/em&gt; checking the requesting user&apos;s authority over the destination. The DACL at &lt;code&gt;target.path&lt;/code&gt; and the DACL at &lt;code&gt;attacker.planted.path&lt;/code&gt; after a junction or hard-link redirect can be different; &lt;code&gt;SeAccessCheck&lt;/code&gt; evaluates the descriptor it arrives at, not the descriptor the original caller intended. Capability systems would resolve names through unforgeable handles instead of strings, and the redirect class would not exist by construction [@en-wiki-capability-based-security]. Windows checks the descriptor on every &lt;code&gt;OpenObject&lt;/code&gt; because the name is a forgeable string. The Object Manager namespace is therefore an attack surface whose load-bearing fix is structural rather than per-bug.The Object Manager&apos;s namespace is not documented as policy in the same way &lt;code&gt;SeAccessCheck&lt;/code&gt;&apos;s algorithm is. The de facto modern documentation is empirical: practitioners enumerate the namespace with tools that read kernel structures directly. James Forshaw&apos;s NtObjectManager PowerShell module, part of the Project Zero &lt;code&gt;sandbox-attacksurface-analysis-tools&lt;/code&gt; repository, is the dominant such tool [@github-projectzero-sandbox-attacksurface]. The repository&apos;s banner is exact: &quot;NtObjectManager: A powershell module which uses NtApiDotNet to expose the NT object manager.&quot;&lt;/p&gt;
&lt;p&gt;The James Forshaw / Project Zero corpus is the systematic reference for the Object Manager attack surface. Forshaw&apos;s &quot;Sharing a Logon Session a Little Too Much&quot; (April 2020) names a primitive PrintSpoofer would later consume: when the LSA creates a token for a new logon session, it caches the token for later retrieval. Forshaw&apos;s verbatim explanation: &quot;when LSASS creates a Token for a new Logon session it stores that Token for later retrieval. For the most part this isn&apos;t that useful, however there is one case where the session Token is repurposed, network authentication&quot; [@tiraniddo-sharing-logon-session]. The cached token plus a named-pipe path-validation bug becomes a non-DCOM SYSTEM-token primitive that no DACL touches.&lt;/p&gt;
&lt;p&gt;The lookup surface is half the attack story. The other half is the token surface, and the canonical example of the token surface is a six-year, eight-tool lineage.&lt;/p&gt;
&lt;h2&gt;10. The Potato Lineage: Eight Tools, One Bug (2016-2021)&lt;/h2&gt;
&lt;p&gt;January 16, 2016. Stephen Breen of Foxglove Security publishes &quot;Hot Potato.&quot; The disclosure post opens with a sentence the article will earn the right to repeat: &quot;Microsoft is aware of all of these issues and has been for some time (circa 2000). These are unfortunately hard to fix without breaking backward compatibility and have been [used] by attackers for over 15 years&quot; [@foxglove-hot-potato-blog]. Six years and seven tools later, that admission still describes the situation.&lt;/p&gt;
&lt;p&gt;The single underlying primitive. A low-privileged service account holding &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; (which IIS, SQL Server, the Print Spooler, scheduled tasks, Docker, Citrix, and almost every managed-service account hold by default) tricks SYSTEM into authenticating to a TCP, RPC, or named-pipe endpoint the attacker controls. The attacker&apos;s endpoint accepts the authentication, ends up with an impersonation handle to a SYSTEM token, and calls &lt;code&gt;ImpersonateLoggedOnUser&lt;/code&gt; followed by &lt;code&gt;CreateProcessAsUser&lt;/code&gt; to run arbitrary code as SYSTEM. Same primitive, eight tools, six years.&lt;/p&gt;

sequenceDiagram
    participant Attacker as Attacker (service account, SeImpersonate)
    participant Coercer as Coercion primitive (NBNS / DCOM / EFSRPC / Spooler RPC)
    participant SYSTEM as SYSTEM service
    participant Endpoint as Attacker-controlled local endpoint
    Attacker-&amp;gt;&amp;gt;Coercer: Trigger coercion (e.g. EfsRpcOpenFileRaw, BITS CoGetInstanceFromIStorage)
    Coercer-&amp;gt;&amp;gt;SYSTEM: Tell SYSTEM to authenticate
    SYSTEM-&amp;gt;&amp;gt;Endpoint: NTLM authentication to attacker endpoint
    Endpoint-&amp;gt;&amp;gt;Endpoint: Accept NTLM, build impersonation token
    Attacker-&amp;gt;&amp;gt;Attacker: ImpersonateLoggedOnUser(SYSTEM token)
    Attacker-&amp;gt;&amp;gt;Attacker: CreateProcessAsUser(SYSTEM token, &quot;cmd.exe&quot;)
    Note over Attacker: SYSTEM shell
&lt;p&gt;Walk the lineage one paragraph at a time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hot Potato (Stephen Breen / Foxglove, January 2016)&lt;/strong&gt; [@foxglove-hot-potato-blog]. The original. Hot Potato chains three Windows defaults: NBT-NS (NetBIOS name service) spoofing on the local network, the Web Proxy Auto-Discovery (WPAD) protocol&apos;s automatic proxy lookup, and Windows Update&apos;s HTTP-to-SMB NTLM relay. The exploit poisons NBT-NS so that the SYSTEM-running Windows Update service resolves &lt;code&gt;WPAD&lt;/code&gt; to the attacker&apos;s local listener; the attacker&apos;s listener serves a proxy configuration that proxies SMB through localhost; the Windows Update service authenticates to the attacker&apos;s localhost SMB endpoint as SYSTEM. Foxglove&apos;s verbatim summary: &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-blog].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rotten Potato (Stephen Breen and Chris Mallz / Foxglove, September 2016)&lt;/strong&gt; [@foxglove-rotten-potato-blog]. The successor abandons the network-protocol fragility for a DCOM activation trick that James Forshaw had published as Project Zero issue 325. The attacker calls &lt;code&gt;CoGetInstanceFromIStorage&lt;/code&gt; with the BITS CLSID &lt;code&gt;{4991d34b-80a1-4291-83b6-3328366b9097}&lt;/code&gt; and a custom marshalled &lt;code&gt;IStorage&lt;/code&gt; pointer; the COM activation runs on the local DCOM server (which runs as SYSTEM) and authenticates back to a TCP listener the attacker controls.&lt;/p&gt;
&lt;p&gt;The Foxglove blog states the three steps verbatim: &quot;1. Trick the &apos;NT AUTHORITY\SYSTEM&apos; account into authenticating via NTLM to a TCP endpoint we control. 2. Man-in-the-middle this authentication attempt (NTLM relay) to locally negotiate a security token for the &apos;NT AUTHORITY\SYSTEM&apos; account. 3. Impersonate the token... This can only be done if the attackers current account has the privilege to impersonate security tokens&quot; [@foxglove-rotten-potato-blog]. The same post credits the Project Zero work directly: &quot;this work is derived directly from James Forshaw&apos;s BlackHat talk and Google Project Zero research&quot; [@foxglove-rotten-potato-blog].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Juicy Potato (decoder_it and ohpe, 2018)&lt;/strong&gt; [@github-ohpe-juicy-potato]. A weaponised, configurable Rotten Potato. The repository README is exact about lineage and entry conditions: &quot;RottenPotatoNG and its variants leverages the privilege escalation chain based on BITS service having the MiTM listener on 127.0.0.1:6666 and when you have SeImpersonate or SeAssignPrimaryToken privileges. ... We decided to weaponize RottenPotatoNG: Say hello to Juicy Potato&quot; [@github-ohpe-juicy-potato].&lt;/p&gt;
&lt;p&gt;Juicy Potato adds a CLSID brute-list (so the attacker can cycle through DCOM activations until one works on the target Windows version), a configurable listener port, and a configurable target binary. The repo&apos;s own framing of when it works is the article&apos;s PullQuote-worthy line: &quot;If the user has SeImpersonate or SeAssignPrimaryToken privileges then you are SYSTEM&quot; [@github-ohpe-juicy-potato]. Juicy Potato was killed by a Windows 10 1809 / Server 2019 mitigation that prevented the OXID resolver from being queried on a port other than 135. The mitigation was the first time Microsoft had touched the underlying primitive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rogue Potato (Antonio Cocomazzi and Andrea Pierini, May 2020)&lt;/strong&gt; [@decoder-rogue-potato-blog]. The bypass for the loopback-OXID mitigation. Decoder&apos;s blog states the engineering problem and the fix in two sentences: &quot;Starting from Windows 10 1809 &amp;amp; Windows Server 2019, its no more possible to query the OXID resolver on a port different than 135&quot; [@decoder-rogue-potato-blog]. Rogue Potato works around the constraint by routing the OXID resolution through an attacker-controlled &lt;em&gt;remote&lt;/em&gt; OXID resolver, typically reached via &lt;code&gt;socat tcp-listen:135,fork TCP:attacker:9999&lt;/code&gt;. The remote resolver returns a string binding pointing back at the attacker&apos;s local listener; the constraint is satisfied (the OXID resolver is on port 135) but the listener it ultimately reaches is the attacker&apos;s. The lineage extends.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PrintSpoofer (Clément Labro / itm4n, May 2020)&lt;/strong&gt; [@github-itm4n-printspoofer, @itm4n-printspoofer-blog]. The same week as Rogue Potato, a different no-DCOM, no-OXID variant lands. PrintSpoofer drops DCOM entirely. It uses the Print Spooler RPC method &lt;code&gt;RpcRemoteFindFirstPrinterChangeNotificationEx&lt;/code&gt; to coerce the spooler (running as SYSTEM) to call back to a named pipe whose path the attacker controls. A path-validation bypass on the named-pipe name lets the attacker capture the SYSTEM credential the spooler offers.&lt;/p&gt;
&lt;p&gt;itm4n&apos;s repository description summarises the entry condition: &quot;From LOCAL/NETWORK SERVICE to SYSTEM by abusing SeImpersonatePrivilege on Windows 10 and Server 2016/2019&quot; [@github-itm4n-printspoofer]. The blog post opens with credit and the canonical decoder_it quote: &quot;I want to start things off with this quote from @decoder_it: &apos;if you have SeAssignPrimaryToken or SeImpersonate privilege, you are SYSTEM&apos;&quot; [@itm4n-printspoofer-blog].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RemotePotato0 (Cocomazzi and Pierini, 2021)&lt;/strong&gt; [@github-antoniococo-remotepotato0]. The first Potato to escape the local machine. A cross-session DCOM activation lets the attacker reach a token from a &lt;em&gt;different&lt;/em&gt; logged-on user; a cross-protocol RPC-to-LDAP relay then turns that user&apos;s authentication into a domain-administrator action against Active Directory.&lt;/p&gt;
&lt;p&gt;The README is candid about the shape of the response: &quot;UPDATE 21-10-2022: The main exploit scenario RPC-&amp;gt;LDAP of RemotePotato0 has been fixed... Just another &apos;Won&apos;t Fix&apos; Windows Privilege Escalation from User to Domain Admin. RemotePotato0 is an exploit that allows you to escalate your privileges from a generic User to Domain Admin... It abuses the DCOM activation service and trigger an NTLM authentication of any user currently logged on in the target machine&quot; [@github-antoniococo-remotepotato0]. &lt;em&gt;Just another &quot;Won&apos;t Fix&quot; Windows Privilege Escalation&lt;/em&gt; is the precise framing: the underlying primitive is structural, the 2022 fix addressed the specific RPC-to-LDAP path, and the construction continues to work for other relay targets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PetitPotam (Lionel Gilles / topotam77, July 2021)&lt;/strong&gt; [@github-topotam-petitpotam, @nvd-cve-2021-36942]. Not strictly a local-LPE Potato, but the source of the EFSRPC coercion primitive that local-LPE Potatoes consume. PetitPotam exploits the Encrypting File System remote-procedure-call protocol&apos;s &lt;code&gt;EfsRpcOpenFileRaw&lt;/code&gt; (and several other functions) to coerce a Windows host to authenticate to an attacker-controlled endpoint.&lt;/p&gt;
&lt;p&gt;The README is exact about the interface choices: &quot;PoC tool to coerce Windows hosts to authenticate to other machines via MS-EFSRPC EfsRpcOpenFileRaw or other functions :) The tools use the LSARPC named pipe with inteface c681d488-d850-11d0-8c52-00c04fd90f7e because it&apos;s more prevalent. But it&apos;s possible to trigger with the EFSRPC named pipe and interface df1941c5-fe89-4e79-bf10-463657acf44d&quot; [@github-topotam-petitpotam]. PetitPotam&apos;s most-cited use case is cross-machine relay against Active Directory Certificate Services, but the EFSRPC coercion is also locally consumable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SharpEfsPotato (bugch3ck, 2021)&lt;/strong&gt; [@github-bugch3ck-sharpefspotato]. The local-machine variant of the PetitPotam coercion. The README is precise about lineage: &quot;Local privilege escalation from SeImpersonatePrivilege using EfsRpc. Built from SweetPotato by &lt;code&gt;@_EthicalChaos_&lt;/code&gt; and SharpSystemTriggers/SharpEfsTrigger by &lt;code&gt;@cube0x0&lt;/code&gt;&quot; [@github-bugch3ck-sharpefspotato]. SharpEfsPotato uses &lt;code&gt;EfsRpcOpenFileRaw&lt;/code&gt; against the local LSARPC pipe to coerce SYSTEM to authenticate to a local endpoint, then performs the by-now-familiar token capture and &lt;code&gt;CreateProcessAsUser&lt;/code&gt;.This article&apos;s stage-4 source verification corrected an attribution that had been carried forward from the original scope file: SharpEfsPotato&apos;s canonical repository is &lt;code&gt;bugch3ck/SharpEfsPotato&lt;/code&gt;, not the often-cited &lt;code&gt;ly4k/SharpEfsPotato&lt;/code&gt;. The latter URL returns HTTP 404. Cross-references that point at the &lt;code&gt;ly4k&lt;/code&gt; URL should be updated to point at &lt;code&gt;bugch3ck&lt;/code&gt; [@github-bugch3ck-sharpefspotato].&lt;/p&gt;
&lt;p&gt;The pattern across the lineage is that &lt;em&gt;the mitigation that did break a tool was always specific&lt;/em&gt; (the loopback-OXID restriction, the cross-session DCOM partial fix, the EFSRPC coercion partial mitigation in KB5005413), and &lt;em&gt;the mitigation that would break the family is structural&lt;/em&gt; (remove &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; from service accounts, end NTLM-to-self-as-machine relay, retire the bearer-credential property of tokens). Microsoft has shipped the first kind of fix seven times across the lineage and continues to ship them; the second kind requires architectural changes that arrive in successor articles in this series.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Author(s)&lt;/th&gt;
&lt;th&gt;Coercion vector&lt;/th&gt;
&lt;th&gt;Mitigation that broke it&lt;/th&gt;
&lt;th&gt;Mitigation that did not&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Hot Potato&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;Breen&lt;/td&gt;
&lt;td&gt;NBT-NS + WPAD + HTTP-to-SMB relay&lt;/td&gt;
&lt;td&gt;Disable WPAD; KB3146965&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeImpersonate&lt;/code&gt; on services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotten Potato&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;Breen, Mallz&lt;/td&gt;
&lt;td&gt;DCOM &lt;code&gt;CoGetInstanceFromIStorage&lt;/code&gt; (BITS)&lt;/td&gt;
&lt;td&gt;(none specific until Juicy fix)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeImpersonate&lt;/code&gt; on services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Juicy Potato&lt;/td&gt;
&lt;td&gt;2018&lt;/td&gt;
&lt;td&gt;decoder_it, ohpe&lt;/td&gt;
&lt;td&gt;DCOM CLSID brute-list, configurable port&lt;/td&gt;
&lt;td&gt;Loopback-OXID restriction (1809 / 2019)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeImpersonate&lt;/code&gt; on services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rogue Potato&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;Cocomazzi, Pierini&lt;/td&gt;
&lt;td&gt;Remote OXID resolver via &lt;code&gt;socat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cross-session DCOM partial fix&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeImpersonate&lt;/code&gt; on services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PrintSpoofer&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;Labro&lt;/td&gt;
&lt;td&gt;Spooler RPC + named-pipe path bypass&lt;/td&gt;
&lt;td&gt;KB5005010 (PrintNightmare-era spooler hardening) [@nvd-cve-2021-34527]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeImpersonate&lt;/code&gt; on services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RemotePotato0&lt;/td&gt;
&lt;td&gt;2021&lt;/td&gt;
&lt;td&gt;Cocomazzi, Pierini&lt;/td&gt;
&lt;td&gt;Cross-session DCOM + RPC-to-LDAP relay&lt;/td&gt;
&lt;td&gt;RPC-to-LDAP relay fix (October 2022)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeImpersonate&lt;/code&gt; on services; remaining relay targets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PetitPotam&lt;/td&gt;
&lt;td&gt;2021&lt;/td&gt;
&lt;td&gt;Gilles&lt;/td&gt;
&lt;td&gt;EFSRPC coercion via LSARPC&lt;/td&gt;
&lt;td&gt;KB5005413 partial; ADCS hardening [@nvd-cve-2021-36942]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeImpersonate&lt;/code&gt; on services; other relay targets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SharpEfsPotato&lt;/td&gt;
&lt;td&gt;2021&lt;/td&gt;
&lt;td&gt;bugch3ck&lt;/td&gt;
&lt;td&gt;Local EFSRPC coercion&lt;/td&gt;
&lt;td&gt;(none specific to local variant)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SeImpersonate&lt;/code&gt; on services&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Eight tools. One privilege. Every &quot;mitigation that did not&quot; cell points at the same thing: a bearer-token model plus &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; on a service account.&lt;/p&gt;

Eight tools in six years against the same underlying primitive is not a tooling coincidence. It is the empirical signature of the bearer-credential property and the omnipresent service-account `SeImpersonate` privilege. The Hot Potato post&apos;s verbatim &quot;hard to fix without breaking backward compatibility&quot; admission [@foxglove-hot-potato-blog] is the same argument Microsoft eventually formalised in the security-servicing-criteria position: this surface is intentionally retained for compatibility, and structural changes belong in a different architecture. The article earns the bridge to the Adminless and NTLMless successors here, six years before the calendar gets there.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A service account holding &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; plus &lt;em&gt;any&lt;/em&gt; RPC interface that authenticates to attacker-controllable endpoints equals SYSTEM. Eight Potatoes in six years prove this is structural, not a tooling fad. Audit every server: any non-LocalSystem-equivalent process holding &lt;code&gt;SeImpersonate&lt;/code&gt; or &lt;code&gt;SeAssignPrimaryToken&lt;/code&gt; should be treated as a Potato target until proven otherwise. Pre-deploy per-service SIDs and Group Managed Service Accounts where possible to constrain the blast radius [@github-itm4n-printspoofer].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Eight Potatoes prove the bearer-token property is unkillable by point fixes. But what does an attacker who is &lt;em&gt;already admin&lt;/em&gt; do? The answer is the most-cited offensive Windows tool of the past fifteen years.&lt;/p&gt;
&lt;h2&gt;11. Mimikatz, Conditional ACEs, and the Edges of the Model&lt;/h2&gt;
&lt;p&gt;May 2011. Benjamin Delpy releases the first version of Mimikatz [@en-wiki-mimikatz]. Wikipedia&apos;s biographical summary is precise: &quot;He released the first version of the software in May 2011 as closed source software&quot; [@en-wiki-mimikatz]. Fifteen years later, every offensive Windows engagement on Earth still reaches for it.&lt;/p&gt;
&lt;p&gt;Mimikatz contains many modules. Two of them sit directly on the access-control surface and are worth naming explicitly. The repository&apos;s own command surface lists them as &lt;code&gt;privilege::debug&lt;/code&gt; and &lt;code&gt;token::elevate&lt;/code&gt; [@github-gentilkiwi-mimikatz].&lt;/p&gt;
&lt;p&gt;&lt;code&gt;privilege::debug&lt;/code&gt; is one line of code: the command enables &lt;code&gt;SeDebugPrivilege&lt;/code&gt; on the caller&apos;s token. Any local administrator on stock Windows holds the privilege on the token by default; the command flips it from &lt;code&gt;Available&lt;/code&gt; to &lt;code&gt;Enabled&lt;/code&gt; via &lt;code&gt;AdjustTokenPrivileges&lt;/code&gt;. With &lt;code&gt;SeDebugPrivilege&lt;/code&gt; enabled, the calling process can &lt;code&gt;OpenProcess&lt;/code&gt; against any other process on the machine, including SYSTEM-level services such as &lt;code&gt;lsass.exe&lt;/code&gt;. Every Mimikatz session that wants to read process memory begins with &lt;code&gt;privilege::debug&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;token::elevate&lt;/code&gt; is three lines of code in spirit. The command opens a SYSTEM-owned process (typically &lt;code&gt;lsass.exe&lt;/code&gt;), calls &lt;code&gt;OpenProcessToken&lt;/code&gt; to retrieve a handle to the SYSTEM token, calls &lt;code&gt;DuplicateTokenEx&lt;/code&gt; to duplicate the handle for impersonation, and calls &lt;code&gt;SetThreadToken&lt;/code&gt; to attach the duplicated SYSTEM token to the calling thread. The thread is now SYSTEM. The bearer-token property in three lines of code.&lt;/p&gt;
&lt;p&gt;This article does not cover &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt;. That command is the most-cited Mimikatz capability in journalism (it reads cached credentials from &lt;code&gt;lsass.exe&lt;/code&gt;), but the credential-storage surface and the Credential Guard mitigation belong to the &lt;a href=&quot;https://paragmali.com/blog/the-windows-secure-kernel/&quot; rel=&quot;noopener&quot;&gt;Secure Kernel sibling article&lt;/a&gt; in this series [@secure-kernel-sibling]. For the purposes of &lt;em&gt;this&lt;/em&gt; article, the lesson stops at &lt;code&gt;token::elevate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The lesson is the structural concession. With administrator rights on the local machine and &lt;code&gt;SeDebugPrivilege&lt;/code&gt; enabled, the access-control model has &lt;em&gt;no defence&lt;/em&gt; for &quot;I will pretend to be a different process,&quot; because admin equals kernel by Microsoft&apos;s own boundary definition [@msrc-servicing-criteria]. The DACL evaluation algorithm does not protect against a caller who can read and write arbitrary kernel memory. The privilege list does not protect against a caller who can rewrite the privilege check. The integrity check does not protect against a caller who can edit the integrity label. Every primitive in the model is, by construction, defenceless against an attacker who has crossed the boundary the model considers itself responsible for defending.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; With administrator rights and &lt;code&gt;SeDebugPrivilege&lt;/code&gt;, the Windows access-control model has no defence for &quot;I will pretend to be a different process,&quot; because admin equals kernel by Microsoft&apos;s own boundary definition. Mimikatz &lt;code&gt;token::elevate&lt;/code&gt; is the canonical demonstration. The structural fix for &lt;em&gt;selected&lt;/em&gt; secrets is Credential Guard, which moves the secret out of the NT kernel&apos;s address space entirely. See the Secure Kernel sibling article for the architecture [@secure-kernel-sibling, @msrc-servicing-criteria].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The model has been extended in only one structural direction since 1993, and that direction is the &lt;em&gt;subject&lt;/em&gt; of the access matrix. &lt;em&gt;Conditional ACEs&lt;/em&gt; and &lt;em&gt;Dynamic Access Control&lt;/em&gt; (DAC) shipped together in Windows Server 2012 and Windows 8 [@ms-learn-dac]. They are the only extension of the access-matrix subject Microsoft has shipped in thirty-three years.&lt;/p&gt;
&lt;p&gt;The mechanism is twofold. First, ACEs gain an expression syntax. The SDDL ACE strings page documents &lt;code&gt;XA&lt;/code&gt;, &lt;code&gt;XD&lt;/code&gt;, &lt;code&gt;XU&lt;/code&gt;, and &lt;code&gt;ZA&lt;/code&gt; as conditional callback variants of the basic allow / deny / audit / object-allow ACE types [@ms-learn-ace-strings]. A conditional ACE carries an expression in addition to a SID and an access mask, and the kernel evaluates the expression against the token&apos;s claims at access time. The canonical example is &lt;code&gt;(XA;;FA;;;AU;(@User.Department==&quot;Finance&quot;))&lt;/code&gt; -- an allow-callback ACE that grants &lt;code&gt;FILE_ALL_ACCESS&lt;/code&gt; to Authenticated Users &lt;em&gt;if&lt;/em&gt; the token carries a &lt;code&gt;Department&lt;/code&gt; claim equal to &lt;code&gt;&quot;Finance&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Second, the token gains &lt;em&gt;claims&lt;/em&gt;. A claim is a typed key-value pair attached to the token by Active Directory at logon. Claims can be sourced from the user&apos;s AD attributes, the device&apos;s AD attributes, or resource properties on the object. Microsoft Learn states the role they play: &quot;A central access rule is an expression of authorization rules that can include one or more conditions involving user groups, user claims, device claims, and resource properties. Multiple central access rules can be combined into a central access policy&quot; [@ms-learn-dac].&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;Central Access Policy&lt;/em&gt; (CAP) is a set of &lt;em&gt;Central Access Rules&lt;/em&gt; (CARs), each of which is a conditional-ACE expression. The CAP is applied to file shares; the file-share metadata says &quot;evaluate this CAP for every access,&quot; and the CAP&apos;s expressions reference token claims. The DAC scenario guidance enumerates the deployment-side primitives -- automatic and manual file classification, central access policies for safety-net authorisation, central audit policies for compliance reporting, and Rights Management Service encryption for data-in-use protection [@ms-learn-dac-scenario].&lt;/p&gt;
&lt;p&gt;The reason DAC has not displaced classic DAC outside file-server scenarios is in the same Microsoft Learn page: &quot;Dynamic Access Control is not supported in Windows operating systems prior to Windows Server 2012 and Windows 8. When Dynamic Access Control is configured in environments with supported and non-supported versions of Windows, only the supported versions will implement the changes&quot; [@ms-learn-dac]. Heterogeneous environments fall back to classic DAC. Airgapped environments without a working AD plus AD FS plane have no claims to evaluate. Conditional ACEs are a real extension of the model&apos;s subject dimension; they are also a real bet that the AD-and-Kerberos plane is healthy enough to evaluate them on every access.AppContainer&apos;s Package SIDs (Windows 8) and conditional ACEs (Server 2012) shipped the same year. Both extend the &lt;em&gt;subject&lt;/em&gt; dimension of the access matrix -- one with code identity, one with attribute claims. Neither closes the kernel-equals-admin gap. The two extensions are coordinate, not stacked: a conditional ACE can reference a Package SID; a Package SID can be the subject of a conditional ACE [@ms-learn-dac, @app-identity-sibling].&lt;/p&gt;
&lt;p&gt;The model has been extended in two coordinate dimensions in thirty-three years. It has not been replaced. So what does the whole thing look like put together -- and what does it actually fail at?&lt;/p&gt;
&lt;h2&gt;12. The 2026 Plane: Ten Primitives, One Decision&lt;/h2&gt;
&lt;p&gt;Run a single &lt;code&gt;OpenObject&lt;/code&gt; call on a Windows 11 machine and walk the kernel&apos;s path. Every primitive the article has introduced fires for that one call.&lt;/p&gt;

flowchart LR
    A[User-mode caller] --&amp;gt;|OpenObject name, DesiredAccess, Token| B[Object Manager]
    B --&amp;gt;|Resolve name in namespace| C[Namespace lookup]
    C --&amp;gt;|Fetch security descriptor| D[Object header SD]
    D --&amp;gt; E[SeAccessCheck]
    E --&amp;gt; F[Generic-to-specific mapping]
    F --&amp;gt; G[Mandatory Integrity Control check]
    G --&amp;gt;|Pass| H[AppContainer / capability check]
    H --&amp;gt; I[Privilege bypass: SeBackup / SeRestore / SeDebug]
    I --&amp;gt; J[DACL walk in canonical order]
    J --&amp;gt; K[Conditional ACE expression evaluation]
    K --&amp;gt; L[GrantedAccess accumulated]
    L --&amp;gt; M[SACL audit ACE emit if matched]
    M --&amp;gt; N[Return HANDLE or STATUS_ACCESS_DENIED]
&lt;p&gt;The diagram is the article in one figure. Read it left to right. Every box is a primitive named in Sections 3 to 11. Every famous Windows escalation tool of the last twenty-five years targets one of those boxes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Primitive&lt;/th&gt;
&lt;th&gt;Section introduced&lt;/th&gt;
&lt;th&gt;Year shipped&lt;/th&gt;
&lt;th&gt;Canonical primary citation&lt;/th&gt;
&lt;th&gt;Canonical attack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Security Reference Monitor&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1993&lt;/td&gt;
&lt;td&gt;[@ms-learn-access-control]&lt;/td&gt;
&lt;td&gt;(Underlying surface; not directly attacked)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Security Identifier (SID)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1993&lt;/td&gt;
&lt;td&gt;[@ms-learn-security-identifiers]&lt;/td&gt;
&lt;td&gt;Misused well-known SIDs (&quot;Everyone is just a SID&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Access Token&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1993&lt;/td&gt;
&lt;td&gt;[@ms-learn-access-tokens]&lt;/td&gt;
&lt;td&gt;The Potato lineage; Mimikatz &lt;code&gt;token::elevate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Security Descriptor&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1993&lt;/td&gt;
&lt;td&gt;[@ms-learn-how-dacls-control-access]&lt;/td&gt;
&lt;td&gt;HiveNightmare (CVE-2021-36934)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;DACL + ACE&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1993&lt;/td&gt;
&lt;td&gt;[@ms-learn-how-dacls-control-access, @ms-learn-order-of-aces]&lt;/td&gt;
&lt;td&gt;NULL DACL misconfigurations; out-of-order ACEs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;SACL + audit&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1993&lt;/td&gt;
&lt;td&gt;[@ms-learn-access-control]&lt;/td&gt;
&lt;td&gt;Tools that copy DACL but not SACL silently drop integrity labels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Privilege&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1993&lt;/td&gt;
&lt;td&gt;[@ms-learn-privileges]&lt;/td&gt;
&lt;td&gt;Mimikatz &lt;code&gt;privilege::debug&lt;/code&gt;; &lt;code&gt;SeBackup&lt;/code&gt; abuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Mandatory Integrity Control&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2007&lt;/td&gt;
&lt;td&gt;[@ms-learn-mic]&lt;/td&gt;
&lt;td&gt;IE7 Protected Mode broker bypasses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;UAC split-token&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2007&lt;/td&gt;
&lt;td&gt;[@ms-learn-uac]&lt;/td&gt;
&lt;td&gt;UACMe: 70+ AutoElevate-redirect methods [@github-hfiref0x-uacme]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Conditional ACE / DAC&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2012&lt;/td&gt;
&lt;td&gt;[@ms-learn-dac, @ms-learn-ace-strings]&lt;/td&gt;
&lt;td&gt;Falls back to classic DAC in heterogeneous environments&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 Windows access-control model is one decision plane, not a feature catalogue. Every securable Windows operation resolves through &lt;code&gt;SeAccessCheck&lt;/code&gt; against five fixed inputs. Every famous escalation tool of the last twenty-five years attacks one of those inputs. Recognising the model as a single plane is the key to using its vocabulary against any specific attack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The plane is whole. It is also full of structural holes its own keepers have publicly admitted. What are they?&lt;/p&gt;
&lt;h2&gt;13. The Five Structural Limits&lt;/h2&gt;
&lt;p&gt;Microsoft&apos;s &lt;em&gt;Security Servicing Criteria for Windows&lt;/em&gt; defines a security boundary as &quot;a logical separation between the code and data of security domains with different levels of trust... the separation between kernel mode and user mode is a classic [...] security boundary&quot; [@msrc-servicing-criteria]. The criteria document then enumerates which boundaries Microsoft commits to servicing. The kernel-mode / user-mode boundary qualifies. UAC and admin-to-kernel are not in the enumerated list. Once that admission is on the public record, the model&apos;s structural arc becomes legible. Five derived limits flow from the concession.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limit 1: Admin equals kernel.&lt;/strong&gt; Any compromise with administrator rights can rewrite the model&apos;s own enforcement code. &lt;em&gt;Consequence:&lt;/em&gt; Mimikatz, every kernel-driver-loading attack, every signed-driver bring-your-own-vulnerable-driver path. &lt;em&gt;Successor:&lt;/em&gt; VBS Trustlets, which host secrets and policy enforcement in the &lt;em&gt;Virtual Trust Level 1&lt;/em&gt; user-mode environment that the VTL0 NT kernel cannot read or modify. Detailed coverage belongs to the Secure Kernel sibling article in this series [@secure-kernel-sibling].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limit 2: Tokens are bearer credentials.&lt;/strong&gt; Whichever process holds the handle gets the rights. The kernel does not ask how the handle was obtained. &lt;em&gt;Consequence:&lt;/em&gt; the entire Potato lineage (eight tools, six years), Mimikatz &lt;code&gt;token::elevate&lt;/code&gt;, every cross-session token-theft attack. &lt;em&gt;Successor:&lt;/em&gt; Adminless / Administrator Protection, which retires the long-lived filtered/full token pair in favour of a fresh, time-limited, just-in-time elevation flow gated by Windows Hello plus a hidden, system-generated, profile-separated user account that issues an isolated admin token [@ms-learn-administrator-protection, @techcommunity-admin-protection]. The forthcoming Adminless article in this series will cover the architecture in detail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limit 3: &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; on every service account.&lt;/strong&gt; IIS, SQL Server, the Print Spooler, scheduled tasks, Docker, Citrix, almost every managed-service account by default. &lt;em&gt;Consequence:&lt;/em&gt; every Potato, by construction. &lt;em&gt;Partial successor today:&lt;/em&gt; per-service SIDs and Group Managed Service Accounts let administrators constrain the blast radius of a compromised service. &lt;em&gt;Structural successor:&lt;/em&gt; Adminless, which removes the privilege from the daily authentication path and demands a fresh elevation per privileged action [@ms-learn-administrator-protection, @techcommunity-admin-protection].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limit 4: NTLM relay surface.&lt;/strong&gt; As long as Windows services accept NTLM and the operating system signs NTLM challenges with the local-machine credential, the local-NTLM-to-self attack is structurally available. &lt;em&gt;Consequence:&lt;/em&gt; PetitPotam, RemotePotato0, every cross-protocol relay. &lt;em&gt;Successor:&lt;/em&gt; NTLMless, which formally retires NTLM as a default Windows authentication protocol [@techcommunity-windows-auth-evolution]. The on-ramp is the NTLM auditing channel introduced in Windows 11 24H2 and Windows Server 2025 (KB5064479, original publish date July 11, 2025), which records NTLMv1 usage in &lt;code&gt;Microsoft\Windows\NTLM\Operational&lt;/code&gt; and gives administrators a per-workload deprecation telemetry [@ms-support-ntlm-auditing]. The forthcoming NTLMless article in this series will cover the architecture in detail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Limit 5: The DACL is local.&lt;/strong&gt; Conditional ACEs and Dynamic Access Control claims need a working AD-and-AD-FS plane to evaluate; airgapped or heterogeneous environments fall back to user / group SIDs as the only available subject. &lt;em&gt;Consequence:&lt;/em&gt; the access-matrix subject is, in practice, still &quot;user and group&quot; for most non-file-server workloads. The 2012 extension to claims and code identity is real but operationally bounded.&lt;/p&gt;
&lt;p&gt;The deepest of the five limits is the one Norm Hardy named in 1988. Hardy&apos;s framing returned in Section 2 [@en-wiki-confused-deputy] holds: capability systems close the gap structurally; ACL engineering does not.&lt;/p&gt;
&lt;p&gt;seL4 closes it with machine-checked correctness proofs and a capability-based design that makes ambient authority a category error [@en-wiki-capability-based-security]. Windows closes it, when it closes it at all, with VBS Trustlets that move &lt;em&gt;the right to perform the operation&lt;/em&gt; into a separate execution domain. The Potato lineage is the textbook confused-deputy instance: a service running with &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; is the privileged compiler; the attacker is the user holding a billing-records-shaped pointer; the service uses &lt;em&gt;its own&lt;/em&gt; authority on every authentication it accepts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Hardy&apos;s 1988 paper [@wayback-cap-lore-hardy] and the Wikipedia summary [@en-wiki-confused-deputy] both say the same thing: ACL systems are structurally vulnerable to confused-deputy attacks; capability systems are not. The gap is not asymptotic. ACL engineering does not close it. The Potato lineage is what the gap looks like in the field, repeated against eight different coercion primitives over six years.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; &lt;strong&gt;The next generation of Windows defences cannot live inside the kernel, because the kernel is on the wrong side of the boundary the model draws.&lt;/strong&gt; Microsoft&apos;s own servicing criteria admit it. Adminless, NTLMless, VBS Trustlets, and Credential Guard are the four non-overlapping ways to fix it. Each successor was scoped to close a specific gap the access-control model could not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The five limits are named. The successors are shipping. What replaces what?&lt;/p&gt;
&lt;h2&gt;14. The Successors: Adminless, NTLMless, VBS Trustlets, Credential Guard&lt;/h2&gt;
&lt;p&gt;One paragraph each. This section is a forward-reference index, not a detailed walk-through.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adminless.&lt;/strong&gt; Removes the local Administrators group from the daily authentication path. The long-lived filtered / full token pair the UAC model produces at logon is replaced with a fresh, time-limited, just-in-time elevation flow: when a user wants to perform a privileged action, the system gates the action behind &lt;a href=&quot;https://paragmali.com/blog/your-face-is-not-your-password-inside-windows-hellos-hardwar/&quot; rel=&quot;noopener&quot;&gt;Windows Hello&lt;/a&gt; plus a hidden, system-generated, profile-separated user account that issues an isolated admin token, and the resulting token is bounded in time and scope [@ms-learn-administrator-protection].&lt;/p&gt;
&lt;p&gt;The Microsoft Tech Community announcement (modified November 19, 2024) summarises the security argument: &quot;By requiring explicit authorization for every administrative task, Administrator protection protects Windows from accidental changes by users and changes by malware... Malicious software often relies on admin privileges to change device settings and execute harmful actions. Administrator protection breaks the attack kill chain&quot; [@techcommunity-admin-protection]. &lt;em&gt;Closes:&lt;/em&gt; limits #2 and #3 -- there is no long-lived bearer credential to steal, and &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; does not need to live on every service account because services run as bounded principals issued capabilities at the moment of need. Forthcoming article in this series.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NTLMless.&lt;/strong&gt; Formally retires NTLM as a default Windows authentication protocol [@techcommunity-windows-auth-evolution]. The Tech Community announcement is unambiguous about direction: &quot;Reducing the use of NTLM will ultimately culminate in it being disabled in Windows 11. We are taking a data-driven approach and monitoring reductions in NTLM usage to determine when it will be safe to disable&quot; [@techcommunity-windows-auth-evolution].&lt;/p&gt;
&lt;p&gt;The transition rests on a local KDC (IAKerb) that lets Kerberos service both local and domain accounts, plus an audit channel introduced in Windows 11 24H2 and Windows Server 2025 (KB5064479, original publish date July 11, 2025) that records NTLMv1 usage in &lt;code&gt;Microsoft\Windows\NTLM\Operational&lt;/code&gt; and gives administrators per-service telemetry on which workloads still require the protocol [@ms-support-ntlm-auditing]. The local-NTLM-to-self attack class -- coerce a SYSTEM service to authenticate with the local-machine credential, accept the challenge, relay it back to a local service that trusts the credential -- ends when the local-machine NTLM credential ends. &lt;em&gt;Closes:&lt;/em&gt; limit #4. Forthcoming article in this series.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VBS Trustlets and Isolated User Mode.&lt;/strong&gt; Shipped in Windows 10 1507 in 2015. &lt;em&gt;Virtualization-Based Security&lt;/em&gt; (VBS) uses the Hyper-V hypervisor to host a second user-mode environment, &lt;em&gt;Virtual Trust Level 1&lt;/em&gt; (VTL1), whose memory the NT kernel running in VTL0 cannot read or write. A &lt;em&gt;Trustlet&lt;/em&gt; is a process that runs in VTL1. &lt;em&gt;Closes:&lt;/em&gt; limit #1, for selected secrets. The ordinary NT kernel still runs the show for ordinary processes; VTL1 is a side-channel for secrets and policy decisions that the model wants to protect even from a kernel-level attacker. Detailed coverage in the Secure Kernel sibling article [@secure-kernel-sibling].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Credential Guard.&lt;/strong&gt; The canonical first Trustlet. &lt;code&gt;lsass.exe&lt;/code&gt; continues to run in VTL0 and answer authentication requests; the credential blobs &lt;code&gt;lsass.exe&lt;/code&gt; historically held are moved to a Trustlet called &lt;code&gt;LsaIso&lt;/code&gt; in VTL1. The VTL0 &lt;code&gt;lsass.exe&lt;/code&gt; retains &lt;em&gt;handles&lt;/em&gt; to the blobs but cannot read their contents; authentication happens by calling into the Trustlet. Mimikatz &lt;code&gt;sekurlsa::logonpasswords&lt;/code&gt; returns no plaintext credentials against a Credential-Guard-on system, because the plaintext does not live in VTL0 memory at all. The default-enablement timeline and the SKU-specific configuration matrix are covered in the Secure Kernel sibling article [@secure-kernel-sibling].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pluton-rooted attestation and the hardware foundation.&lt;/strong&gt; The successor architectures rest on a hardware identity chain that begins below the firmware, in Microsoft&apos;s Pluton in-die security processor. Pluton holds the keys that vouch for the boot measurements that the OS in turn uses to attest its own integrity to a remote relying party. The &lt;a href=&quot;https://paragmali.com/blog/pluton-a-tpm-on-silicon-microsoft-can-patch/&quot; rel=&quot;noopener&quot;&gt;Pluton article&lt;/a&gt; in this series covers the architecture and the Caliptra root-of-trust direction it foreshadows [@pluton-sibling]. The &lt;a href=&quot;https://paragmali.com/blog/the-tpm-in-windows-one-primitive-twenty-five-years-and-the-c/&quot; rel=&quot;noopener&quot;&gt;TPM 2.0 architecture&lt;/a&gt; that the same chain extends and the &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 chain&lt;/a&gt; that runs before the access-control model boots are covered in their own sibling articles in this series.&lt;/p&gt;

The five limits enumerated in Section 13 and the four successor articles in this section are in one-to-one correspondence: Adminless closes #2 and #3, NTLMless closes #4, VBS Trustlets close #1, and Credential Guard is the canonical first Trustlet that demonstrates #1 closing for a specific high-value secret. Limit #5 -- the DACL is local -- is operational rather than architectural and is closed by deployment investment in AD plus AD FS rather than by a new mechanism. The correspondence is not a coincidence. Each successor was scoped to close a specific gap the access-control model could not close from inside.
&lt;p&gt;With the gaps named and the successors mapped, what does an administrator actually do today?&lt;/p&gt;
&lt;h2&gt;15. Practical Guide&lt;/h2&gt;
&lt;p&gt;Six concrete recommendations for 2026, each tied to a primary Microsoft Learn or named-expert source.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;whoami /all&lt;/code&gt; prints the SIDs in the calling thread&apos;s token, the integrity level, every privilege with its &lt;code&gt;Enabled&lt;/code&gt; / &lt;code&gt;Disabled&lt;/code&gt; / &lt;code&gt;Default Enabled&lt;/code&gt; state, and -- on AD-joined machines with claims -- the user and device claim set. It is the single most useful diagnostic command for understanding what a session can do. Read the &lt;code&gt;Enabled&lt;/code&gt; column carefully: an available-but-disabled privilege does not affect any access check until the process explicitly enables it [@ms-learn-access-tokens].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;icacls &amp;lt;path&amp;gt;&lt;/code&gt; prints the DACL on a file or directory; the mass-rights letters are &lt;code&gt;(F)&lt;/code&gt; full, &lt;code&gt;(M)&lt;/code&gt; modify, &lt;code&gt;(RX)&lt;/code&gt; read and execute, &lt;code&gt;(R)&lt;/code&gt; read, &lt;code&gt;(W)&lt;/code&gt; write, &lt;code&gt;(D)&lt;/code&gt; delete, &lt;code&gt;(GA)&lt;/code&gt; generic all, &lt;code&gt;(GR)&lt;/code&gt; generic read, &lt;code&gt;(GW)&lt;/code&gt; generic write [@ms-learn-how-dacls-control-access]. PowerShell&apos;s &lt;code&gt;Get-Acl&lt;/code&gt; returns the same descriptor as a structured object that can be filtered and audited at scale. Sysinternals &lt;code&gt;accesschk.exe&lt;/code&gt; answers the inverted query (which paths grant a given SID a given right) and is the right tool for catching descriptor misconfigurations across a large file system. Treat NULL DACL and empty DACL surfaces as the most-likely misconfiguration vectors.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; On every Windows server, enumerate the principals whose tokens hold &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; or &lt;code&gt;SeAssignPrimaryTokenPrivilege&lt;/code&gt; in their &lt;em&gt;Default&lt;/em&gt; or &lt;em&gt;Available&lt;/em&gt; lists. Treat any non-LocalSystem-or-equivalent holder as a Potato target until proven otherwise. Where a service must hold the privilege (most managed-service workloads do), constrain the blast radius with per-service SIDs and Group Managed Service Accounts so that a compromise of one service does not extend to a compromise of every service that shares the host&apos;s identity [@github-itm4n-printspoofer].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The UACMe README is the institutional memory for the seventy-method bypass canon. Every method&apos;s &lt;code&gt;Fixed in:&lt;/code&gt; field cites a specific Windows version or &lt;code&gt;unfixed&lt;/code&gt;. Before declaring a binary &quot;patched,&quot; consult the README; a method with a &lt;code&gt;Fixed in: unfixed&lt;/code&gt; annotation is structurally available on every supported Windows version. The institutional position is that UAC bypasses do not, by Microsoft&apos;s own servicing-criteria policy, earn CVEs of their own, so the mitigations are issued per-redirect rather than per-feature [@github-hfiref0x-uacme, @msrc-servicing-criteria].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Windows Event ID 4688 (&quot;A new process has been created&quot;) is the most-cited detection signal for the Potato lineage and the UAC bypass tradition, because almost every member of both families ends in a &lt;code&gt;CreateProcessAsUser&lt;/code&gt; or a redirected AutoElevate launch with a command-line argument that does not match the legitimate use of the parent binary. Enable command-line auditing under &lt;em&gt;Audit Process Creation&lt;/em&gt; and forward the log; Sysmon Event ID 1 is the equivalent and richer signal in environments that deploy Sysinternals&apos; Sysmon.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The access matrix is the part of the model with deliberately extensible &lt;em&gt;subjects&lt;/em&gt;. New code that lives behind an AppContainer SID gets a Low-IL token, a Package SID, and a capability list that constrain what it can touch even when the user running it is an administrator. New file shares that need attribute-based authorization should use conditional ACEs and Dynamic Access Control rather than ad-hoc group membership. Cross-link to the &lt;a href=&quot;https://paragmali.com/blog/windows-app-identity-33-year-reinvention/&quot; rel=&quot;noopener&quot;&gt;App Identity sibling article&lt;/a&gt; for Package SID derivation [@app-identity-sibling] and to the Dynamic Access Control overview [@ms-learn-dac].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{`
// Inputs:
//   token       -- {sids:[...], integrity:&apos;Low&apos;|&apos;Medium&apos;|&apos;High&apos;|&apos;System&apos;,
//                   appContainer:bool, capabilities:[...], claims:{...},
//                   privileges:{enabled:[...]}}
//   descriptor  -- {dacl:[...], integrityLabel:&apos;Low&apos;|...|&apos;System&apos;,
//                   policyNoWriteUp:bool}
//   desired     -- access mask (number)
// Output: {granted, status, fired:[...]}&lt;/p&gt;
&lt;p&gt;function fullAccessCheck(token, descriptor, desired) {
  const fired = [];
  const ilOrder = {Low:1, Medium:2, High:3, System:4};&lt;/p&gt;
&lt;p&gt;  // 1. MIC check fires before DACL walk.
  if (descriptor.policyNoWriteUp) {
    const writeBits = 0x00040000 | 0x00000002 | 0x00000004; // WRITE_DAC|WRITE|APPEND
    if ((desired &amp;amp; writeBits) &amp;amp;&amp;amp; ilOrder[token.integrity] &amp;lt; ilOrder[descriptor.integrityLabel]) {
      fired.push(&apos;MIC no-write-up: token IL &apos; + token.integrity + &apos; &amp;lt; object IL &apos; + descriptor.integrityLabel);
      return {granted:0, status:&apos;DENIED at integrity check&apos;, fired};
    }
  }&lt;/p&gt;
&lt;p&gt;  // 2. Privilege bypass short-circuit.
  if (token.privileges.enabled.includes(&apos;SeBackupPrivilege&apos;) &amp;amp;&amp;amp; (desired &amp;amp; 0x80000000)) {
    fired.push(&apos;SeBackupPrivilege bypass: GENERIC_READ granted&apos;);
    return {granted: desired, status:&apos;GRANTED via SeBackupPrivilege&apos;, fired};
  }&lt;/p&gt;
&lt;p&gt;  // 3. AppContainer capability check (simplified).
  if (token.appContainer &amp;amp;&amp;amp; descriptor.requiresCapability) {
    if (!token.capabilities.includes(descriptor.requiresCapability)) {
      fired.push(&apos;AppContainer capability check: missing &apos; + descriptor.requiresCapability);
      return {granted:0, status:&apos;DENIED at AppContainer check&apos;, fired};
    }
  }&lt;/p&gt;
&lt;p&gt;  // 4. DACL walk, deny-first, in canonical order.
  let remaining = desired;
  let granted = 0;
  for (const ace of (descriptor.dacl || [])) {
    if (!token.sids.includes(ace.sid)) continue;
    if (ace.condition &amp;amp;&amp;amp; !evalCondition(ace.condition, token.claims)) continue;
    if (ace.type === &apos;DENY&apos; &amp;amp;&amp;amp; (ace.mask &amp;amp; remaining) !== 0) {
      fired.push(&apos;Conditional/plain DENY: &apos; + ace.sid);
      return {granted:0, status:&apos;DENIED at DACL&apos;, fired};
    }
    if (ace.type === &apos;ALLOW&apos;) {
      const newBits = ace.mask &amp;amp; remaining;
      granted |= newBits;
      remaining &amp;amp;= ~newBits;
      fired.push(&apos;ALLOW &apos; + ace.sid + &apos;: granted 0x&apos; + newBits.toString(16));
    }
    if (remaining === 0) {
      return {granted, status:&apos;GRANTED&apos;, fired};
    }
  }
  return {granted, status: remaining === 0 ? &apos;GRANTED&apos; : &apos;DENIED end of DACL&apos;, fired};
}&lt;/p&gt;
&lt;p&gt;function evalCondition(expr, claims) {
  // Toy evaluator for &quot;(@User.Department == \&quot;Finance\&quot;)&quot;-style expressions.
  const m = expr.match(/@User\.(\w+)\s*==\s*&quot;([^&quot;]+)&quot;/);
  if (!m) return true;
  return claims[m[1]] === m[2];
}&lt;/p&gt;
&lt;p&gt;// Demo: a Medium-IL user trying to write to a System-IL object via an allow ACE.
console.log(fullAccessCheck(
  {sids:[&apos;S-1-5-21-X-Y-Z-1001&apos;], integrity:&apos;Medium&apos;, appContainer:false, capabilities:[], claims:{Department:&apos;Finance&apos;},
   privileges:{enabled:[]}},
  {dacl:[{type:&apos;ALLOW&apos;, sid:&apos;S-1-5-21-X-Y-Z-1001&apos;, mask:0xFFFFFFFF}],
   integrityLabel:&apos;System&apos;, policyNoWriteUp:true},
  0x00040000));  // WRITE_DAC
`}&lt;/p&gt;
&lt;p&gt;The simulator runs the full plane in order: MIC integrity check, privilege bypass short-circuit, AppContainer capability check, DACL walk with conditional-ACE evaluation. Reading the &lt;code&gt;fired&lt;/code&gt; log in the output tells you which primitive made the decision and why. It is the mental model the rest of the article has been building toward.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The six tips and the simulator together close the practical loop. With them, the practitioner can reason about any specific access decision the way the kernel does -- not by remembering features, but by walking the same plane.&lt;/p&gt;
&lt;p&gt;The Sysinternals &lt;code&gt;accesschk&lt;/code&gt; and &lt;code&gt;psgetsid&lt;/code&gt; utilities have long been first-line investigative tools for ACL audits. Both ship in the Sysinternals Suite today and continue to surface the same descriptors &lt;code&gt;Get-Acl&lt;/code&gt; and &lt;code&gt;icacls&lt;/code&gt; print, in the form most useful to an administrator working at scale.&lt;/p&gt;
&lt;h2&gt;16. Frequently Asked Questions&lt;/h2&gt;


No. By Microsoft&apos;s own *Security Servicing Criteria for Windows*, UAC and admin-to-kernel are not on the enumerated security-boundary list [@msrc-servicing-criteria]; bypasses are not, by policy, eligible for security servicing as boundary violations. The seventy-plus methods catalogued in UACMe are the empirical consequence of the classification, not a long string of bugs in a feature that was meant to defend against the techniques [@github-hfiref0x-uacme].


No. `Everyone` (the well-known SID `S-1-1-0`) is just a SID. ACEs that reference it are subject to the same DACL walk as any other SID; if a deny ACE for `Everyone` precedes an allow ACE for `Authenticated Users`, access is denied. The DACL evaluation algorithm does not know `Everyone` is special. Forshaw made the point with a meme-able rant in 2020: &quot;S-1-1-0 is NOT A SECURITY BOUNDARY&quot; [@tiraniddo-sharing-logon-session].


No. *Empty* DACL denies all access. *No* DACL (a NULL DACL) grants full access. Newly-written code that &quot;creates a file with no protection&quot; almost always gets this distinction wrong [@ms-learn-how-dacls-control-access]. Verify with `Get-Acl` or `icacls` after creation; an `icacls` output of `Everyone:(F)` on an object you intended to lock down is almost always a NULL DACL, not the policy you meant to write.


For DACL alone, yes. For MIC, no -- a System-IL administrator still cannot write to a process at higher integrity if MIC denies the request, because the integrity check fires before the DACL walk [@ms-learn-mic]. For AppContainer, no -- AppContainer-bound objects require the capability SID, not just an administrator SID. For VBS Trustlets, no -- secrets in VTL1 are unreachable from VTL0 even with administrator rights, which is the whole point of the architecture [@secure-kernel-sibling].


No. The list shows *available* privileges. The `Enabled` column is the one that matters for runtime decisions; available-but-disabled privileges must be enabled via `AdjustTokenPrivileges` before any privileged operation can use them. Most privileges are disabled by default precisely so that a process must explicitly opt in to using one, which lets a security-conscious application minimise the window in which a bug can abuse the privilege [@ms-learn-privileges].


No. The underlying NTLM-to-self surface is still open. SharpEfsPotato (2021) [@github-bugch3ck-sharpefspotato] is the most recent member of the lineage; new tools using fresh coercion primitives (EFSRPC, the spooler, the schedule-task COM interface, cross-session DCOM) appear every twelve to eighteen months. Microsoft&apos;s own Hot Potato post called the underlying issue &quot;hard to fix without breaking backward compatibility&quot; [@foxglove-hot-potato-blog]; the structural fix is the Adminless and NTLMless successor articles, not a point patch on any one primitive.


Partially. AppContainer is a process-level isolation mechanism with a Low-IL token plus a capability-SID list. It is also a *named principal* in the Windows access-control model -- something Chrome&apos;s sandbox is not -- which means AppContainer-bound code can be referenced by SID in a DACL or conditional ACE, and Windows can refuse access to it as a principal in its own right. The sibling App Identity article in this series covers the Package SID derivation and the relationship to Authenticode and App Control for Business [@app-identity-sibling].

&lt;h2&gt;17. Closing: Return to the Hook&lt;/h2&gt;
&lt;p&gt;Open a Windows PowerShell window again. Run &lt;code&gt;whoami /priv&lt;/code&gt;. Read the column on the right, this time with the article&apos;s vocabulary annotated above each line.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SeShutdownPrivilege&lt;/code&gt; -- a privilege, in the kernel sense of a pre-checked superpower; bookkeeping rather than power.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SeIncreaseWorkingSetPrivilege&lt;/code&gt; -- the same. Most of the twenty rows are housekeeping that the kernel checks at specific call sites to gate non-security-critical operations.&lt;/p&gt;
&lt;p&gt;The five rows that matter are easy to spot once you know what to look for. &lt;code&gt;SeDebugPrivilege&lt;/code&gt; -- Mimikatz starts here. &lt;code&gt;SeImpersonatePrivilege&lt;/code&gt; -- the entire Potato lineage starts here. &lt;code&gt;SeAssignPrimaryTokenPrivilege&lt;/code&gt; -- the second half of every token-replay attack. &lt;code&gt;SeBackupPrivilege&lt;/code&gt; -- HiveNightmare&apos;s privilege class. &lt;code&gt;SeRestorePrivilege&lt;/code&gt; -- service-binary replacement. The kernel reads the same column on every securable operation, billions of times a second, and the answer to &quot;can this code do this?&quot; is built out of this list every time.&lt;/p&gt;
&lt;p&gt;Now run &lt;code&gt;icacls C:\Windows\System32\drivers\etc\hosts&lt;/code&gt; again. &lt;code&gt;BUILTIN\Administrators:(F)&lt;/code&gt; is an allow ACE granting full control to the well-known SID &lt;code&gt;S-1-5-32-544&lt;/code&gt;. &lt;code&gt;NT AUTHORITY\SYSTEM:(F)&lt;/code&gt; is an allow ACE granting full control to &lt;code&gt;S-1-5-18&lt;/code&gt;. &lt;code&gt;BUILTIN\Users:(R)&lt;/code&gt; is an allow ACE granting &lt;code&gt;FILE_GENERIC_READ&lt;/code&gt; to &lt;code&gt;S-1-5-32-545&lt;/code&gt;. The DACL is in canonical order: explicit entries before inherited entries, deny entries (none here) before allow entries within each group. &lt;code&gt;SeAccessCheck&lt;/code&gt; will walk this DACL on every read of &lt;code&gt;hosts&lt;/code&gt; from any process on the machine, and the output will be deterministic -- the same answer every time, for the same caller -- because the model that produces it is closed and finite.&lt;/p&gt;
&lt;p&gt;The article&apos;s payoff. Every later post in this series starts where this one ends. The Adminless article retires the bearer-credential property of long-lived tokens. The NTLMless article retires the local-NTLM-to-self relay surface. The Secure Kernel article hosts secrets in VTL1 outside the NT kernel&apos;s address space and tells the Credential Guard story in detail [@secure-kernel-sibling]. The Pluton article roots the hardware identity chain that the successor architectures all eventually verify against [@pluton-sibling]. The TPM article and the Secure Boot article cover the static-time and boot-time chains that run before the access-control model even loads. Each successor was scoped to close a specific gap the access-control model could not close from inside.&lt;/p&gt;
&lt;p&gt;NT 3.1 froze a model in July 1993 because federal procurement demanded it. That model has not structurally changed in thirty-three years. The accumulated attack surface against it -- twenty-five years, eight Potatoes, seventy UAC bypasses, one Mimikatz -- is the empirical proof that &quot;frozen&quot; was always going to mean &quot;attackable from below.&quot; The next generation of defences takes that lesson and stops trying to fix the model from inside. The model is not a feature catalogue. It is a decision plane with five inputs, ten primitives, and five publicly conceded structural limits, and the four successor architectures of the next decade are the four non-overlapping ways to close those limits without re-evaluating against TCSEC C2 again.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SeAccessCheck&lt;/code&gt; decides every time. The next decade decides what it decides about.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;windows-access-control-twenty-five-years&quot; keyTerms={[
  { term: &quot;SeAccessCheck&quot;, definition: &quot;The kernel routine that decides whether a thread may perform a requested set of operations on an object. Takes a security descriptor, an access token, a desired-access mask, a generic-mapping table, and previously-granted access; returns the granted access mask plus a status code.&quot; },
  { term: &quot;Security Reference Monitor (SRM)&quot;, definition: &quot;The kernel-mode component that owns SeAccessCheck and the audit log generation routines. Every other kernel component that needs to grant or deny access calls into it.&quot; },
  { term: &quot;Access Token&quot;, definition: &quot;A kernel object that names the security identity of a thread or process. Carries the user&apos;s SID, group SIDs, privileges, integrity level, primary/impersonation flag, and (for restricted tokens) a list of restricting SIDs. The kernel consults the token on every access check.&quot; },
  { term: &quot;Discretionary Access Control List (DACL)&quot;, definition: &quot;The ordered list of allow / deny ACEs attached to a securable object. The object&apos;s owner controls the contents, in contrast to a mandatory list.&quot; },
  { term: &quot;Mandatory Integrity Control (MIC)&quot;, definition: &quot;A Vista-era addition that adds an integrity-level check to SeAccessCheck. The integrity check fires before the DACL walk and enforces no-write-up by default.&quot; },
  { term: &quot;User Account Control (UAC)&quot;, definition: &quot;A Vista-era split-token mechanism in which an administrative user receives two linked tokens at logon: a filtered Medium-IL standard-user token and a full High-IL administrative counterpart. Not, by Microsoft&apos;s own servicing criteria, an enforced security boundary.&quot; },
  { term: &quot;SeImpersonatePrivilege&quot;, definition: &quot;The privilege that lets a service accept an impersonation token from a client. Held by every Windows service account by default. The load-bearing privilege for the entire Potato lineage.&quot; },
  { term: &quot;Confused Deputy&quot;, definition: &quot;Norm Hardy&apos;s 1988 framing of the structural failure mode of any ambient-authority access-control system: a privileged service can be tricked into using its own authority on the attacker&apos;s behalf because the system cannot distinguish authority the service has from authority the service is being asked to use.&quot; },
  { term: &quot;VBS Trustlet&quot;, definition: &quot;A Windows process that runs in Virtual Trust Level 1, a hardware-isolated user-mode environment whose memory the NT kernel running in VTL0 cannot read or write. The architectural answer to the admin-equals-kernel concession of the access-control model.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>access-control</category><category>privilege-escalation</category><category>security-tokens</category><category>uac</category><category>mimikatz</category><category>potato-attacks</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>