78 min read

"Can This Code Do This?" -- Twenty-Five Years of Attacks on the Windows Access-Control Model

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.

Permalink

1. One Question, Billions of Times a Second

Open a Windows PowerShell window and run whoami /priv. Read the column on the right. SeShutdownPrivilege. SeUndockPrivilege. SeIncreaseWorkingSetPrivilege. SeTimeZonePrivilege. About twenty rows of capabilities, almost all marked Disabled, on a token that lives inside explorer.exe's memory and that the kernel consults billions of times a second.

Now run icacls C:\Windows\System32\drivers\etc\hosts. The output reads BUILTIN\Administrators:(F), NT AUTHORITY\SYSTEM:(F), BUILTIN\Users:(R). Six characters per principal, decoded by something inside the kernel called SeAccessCheck, 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 [1].

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 [2]. 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 [3, 4, 5].

The thesis comes in three convictions:

  1. SeAccessCheck is the answer. Every securable Windows operation that touches a securable object resolves through one decision function with one set of inputs [6].
  2. Every famous Windows escalation tool attacks 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 DACL on a single file [7]. The vocabulary scales.
  3. The model has five structural limits its keepers have publicly conceded [8], and Adminless, NTLMless, VBS Trustlets, and Credential Guard are the four non-overlapping ways to close them.

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 (SeAccessCheck), two attack families (the Potato lineage and the UACMe bypass tradition), and four successor architectures (Adminless, NTLMless, VBS Trustlets, Credential Guard).

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?

2. Origins: From Lampson's Matrix to Cutler's Kernel (1971-1993)

The vocabulary starts in a paper Butler Lampson presented at Princeton in 1971 and the ACM republished in Operating Systems Review in January 1974 [9, 10]. Lampson framed protection as a 2-D matrix: rows index the subjects (users, processes), columns index the objects (files, devices, memory pages), and the cell at the intersection holds the operations 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.

Two implementation strategies fall out of the formalism. Slice the matrix by row and you get capability lists: each subject carries a token that names the objects it can touch. Slice by column and you get access-control lists: 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's "Confused Deputy" in a moment.

The lever that turned theory into Windows came from procurement, not academia. On December 26, 1985, the U.S. Department of Defense published DoD 5200.28-STD, the Trusted Computer System Evaluation Criteria, known by the colour of its cover as the Orange Book [11]. The Orange Book defined four divisions of trusted-system assurance, and its C2 class -- "Controlled Access Protection" -- 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 [11]. After 1985, no operating system that wanted U.S. federal customers could ship without per-user ACLs and an audit log.

A year before the Orange Book ossified DAC into procurement, Norm Hardy of the Tymshare / KeyKOS lineage published a three-page paper in Operating Systems Review that named the structural limit of the entire ACL-shaped class: "The Confused Deputy (or why capabilities might have been invented)" [12, 13]. Hardy described a privileged compiler that wrote billing records to a system file. A user could trick the compiler into writing the user's output file over the billing file, because the compiler used its own authority on every write and could not distinguish "authority I have" from "authority the caller asked me to use."

The Wikipedia summary of the field is exact: "Capability systems protect against the confused deputy problem, whereas access-control list-based systems do not" [14]. Hold this paper. It will come back in Section 10.

NT 3.1 released to manufacturing on July 27, 1993 [2]. NT 3.5, released to manufacturing on September 21, 1994 [18], 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 [18]; NT 4.0 Service Pack 6a later cleared the C2 network class against the Red Book's Trusted Network Interpretation [11]. 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.

Cutler shipped a model that has answered "can this code do this?" the same way for thirty-three years. What is the actual function -- and what are its inputs?

3. The Kernel Oracle: SeAccessCheck and Its Inputs

The function has one signature, one return value, and one job. Microsoft's Win32 documentation exposes a user-mode mirror called AccessCheck that lets userland code ask the question without holding a handle, and a kernel routine called SeAccessCheck that the kernel invokes at every securable operation [1, 19]. The shape is the same in both directions:

SeAccessCheck(SD,Token,DesiredAccess)(GrantedAccess,Status)\textsf{SeAccessCheck}(\textit{SD}, \textit{Token}, \textit{DesiredAccess}) \to (\textit{GrantedAccess}, \textit{Status})

Three inputs in (a security descriptor, an access token, a requested-access mask), two outputs out (the access mask actually granted, and a STATUS_ACCESS_DENIED flavour if any). Two more hidden inputs make the kernel signature precise: a generic mapping table that translates the four generic rights (GENERIC_READ, GENERIC_WRITE, GENERIC_EXECUTE, GENERIC_ALL) into object-type-specific bits, and a previously-granted-access 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 AccessMode discriminator that elides the check for kernel-mode callers, and a privileges out-parameter -- which the explanatory five-input model collapses; the user-mode AccessCheck mirror is closer to the model the article uses [20, 21]).

Security Reference Monitor (SRM)

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.

SeAccessCheck

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 [20, 21].) 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 [6].

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.

A security descriptor 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 [22]. The DACL is what icacls prints. The SACL is what writes Event Log entries when something the descriptor's writer wanted to watch happens.

An access token is the data structure attached to the caller. It names the user (one SID), the user'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's label, and a flag that says whether the token is a primary token (one per process) or an impersonation token (one per thread, used to act on a client's behalf) [19].

Microsoft's documentation lists the token's contents almost as bullet points: "The security identifier (SID) for the user'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'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..." [19].

The flow inside SeAccessCheck is mechanical. The kernel maps DesiredAccess from generic to specific bits using the type's mapping table. It checks the integrity label of the object against the integrity level on the token (Mandatory Integrity Control runs before 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's list. It applies a privilege bypass short-circuit if the caller holds SeBackupPrivilege, SeRestorePrivilege, SeDebugPrivilege, or one of the other privileges that grants access to whole classes of objects without consulting the DACL. It returns the accumulated GrantedAccess, or STATUS_ACCESS_DENIED if the requested bits were not all granted.

Ctrl + scroll to zoom
A single OpenObject call traced through the kernel's access-control plane: every primitive in this article fires for one access decision.

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's subject 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 SeAccessCheck's five inputs, and every famous attack tool in the article maps cleanly onto one primitive.

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?

4. The DACL Algorithm and the SID Namespace

"Walk the DACL in order." 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 "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's access token... one or more access-allowed ACEs for trustees listed in the thread'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" [6].

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.

Discretionary Access Control List (DACL)

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

Access-Control Entry (ACE)

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 [23].

JavaScript The SeAccessCheck DACL walk, ACE by ACE
// 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.

function seAccessCheck(token, dacl, desiredAccess) {
let remaining = desiredAccess;       // bits still needed
let granted = 0;                     // bits accumulated so far
const trace = [];

// NULL DACL grants everything; empty DACL grants nothing.
if (dacl === null) {
  return { status: 'GRANTED (NULL DACL)', granted: desiredAccess, trace: ['NULL DACL: full access'] };
}
if (dacl.length === 0) {
  return { status: 'DENIED (empty DACL)', granted: 0, trace: ['Empty DACL: no access'] };
}

for (let i = 0; i < 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 === 'DENY') {
    const deniedBits = ace.mask & remaining;
    if (deniedBits !== 0) {
      trace.push(`ACE ${i}: DENY hits 0x${deniedBits.toString(16)} -> ACCESS_DENIED`);
      return { status: 'DENIED', granted: 0, trace };
    }
    trace.push(`ACE ${i}: DENY ${ace.sid} 0x${ace.mask.toString(16)} -- no requested bit hit; skip`);
  } else if (ace.type === 'ALLOW') {
    const newBits = ace.mask & remaining;
    granted |= newBits;
    remaining &= ~newBits;
    trace.push(`ACE ${i}: ALLOW grants 0x${newBits.toString(16)}, remaining 0x${remaining.toString(16)}`);
    if (remaining === 0) {
      return { status: 'GRANTED', granted, trace };
    }
  }
}
return { status: 'DENIED (end of DACL with bits unsatisfied)', granted: 0, trace };
}

// Demo: a token with the user's SID and BUILTIN\Users; DACL with explicit deny + Everyone allow.
const token = { sids: ['S-1-5-21-A-B-C-1001', 'S-1-5-32-545', 'S-1-1-0'] };
const dacl = [
{ type: 'DENY',  sid: 'S-1-5-21-A-B-C-1001', mask: 0x00040000 },  // deny WRITE_DAC
{ type: 'ALLOW', sid: 'S-1-1-0',              mask: 0x00120089 }, // FILE_GENERIC_READ
{ type: 'ALLOW', sid: 'S-1-5-32-545',         mask: 0x001200A9 }, // GENERIC_READ + EXECUTE
];
console.log(seAccessCheck(token, dacl, 0x00040089));

Press Run to execute.

The runnable above implements three subtleties the prose can flatten. First: the NULL DACL versus empty DACL distinction. A descriptor with no DACL at all -- a literal NULL 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.

Second: ACE order is the caller's responsibility. The kernel walks the list in the order it finds it. The "canonical" order Windows expects is four-step, quoted verbatim from the Microsoft Learn reference [24]: "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."

The same page underlines who has to enforce the order: "Functions such as AddAccessAllowedAceEx and AddAccessAllowedObjectAce add an ACE to the end of an ACL. It is the caller's responsibility to ensure that the ACEs are added in the proper order." 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.

Third: there is no special case for "Everyone." The well-known SID S-1-1-0 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 "if this is Everyone, treat it differently from any other group." James Forshaw made the point with characteristic bluntness in 2020: "don't forget S-1-1-0, this is NOT A SECURITY BOUNDARY. Lah lah, I can't hear you!" [25]. The DACL evaluation algorithm does not know "Everyone" is special. It is a SID like any other.

That makes the SID namespace itself worth a tour. Microsoft documents the structure as a revision number, an identifier authority (a six-byte field that says which authority issued the SID), a list of sub-authorities, and a final relative identifier (RID) [26]. Microsoft's own page on SIDs is precise: "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."

The well-known SIDs the kernel recognises by name include SYSTEM (S-1-5-18), LocalService (S-1-5-19), NetworkService (S-1-5-20), Everyone (S-1-1-0), Authenticated Users (S-1-5-11), and the four Mandatory Integrity Control levels (S-1-16-4096 / 8192 / 12288 / 16384) [27, 22]. Machine-issued SIDs encode the machine'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.

Security Identifier (SID)

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 [26].

James Forshaw documented in 2017 that Windows generates the per-service SID for NT SERVICE\<ServiceName> deterministically: it is the SHA-1 hash of the uppercased service name, formatted into the SID'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 [28].

Two SID families this article will not derive: AppContainer Package SIDs (S-1-15-2-...) and capability SIDs (S-1-15-3-...). Both arrived with Windows 8 in 2012 and extend the matrix's subjects with code identity and capability tokens. The sibling App Identity article in this series carries the canonical derivation, including the Crockford-Base32 PublisherId derivation that produces a Package SID from an MSIX package signature [29]. Section 5 of this article will mention those SIDs in tokens; we will not redefine them here.

The DACL is half the story. What does the thread bring to the access check -- and why is the answer "whoever happens to hold the handle"?

5. Tokens as Bearer Credentials

A token is not a credential the way a password is. A token is a credential the way cash is: whoever holds it gets the rights. This is the single most important property in the article.

Microsoft splits tokens into two flavours by purpose [19]. A primary token hangs off a process and represents the security identity that process runs as. Every process has exactly one. An impersonation token 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's table, the kernel grants the rights the token names.

Access Token

A kernel object that names the security identity of a thread or process. It carries the user's SID, the SIDs of the user'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) [19]. The kernel consults a token on every access check for the thread that holds it.

Primary Token vs Impersonation Token

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 [19].

The impersonation flavour acquired its modern shape in Windows 2000. A token's impersonation level takes one of four values, ordered from least to most privileged for the impersonator. Anonymous lets the server know nothing about the client. Identification lets the server learn the client's SIDs but not act as the client. Impersonation lets the server perform local access checks as the client; this is the level a typical RPC server requests. Delegation lets the server forward the client'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 Impersonation-level token; that is enough to call ImpersonateLoggedOnUser and run as the client [30].

Microsoft documents a third token shape, the restricted token, 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 deny-only (their grants count for no allow ACE but their presence still triggers deny ACEs), a list of restricting SIDs that the access check must independently permit, and a list of privileges removed from the token's privilege set [31].

The kernel runs SeAccessCheck twice and grants only the intersection: "When a restricted process or thread tries to access a securable object, the system performs two access checks: one using the token's enabled SIDs, and another using the list of restricting SIDs. Access is granted only if both access checks allow the requested access rights" [31]. Restricted tokens are operationally niche because the same documentation requires applications using them to "run the restricted application on desktops other than the default desktop. This is necessary to prevent an attack by a restricted application, using SendMessage or PostMessage, to unrestricted applications on the default desktop" [31]. Few applications can spare the desktop overhead.

whoami /priv shows available privileges, not enabled privileges. The Enabled column is the load-bearing one: an available-but-disabled privilege does not affect any access check until the process explicitly enables it via AdjustTokenPrivileges. 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.

A token also carries flags that drive specific runtime behaviours: a split-token indicator points at a linked full-administrator counterpart for the UAC scenario in Section 8; an AppContainer 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.

This is the property that organises the rest of the article.

The Windows access token is a bearer credential. 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 token::elevate, 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.

If the token is a bearer credential, anyone with a way to obtain a SYSTEM token's handle is SYSTEM. Every Potato in Section 10 will be a different way to provoke a SYSTEM-token handle into the attacker's process. But the access check has another input that bypasses the DACL entirely. What is it, and which attackers know about it?

6. Privileges as a Different Dimension

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.

Microsoft's framing on Learn is exact: "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" [32]. The same page makes the operational consequence clear: "Most privileges are disabled by default" [32]. A process that holds a privilege but has not enabled it (via AdjustTokenPrivileges) cannot use it. The discipline is "principle of least privilege at the millisecond level" -- the privilege is on the token, but it does nothing until the program explicitly turns it on for the next system call.

Privilege

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'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 [32].

The reason privileges deserve a section of their own is that five of them are equivalent to "I am SYSTEM" 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.

SeDebugPrivilege lets the holder open any process for full read and write, including processes running as SYSTEM. The privilege exists so that Visual Studio and WinDbg can debug code other users have started. The first move in almost every Mimikatz session is privilege::debug, which enables the privilege the local administrator already has on the token [3, 33]. Once enabled, the next Mimikatz command opens lsass.exe and reads the credentials.

SeImpersonatePrivilege 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. This is the load-bearing privilege for the Potato lineage [30]. Section 10 will spend twenty paragraphs on what a service account holding SeImpersonate can be tricked into doing; the privilege is the entry condition.

SeAssignPrimaryTokenPrivilege lets the holder launch a new process under any primary token. Combined with SeImpersonate, the pair gives an attacker the entire token-replay attack: get an impersonation handle to a SYSTEM token, then call CreateProcessAsUser to run a command as SYSTEM. itm4n quotes decoder_it on the operational consequence.

"if you have SeAssignPrimaryToken or SeImpersonate privilege, you are SYSTEM." -- decoder_it, quoted by itm4n in the PrintSpoofer disclosure [30]

SeBackupPrivilege lets the holder read any file regardless of DACL, on the theory that backup software has to. SeRestorePrivilege is the symmetric write privilege. The two together mean that a process holding both can rewrite any file on the machine, including service binaries.

The 2021 HiveNightmare / SeriousSAM 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: "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" [7]. The DACL on the live \Windows\System32\config\SAM was correct; the DACL on the Volume Shadow Copy mirror was overly permissive enough that any local user could read the SAM hashes through the shadow-copy device path.

Microsoft's fix required not just a patch but a manual operator step: "After installing this security update, you must manually delete all shadow copies of system files, including the SAM database, to fully mitigate this vulnerability" [7]. A patch alone could not erase the historical shadow copies that already had the wrong DACL.

SeTcbPrivilege lets the holder "act as the operating system" -- it is the privilege that grants identity to the kernel itself. Held only by LocalSystem services in well-administered environments. A non-system process that somehow acquired SeTcb is, in effect, indistinguishable from the kernel.

SeLoadDriverPrivilege 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 bring-your-own-vulnerable-driver (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.

The kernel-read/write half of the class is best illustrated by Micro-Star's RTCore64.sys (CVE-2019-16098 [34]), the MSI Afterburner driver that "allows any authenticated user to read and write to arbitrary memory, I/O ports, and MSRs" [34]. In October 2022, the threat actors behind the BlackByte ransomware weaponised the primitive at scale [35]: the dropper loaded RTCore64.sys, then walked the kernel's PspCreateProcessNotifyRoutine callback array and zeroed every entry, blinding every registered process-creation callback before the encryption stage ran.

Sophos's October 4, 2022 disclosure named the technique exactly: "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" [35].

The kernel-code-execution half of the class is GIGABYTE's gdrv.sys (CVE-2018-19320 [36]). The NVD description states the primitive directly: "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" [36]. A signed IOCTL 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 RTCore64.sys to mint arbitrary kernel code execution.

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 [36]. The U.S. federal-civilian executive branch had two weeks to remediate; the rest of the install base did not.

Both RTCore64.sys and gdrv.sys appear on the blocklist; the ride from disclosure to default-on enforcement was four years for RTCore64, four years for gdrv, and the same arc applies to every member of the class. Honourable mention: aswArPot.sys (CVE-2022-26522 / CVE-2022-26523 [38]) shows the same pattern from a security-product driver, with SentinelLabs reporting "two high severity flaws in Avast and AVG... that went undiscovered for years affecting dozens of millions of users" before the silent fix [38].

The DACL is the load-bearing thing for file access, but exactly one bad DACL 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's input list has at least one production example where misuse of that primitive alone dropped the security model to zero.

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?

7. Mandatory Integrity Control: Stapling No-Write-Up onto DAC

November 2006. Vista releases to manufacturing, and for the first time in Windows history, the kernel's access check fires before the DACL walk -- on something other than the user's identity. The new layer is Mandatory Integrity Control (MIC), and it adds a four-level lattice to every securable object in the system.

Microsoft Learn frames MIC compactly. "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's DACL allows write access to the principal" [22]. The same page enumerates the levels: "Windows defines four integrity levels: low, medium, high, and system" [22]. 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) [27].

Mandatory Integrity Control (MIC)

A Windows kernel mechanism, introduced with Vista (released to manufacturing November 8, 2006; consumer general availability January 30, 2007 [39]), 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 [22].

Integrity Level

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'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) [27].

The default policy is no-write-up. A process at integrity level LL cannot modify an object at integrity level greater than LL, regardless of what the DACL says. Microsoft'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 before the DACL walk; if the integrity check denies, the DACL is not consulted [22].

The integrity label is stored as a SYSTEM_MANDATORY_LABEL_ACE inside the SACL, not the DACL [40]. The mask field on the label ACE encodes which directions of access the policy forbids: SYSTEM_MANDATORY_LABEL_NO_WRITE_UP (0x1), SYSTEM_MANDATORY_LABEL_NO_READ_UP (0x2), and SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP (0x4) [40].

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's protection. The opposite mistake -- a Medium-IL file losing its label and dropping to Low -- is the more dangerous one.

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 WRITE_DAC (bit 18 of the 32-bit ACCESS_MASK [41]) and WRITE_OWNER (bit 19), the type-generic DELETE (bit 16), and the file-specific FILE_WRITE_DATA (0x2), FILE_APPEND_DATA (0x4), FILE_WRITE_EA (0x10), and FILE_WRITE_ATTRIBUTES (0x100) [42].

The presence of FILE_APPEND_DATA in that list matters operationally: a careless reader of the spec might assume that "append" 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.

A second rule completes the model: process integrity inheritance. When a process is created, the kernel assigns it the minimum of the user's integrity level and the file's integrity level [22]. 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.

The first MIC consumer was IE7 Protected Mode, which shipped with Windows Vista RTM (released to manufacturing November 8, 2006) [43, 39]. (IE7 standalone for Windows XP, released October 18, 2006 [44], runs without Protected Mode -- the feature depends on Vista's MIC kernel layer.) Skywing's Uninformed Volume 8 Article 6, "Getting Out of Jail: Escaping Internet Explorer Protected Mode," is the first public reverse-engineering of the implementation.

Skywing's framing remains the most-cited primer on the subject: "With the introduction of Windows Vista, Microsoft has added a new form of mandatory access control to the core operating system. Internally known as 'integrity levels', 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" [43]. IE7'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's behalf -- became the template Windows would later generalise into AppContainer.

User Interface Privilege Isolation (UIPI) is the window-message gate that uses MIC at the desktop. A Low-IL window cannot send SendMessage or PostMessage 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 [27].

Windows 8 generalised the MIC pattern into AppContainer. An AppContainer process gets a fresh Low-IL token plus an AppContainer 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: "Windows ensures that processes running with a low integrity level cannot obtain access to a process which is associated with an app container" [22]. 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 [29].

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 .exe with full administrative authority? And why did Microsoft refuse to call its answer a security boundary?

8. UAC, the Split-Token, and the Bypass Tradition

Vista's User Account Control is the most famous Windows security retrofit, and the only one whose own keepers explicitly published a document declaring it not a security boundary [8]. The mechanism is precise. The bypass tradition is enormous. The classification is honest.

The mechanism first. Microsoft's documentation on how UAC works gives the verbatim recipe [45]: "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 explorer.exe. Explorer.exe 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."

User Account Control (UAC) and Split-Token Elevation

A Windows mechanism, introduced with Vista (released to manufacturing November 8, 2006 [39]), 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 [45, 8].

The split-token mechanism produces three elevation triggers. First, an executable can declare its required level in its manifest via the requestedExecutionLevel element (asInvoker, highestAvailable, or requireAdministrator). Second, certain Microsoft-signed binaries appear on an AutoElevate 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 COM Elevation Moniker, which lets code instantiate Elevation:Administrator!new:{guid} (or Elevation:Highest!new:{guid}) to obtain a High-IL administrator COM caller -- not a SYSTEM caller; the moniker's supported run levels are Administrator and Highest [46]. Method 41 (Oddvar Moe's ICMLuaUtil construction) is the canonical worked example.

The classification next. Microsoft's Security Servicing Criteria for Windows 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 [8]. The criteria document then enumerates which boundaries Microsoft commits to servicing. UAC and admin-to-kernel are not on the enumerated list.

"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 [8]

The "outside the enumerated list" 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's specific path becomes operationally noisy enough to warrant attention. The seventy-plus methods catalogued in UACMe are the empirical consequence.

UAC was never a security boundary. The seventy-plus methods catalogued in UACMe are not bugs in UAC. They are the formal consequence of UAC'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.

The bypass canon. Walk it generation by generation.

Method 1 -- Leo Davidson, 2009. Davidson's Windows 7 UAC Whitelist writeup is the genealogical root of the UAC bypass tradition [47, 5]. He noticed that sysprep.exe is on the AutoElevate whitelist and that, when launched from C:\Windows\System32\sysprep\, it loads several DLLs from the working directory. Drop a malicious cryptbase.dll next to sysprep.exe, launch sysprep.exe via the IFileOperation COM interface (which has an AutoApprove flag honoured by the elevator), and sysprep.exe runs with a full token, loads the attacker's DLL, and the attacker has SYSTEM.

Davidson's writeup quotes himself bluntly: "This works against the RTM (retail) and RC1 versions of Windows 7" [47]. UACMe Method 1 records the technique with structured metadata: "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)" [5].

Method 25 -- Matt Nelson (enigma0x3), August 15, 2016. Seven years after Davidson, Nelson published "Fileless UAC Bypass Using eventvwr.exe and Registry Hijacking" [48]. The bypass replaces the file-system DLL hijack with a registry redirect. Nelson noticed that eventvwr.exe, an AutoElevated binary, queries HKCU\Software\Classes\mscfile\shell\open\command before HKCR\mscfile\shell\open\command to find the command to run for the mscfile ProgID. His verbatim observation: "From the output, it appears that 'eventvwr.exe', as a high integrity process, queries both HKCU and HKCR registry hives to start mmc.exe" [48]. HKCU is writable by the standard user; the user writes a malicious command line under that key, runs eventvwr.exe, and the auto-elevated process happily executes the user-supplied command line. The first fileless UAC bypass.

Method 33 -- winscripting.blog, 2017. Same primitive, different target. The fodhelper.exe binary is on the AutoElevate whitelist and queries HKCU\Software\Classes\ms-settings\shell\open\command to launch the Settings app. UACMe records the credit precisely: "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" [5]. Fixed in: unfixed. This is what "outside the enumerated list" 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.

Method 41 -- Oddvar Moe. The COM elevation moniker route. UACMe records: "Author: Oddvar Moe / Type: Elevated COM interface / Method: ICMLuaUtil" [5]. Instantiate the CMLuaUtil COM object via the elevation moniker from a Medium-IL process, get back a High-IL COM caller, and call its ShellExec method to run an attacker command line at High IL. The seam is the COM moniker registry's Elevation\Enabled key, which marks specific CLSIDs as elevation-capable.

Method 31 (sdclt), 29, 34, ... The pattern repeats. Matt Nelson's sdclt.exe variants exploit the backup-restore UI's registry lookups. Forshaw's schtasks variant exploits the scheduled-task COM interface. The UACMe README enumerates the lot with the laconic one-liner "Defeating Windows User Account Control by abusing built-in Windows AutoElevate backdoor" [5]. 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.

Ctrl + scroll to zoom
The AutoElevate redirect template that 70+ UACMe methods reduce to: an auto-elevated Microsoft-signed binary performs a lookup the standard user can redirect, and supplies an attacker-controlled command line that executes at High IL.

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?

9. The Object Manager and the Lookup Surface

SeAccessCheck evaluates a security descriptor it has been handed. Who hands it the descriptor?

The kernel's Object Manager does, after walking a name. Every named kernel object lives somewhere in a hierarchical namespace rooted at \. Devices live under \Device. Synchronisation primitives live under \BaseNamedObjects. Pre-resolved DLL names live under \KnownDlls. Per-session subtrees live under \Sessions. The DOS device prefix \GLOBAL?? (and its session-local sibling \??) holds drive-letter symbolic links into the device tree. When a process calls OpenObject, the Object Manager parses the name, walks the tree, and returns the object whose security descriptor SeAccessCheck will then evaluate.

The kernel performs the lookup before the access check. This sequencing creates a parallel attack surface that bypasses SeAccessCheck entirely. If the attacker can influence the name resolution -- redirect a \??\ 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's descriptor -- then by the time SeAccessCheck runs, it is being asked about a different object than the original code path intended to open.

The HiveNightmare lookup path is the canonical worked example. The exploit reads the SAM database not via \Windows\System32\config\SAM (which has a tight DACL) but via \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy*\Windows\System32\config\SAM. That path resolves through the Object Manager's \GLOBAL?? symbolic link, into the device tree, into a Volume Shadow Copy mirror of the original volume, into a copy of SAM whose DACL was, until the August 2021 patch, world-readable [7].

The lookup-phase attack class is wider than file-system shadow copies. Object Manager symbolic links and NTFS hard links both produce the same primitive: the kernel resolves a name through an attacker-influenced redirect and ends up evaluating SeAccessCheck against a different security descriptor than the calling code intended.

CVE-2020-0668, the Service Tracing elevation-of-privilege bug Clément Labro disclosed in February 2020, is the textbook symbolic-link case [50, 51]. The Service Tracing infrastructure under HKLM\SOFTWARE\Microsoft\Tracing is user-writable, and several SYSTEM services -- IKEEXT, RasMan, the Update Session Orchestrator service -- consult those keys to find a tracing log path. When the log file exceeds the configured MaxFileSize, the service renames it from MODULE.LOG to MODULE.OLD, deleting any existing MODULE.OLD first.

itm4n's exploit is exactly the one his blog post names: "All you need to do is set the target directory as a mountpoint to the \RPC Control object directory and then create two symbolic links: A symbolic link from MODULE.LOG to a file you own; A symbolic link from MODULE.OLD to any file on the file system" [51]. The mountpoint reroutes the kernel'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's googleprojectzero/symboliclink-testing-tools repository [52] provides the primitive library the exploit consumes -- CreateSymlink, CreateMountPoint, BaitAndSwitch -- and the repository is, in effect, the institutional library every Object Manager lookup-phase attack of the past decade has linked against.

The worked example Forshaw walks is CVE-2015-4481, a Mozilla Maintenance Service hard-link primitive: any user can write a status log to C:\ProgramData\Mozilla\logs\maintenanceservice.log, and during the service's pre-write BackupOldLogs 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'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 SeAccessCheck saw only the destination DACL, not the planted-link DACL [54, 53]. Microsoft's MS15-115 mitigation tightened the kernel's hard-link semantics for sandboxed callers (the kernel's NtSetInformationFile now requires FILE_WRITE_ATTRIBUTES on the target handle when the caller's token has the sandboxed-token flag, matching what the user-mode CreateHardLink 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.

The two examples generalise the rule. The kernel resolves names before checking the requesting user's authority over the destination. The DACL at target.path and the DACL at attacker.planted.path after a junction or hard-link redirect can be different; SeAccessCheck 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 [55]. Windows checks the descriptor on every OpenObject 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's namespace is not documented as policy in the same way SeAccessCheck's algorithm is. The de facto modern documentation is empirical: practitioners enumerate the namespace with tools that read kernel structures directly. James Forshaw's NtObjectManager PowerShell module, part of the Project Zero sandbox-attacksurface-analysis-tools repository, is the dominant such tool [56]. The repository's banner is exact: "NtObjectManager: A powershell module which uses NtApiDotNet to expose the NT object manager."

The James Forshaw / Project Zero corpus is the systematic reference for the Object Manager attack surface. Forshaw's "Sharing a Logon Session a Little Too Much" (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's verbatim explanation: "when LSASS creates a Token for a new Logon session it stores that Token for later retrieval. For the most part this isn't that useful, however there is one case where the session Token is repurposed, network authentication" [25]. The cached token plus a named-pipe path-validation bug becomes a non-DCOM SYSTEM-token primitive that no DACL touches.

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.

10. The Potato Lineage: Eight Tools, One Bug (2016-2021)

January 16, 2016. Stephen Breen of Foxglove Security publishes "Hot Potato." The disclosure post opens with a sentence the article will earn the right to repeat: "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" [57]. Six years and seven tools later, that admission still describes the situation.

The single underlying primitive. A low-privileged service account holding SeImpersonatePrivilege (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's endpoint accepts the authentication, ends up with an impersonation handle to a SYSTEM token, and calls ImpersonateLoggedOnUser followed by CreateProcessAsUser to run arbitrary code as SYSTEM. Same primitive, eight tools, six years.

Ctrl + scroll to zoom
The canonical Potato attack flow: a service account with SeImpersonate coerces SYSTEM to authenticate to an attacker-controlled endpoint, captures the impersonation token, and runs a child process under SYSTEM.

Walk the lineage one paragraph at a time.

Hot Potato (Stephen Breen / Foxglove, January 2016) [57, 58]. 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's automatic proxy lookup, and Windows Update's HTTP-to-SMB NTLM relay. The exploit poisons NBT-NS so that the SYSTEM-running Windows Update service resolves WPAD to the attacker's local listener; the attacker's listener serves a proxy configuration that proxies SMB through localhost; the Windows Update service authenticates to the attacker's localhost SMB endpoint as SYSTEM. Foxglove's verbatim summary: "Hot Potato (aka: Potato) takes advantage of known issues in Windows to gain local privilege escalation in default configurations, namely NTLM relay (specifically HTTP->SMB relay) and NBNS spoofing" [57].

Rotten Potato (Stephen Breen and Chris Mallz / Foxglove, September 2016) [59, 60]. 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 CoGetInstanceFromIStorage with the BITS CLSID {4991d34b-80a1-4291-83b6-3328366b9097} and a custom marshalled IStorage pointer; the COM activation runs on the local DCOM server (which runs as SYSTEM) and authenticates back to a TCP listener the attacker controls.

The Foxglove blog states the three steps verbatim: "1. Trick the 'NT AUTHORITY\SYSTEM' 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 'NT AUTHORITY\SYSTEM' account. 3. Impersonate the token... This can only be done if the attackers current account has the privilege to impersonate security tokens" [59]. The same post credits the Project Zero work directly: "this work is derived directly from James Forshaw's BlackHat talk and Google Project Zero research" [59].

Juicy Potato (decoder_it and ohpe, 2018) [4]. A weaponised, configurable Rotten Potato. The repository README is exact about lineage and entry conditions: "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" [4].

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's own framing of when it works is the article's PullQuote-worthy line: "If the user has SeImpersonate or SeAssignPrimaryToken privileges then you are SYSTEM" [4]. 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.

Rogue Potato (Antonio Cocomazzi and Andrea Pierini, May 2020) [61, 62]. The bypass for the loopback-OXID mitigation. Decoder's blog states the engineering problem and the fix in two sentences: "Starting from Windows 10 1809 & Windows Server 2019, its no more possible to query the OXID resolver on a port different than 135" [62]. Rogue Potato works around the constraint by routing the OXID resolution through an attacker-controlled remote OXID resolver, typically reached via socat tcp-listen:135,fork TCP:attacker:9999. The remote resolver returns a string binding pointing back at the attacker's local listener; the constraint is satisfied (the OXID resolver is on port 135) but the listener it ultimately reaches is the attacker's. The lineage extends.

PrintSpoofer (Clément Labro / itm4n, May 2020) [63, 30]. 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 RpcRemoteFindFirstPrinterChangeNotificationEx 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.

itm4n's repository description summarises the entry condition: "From LOCAL/NETWORK SERVICE to SYSTEM by abusing SeImpersonatePrivilege on Windows 10 and Server 2016/2019" [63]. The blog post opens with credit and the canonical decoder_it quote: "I want to start things off with this quote from @decoder_it: 'if you have SeAssignPrimaryToken or SeImpersonate privilege, you are SYSTEM'" [30].

RemotePotato0 (Cocomazzi and Pierini, 2021) [64]. The first Potato to escape the local machine. A cross-session DCOM activation lets the attacker reach a token from a different logged-on user; a cross-protocol RPC-to-LDAP relay then turns that user's authentication into a domain-administrator action against Active Directory.

The README is candid about the shape of the response: "UPDATE 21-10-2022: The main exploit scenario RPC->LDAP of RemotePotato0 has been fixed... Just another 'Won't Fix' 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" [64]. Just another "Won't Fix" Windows Privilege Escalation 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.

PetitPotam (Lionel Gilles / topotam77, July 2021) [65, 66]. 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's EfsRpcOpenFileRaw (and several other functions) to coerce a Windows host to authenticate to an attacker-controlled endpoint.

The README is exact about the interface choices: "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's more prevalent. But it's possible to trigger with the EFSRPC named pipe and interface df1941c5-fe89-4e79-bf10-463657acf44d" [65]. PetitPotam's most-cited use case is cross-machine relay against Active Directory Certificate Services, but the EFSRPC coercion is also locally consumable.

SharpEfsPotato (bugch3ck, 2021) [67]. The local-machine variant of the PetitPotam coercion. The README is precise about lineage: "Local privilege escalation from SeImpersonatePrivilege using EfsRpc. Built from SweetPotato by @_EthicalChaos_ and SharpSystemTriggers/SharpEfsTrigger by @cube0x0" [67]. SharpEfsPotato uses EfsRpcOpenFileRaw against the local LSARPC pipe to coerce SYSTEM to authenticate to a local endpoint, then performs the by-now-familiar token capture and CreateProcessAsUser.

This article's stage-4 source verification corrected an attribution that had been carried forward from the original scope file: SharpEfsPotato's canonical repository is bugch3ck/SharpEfsPotato, not the often-cited ly4k/SharpEfsPotato. The latter URL returns HTTP 404. Cross-references that point at the ly4k URL should be updated to point at bugch3ck [67].

The pattern across the lineage is that the mitigation that did break a tool was always specific (the loopback-OXID restriction, the cross-session DCOM partial fix, the EFSRPC coercion partial mitigation in KB5005413), and the mitigation that would break the family is structural (remove SeImpersonatePrivilege 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.

ToolYearAuthor(s)Coercion vectorMitigation that broke itMitigation that did not
Hot Potato2016BreenNBT-NS + WPAD + HTTP-to-SMB relayDisable WPAD; KB3146965SeImpersonate on services
Rotten Potato2016Breen, MallzDCOM CoGetInstanceFromIStorage (BITS)(none specific until Juicy fix)SeImpersonate on services
Juicy Potato2018decoder_it, ohpeDCOM CLSID brute-list, configurable portLoopback-OXID restriction (1809 / 2019)SeImpersonate on services
Rogue Potato2020Cocomazzi, PieriniRemote OXID resolver via socatCross-session DCOM partial fixSeImpersonate on services
PrintSpoofer2020LabroSpooler RPC + named-pipe path bypassKB5005010 (PrintNightmare-era spooler hardening) [68]SeImpersonate on services
RemotePotato02021Cocomazzi, PieriniCross-session DCOM + RPC-to-LDAP relayRPC-to-LDAP relay fix (October 2022)SeImpersonate on services; remaining relay targets
PetitPotam2021GillesEFSRPC coercion via LSARPCKB5005413 partial; ADCS hardening [66]SeImpersonate on services; other relay targets
SharpEfsPotato2021bugch3ckLocal EFSRPC coercion(none specific to local variant)SeImpersonate on services

Eight tools. One privilege. Every "mitigation that did not" cell points at the same thing: a bearer-token model plus SeImpersonatePrivilege on a service account.

Eight Potatoes prove the bearer-token property is unkillable by point fixes. But what does an attacker who is already admin do? The answer is the most-cited offensive Windows tool of the past fifteen years.

11. Mimikatz, Conditional ACEs, and the Edges of the Model

May 2011. Benjamin Delpy releases the first version of Mimikatz [33]. Wikipedia's biographical summary is precise: "He released the first version of the software in May 2011 as closed source software" [33]. Fifteen years later, every offensive Windows engagement on Earth still reaches for it.

Mimikatz contains many modules. Two of them sit directly on the access-control surface and are worth naming explicitly. The repository's own command surface lists them as privilege::debug and token::elevate [3].

privilege::debug is one line of code: the command enables SeDebugPrivilege on the caller's token. Any local administrator on stock Windows holds the privilege on the token by default; the command flips it from Available to Enabled via AdjustTokenPrivileges. With SeDebugPrivilege enabled, the calling process can OpenProcess against any other process on the machine, including SYSTEM-level services such as lsass.exe. Every Mimikatz session that wants to read process memory begins with privilege::debug.

token::elevate is three lines of code in spirit. The command opens a SYSTEM-owned process (typically lsass.exe), calls OpenProcessToken to retrieve a handle to the SYSTEM token, calls DuplicateTokenEx to duplicate the handle for impersonation, and calls SetThreadToken to attach the duplicated SYSTEM token to the calling thread. The thread is now SYSTEM. The bearer-token property in three lines of code.

This article does not cover sekurlsa::logonpasswords. That command is the most-cited Mimikatz capability in journalism (it reads cached credentials from lsass.exe), but the credential-storage surface and the Credential Guard mitigation belong to the Secure Kernel sibling article in this series [69]. For the purposes of this article, the lesson stops at token::elevate.

The lesson is the structural concession. With administrator rights on the local machine and SeDebugPrivilege enabled, the access-control model has no defence for "I will pretend to be a different process," because admin equals kernel by Microsoft's own boundary definition [8]. 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.

The model has been extended in only one structural direction since 1993, and that direction is the subject of the access matrix. Conditional ACEs and Dynamic Access Control (DAC) shipped together in Windows Server 2012 and Windows 8 [70]. They are the only extension of the access-matrix subject Microsoft has shipped in thirty-three years.

The mechanism is twofold. First, ACEs gain an expression syntax. The SDDL ACE strings page documents XA, XD, XU, and ZA as conditional callback variants of the basic allow / deny / audit / object-allow ACE types [23]. A conditional ACE carries an expression in addition to a SID and an access mask, and the kernel evaluates the expression against the token's claims at access time. The canonical example is (XA;;FA;;;AU;(@User.Department=="Finance")) -- an allow-callback ACE that grants FILE_ALL_ACCESS to Authenticated Users if the token carries a Department claim equal to "Finance".

Second, the token gains claims. A claim is a typed key-value pair attached to the token by Active Directory at logon. Claims can be sourced from the user's AD attributes, the device's AD attributes, or resource properties on the object. Microsoft Learn states the role they play: "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" [70].

A Central Access Policy (CAP) is a set of Central Access Rules (CARs), each of which is a conditional-ACE expression. The CAP is applied to file shares; the file-share metadata says "evaluate this CAP for every access," and the CAP'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 [71].

The reason DAC has not displaced classic DAC outside file-server scenarios is in the same Microsoft Learn page: "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" [70]. 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'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's Package SIDs (Windows 8) and conditional ACEs (Server 2012) shipped the same year. Both extend the subject 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 [70, 29].

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?

12. The 2026 Plane: Ten Primitives, One Decision

Run a single OpenObject call on a Windows 11 machine and walk the kernel's path. Every primitive the article has introduced fires for that one call.

Ctrl + scroll to zoom
The 2026 access-control plane: every primitive in this article composes for a single OpenObject decision.

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:

#PrimitiveSection introducedYear shippedCanonical primary citationCanonical attack
1Security Reference Monitor31993[1](Underlying surface; not directly attacked)
2Security Identifier (SID)41993[26]Misused well-known SIDs ("Everyone is just a SID")
3Access Token51993[19]The Potato lineage; Mimikatz token::elevate
4Security Descriptor31993[6]HiveNightmare (CVE-2021-36934)
5DACL + ACE41993[6, 24]NULL DACL misconfigurations; out-of-order ACEs
6SACL + audit31993[1]Tools that copy DACL but not SACL silently drop integrity labels
7Privilege61993[32]Mimikatz privilege::debug; SeBackup abuse
8Mandatory Integrity Control72007[22]IE7 Protected Mode broker bypasses
9UAC split-token82007[45]UACMe: 70+ AutoElevate-redirect methods [5]
10Conditional ACE / DAC112012[70, 23]Falls back to classic DAC in heterogeneous environments

The plane is whole. It is also full of structural holes its own keepers have publicly admitted. What are they?

13. The Five Structural Limits

Microsoft's Security Servicing Criteria for Windows defines a security boundary as "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" [8]. 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's structural arc becomes legible. Five derived limits flow from the concession.

Limit 1: Admin equals kernel. Any compromise with administrator rights can rewrite the model's own enforcement code. Consequence: Mimikatz, every kernel-driver-loading attack, every signed-driver bring-your-own-vulnerable-driver path. Successor: VBS Trustlets, which host secrets and policy enforcement in the Virtual Trust Level 1 user-mode environment that the VTL0 NT kernel cannot read or modify. Detailed coverage belongs to the Secure Kernel sibling article in this series [69].

Limit 2: Tokens are bearer credentials. Whichever process holds the handle gets the rights. The kernel does not ask how the handle was obtained. Consequence: the entire Potato lineage (eight tools, six years), Mimikatz token::elevate, every cross-session token-theft attack. Successor: 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 [72, 73]. The forthcoming Adminless article in this series will cover the architecture in detail.

Limit 3: SeImpersonatePrivilege on every service account. IIS, SQL Server, the Print Spooler, scheduled tasks, Docker, Citrix, almost every managed-service account by default. Consequence: every Potato, by construction. Partial successor today: per-service SIDs and Group Managed Service Accounts let administrators constrain the blast radius of a compromised service. Structural successor: Adminless, which removes the privilege from the daily authentication path and demands a fresh elevation per privileged action [72, 73].

Limit 4: NTLM relay surface. 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. Consequence: PetitPotam, RemotePotato0, every cross-protocol relay. Successor: NTLMless, which formally retires NTLM as a default Windows authentication protocol [74]. 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 Microsoft\Windows\NTLM\Operational and gives administrators a per-workload deprecation telemetry [75]. The forthcoming NTLMless article in this series will cover the architecture in detail.

Limit 5: The DACL is local. 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. Consequence: the access-matrix subject is, in practice, still "user and group" for most non-file-server workloads. The 2012 extension to claims and code identity is real but operationally bounded.

The deepest of the five limits is the one Norm Hardy named in 1988. Hardy's framing returned in Section 2 [14] holds: capability systems close the gap structurally; ACL engineering does not.

seL4 closes it with machine-checked correctness proofs and a capability-based design that makes ambient authority a category error [55]. Windows closes it, when it closes it at all, with VBS Trustlets that move the right to perform the operation into a separate execution domain. The Potato lineage is the textbook confused-deputy instance: a service running with SeImpersonatePrivilege is the privileged compiler; the attacker is the user holding a billing-records-shaped pointer; the service uses its own authority on every authentication it accepts.

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. Microsoft'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.

The five limits are named. The successors are shipping. What replaces what?

14. The Successors: Adminless, NTLMless, VBS Trustlets, Credential Guard

One paragraph each. This section is a forward-reference index, not a detailed walk-through.

Adminless. 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 Windows Hello 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 [72].

The Microsoft Tech Community announcement (modified November 19, 2024) summarises the security argument: "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" [73]. Closes: limits #2 and #3 -- there is no long-lived bearer credential to steal, and SeImpersonatePrivilege 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.

NTLMless. Formally retires NTLM as a default Windows authentication protocol [74]. The Tech Community announcement is unambiguous about direction: "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" [74].

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 Microsoft\Windows\NTLM\Operational and gives administrators per-service telemetry on which workloads still require the protocol [75]. 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. Closes: limit #4. Forthcoming article in this series.

VBS Trustlets and Isolated User Mode. Shipped in Windows 10 1507 in 2015. Virtualization-Based Security (VBS) uses the Hyper-V hypervisor to host a second user-mode environment, Virtual Trust Level 1 (VTL1), whose memory the NT kernel running in VTL0 cannot read or write. A Trustlet is a process that runs in VTL1. Closes: 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 [69].

Credential Guard. The canonical first Trustlet. lsass.exe continues to run in VTL0 and answer authentication requests; the credential blobs lsass.exe historically held are moved to a Trustlet called LsaIso in VTL1. The VTL0 lsass.exe retains handles to the blobs but cannot read their contents; authentication happens by calling into the Trustlet. Mimikatz sekurlsa::logonpasswords 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 [69].

Pluton-rooted attestation and the hardware foundation. The successor architectures rest on a hardware identity chain that begins below the firmware, in Microsoft'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 Pluton article in this series covers the architecture and the Caliptra root-of-trust direction it foreshadows [76]. The TPM 2.0 architecture that the same chain extends and the Secure Boot chain that runs before the access-control model boots are covered in their own sibling articles in this series [77, 78].

With the gaps named and the successors mapped, what does an administrator actually do today?

15. Practical Guide

Six concrete recommendations for 2026, each tied to a primary Microsoft Learn or named-expert source.

A simulator for the full access-control plane
JavaScript A boot-time decision-tree simulator for SeAccessCheck plus MIC plus AppContainer plus conditional-ACE evaluation
// Inputs:
//   token       -- {sids:[...], integrity:'Low'|'Medium'|'High'|'System',
//                   appContainer:bool, capabilities:[...], claims:{...},
//                   privileges:{enabled:[...]}}
//   descriptor  -- {dacl:[...], integrityLabel:'Low'|...|'System',
//                   policyNoWriteUp:bool}
//   desired     -- access mask (number)
// Output: {granted, status, fired:[...]}

function fullAccessCheck(token, descriptor, desired) {
const fired = [];
const ilOrder = {Low:1, Medium:2, High:3, System:4};

// 1. MIC check fires before DACL walk.
if (descriptor.policyNoWriteUp) {
  const writeBits = 0x00040000 | 0x00000002 | 0x00000004; // WRITE_DAC|WRITE|APPEND
  if ((desired & writeBits) && ilOrder[token.integrity] < ilOrder[descriptor.integrityLabel]) {
    fired.push('MIC no-write-up: token IL ' + token.integrity + ' < object IL ' + descriptor.integrityLabel);
    return {granted:0, status:'DENIED at integrity check', fired};
  }
}

// 2. Privilege bypass short-circuit.
if (token.privileges.enabled.includes('SeBackupPrivilege') && (desired & 0x80000000)) {
  fired.push('SeBackupPrivilege bypass: GENERIC_READ granted');
  return {granted: desired, status:'GRANTED via SeBackupPrivilege', fired};
}

// 3. AppContainer capability check (simplified).
if (token.appContainer && descriptor.requiresCapability) {
  if (!token.capabilities.includes(descriptor.requiresCapability)) {
    fired.push('AppContainer capability check: missing ' + descriptor.requiresCapability);
    return {granted:0, status:'DENIED at AppContainer check', fired};
  }
}

// 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 && !evalCondition(ace.condition, token.claims)) continue;
  if (ace.type === 'DENY' && (ace.mask & remaining) !== 0) {
    fired.push('Conditional/plain DENY: ' + ace.sid);
    return {granted:0, status:'DENIED at DACL', fired};
  }
  if (ace.type === 'ALLOW') {
    const newBits = ace.mask & remaining;
    granted |= newBits;
    remaining &= ~newBits;
    fired.push('ALLOW ' + ace.sid + ': granted 0x' + newBits.toString(16));
  }
  if (remaining === 0) {
    return {granted, status:'GRANTED', fired};
  }
}
return {granted, status: remaining === 0 ? 'GRANTED' : 'DENIED end of DACL', fired};
}

function evalCondition(expr, claims) {
// Toy evaluator for "(@User.Department == \"Finance\")"-style expressions.
const m = expr.match(/@User\.(\w+)\s*==\s*"([^"]+)"/);
if (!m) return true;
return claims[m[1]] === m[2];
}

// Demo: a Medium-IL user trying to write to a System-IL object via an allow ACE.
console.log(fullAccessCheck(
{sids:['S-1-5-21-X-Y-Z-1001'], integrity:'Medium', appContainer:false, capabilities:[], claims:{Department:'Finance'},
 privileges:{enabled:[]}},
{dacl:[{type:'ALLOW', sid:'S-1-5-21-X-Y-Z-1001', mask:0xFFFFFFFF}],
 integrityLabel:'System', policyNoWriteUp:true},
0x00040000));  // WRITE_DAC

Press Run to execute.

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 fired 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.

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.

The Sysinternals accesschk and psgetsid 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 Get-Acl and icacls print, in the form most useful to an administrator working at scale.

16. Frequently Asked Questions

Frequently asked questions

Is UAC a security boundary?

No. By Microsoft's own Security Servicing Criteria for Windows, UAC and admin-to-kernel are not on the enumerated security-boundary list [8]; 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 [5].

Doesn't 'Everyone' grant full access?

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: "S-1-1-0 is NOT A SECURITY BOUNDARY" [25].

If a file's DACL is empty, can anyone open it?

No. Empty DACL denies all access. No DACL (a NULL DACL) grants full access. Newly-written code that "creates a file with no protection" almost always gets this distinction wrong [6]. 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.

Discretionary access control means admin can do anything, right?

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 [22]. 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 [69].

whoami /priv shows twenty privileges. Do I have them all?

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 [32].

JuicyPotato was patched. Is the Potato chapter closed?

No. The underlying NTLM-to-self surface is still open. SharpEfsPotato (2021) [67] 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's own Hot Potato post called the underlying issue "hard to fix without breaking backward compatibility" [57]; the structural fix is the Adminless and NTLMless successor articles, not a point patch on any one primitive.

Is AppContainer a sandbox like Chrome's?

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'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 [29].

17. Closing: Return to the Hook

Open a Windows PowerShell window again. Run whoami /priv. Read the column on the right, this time with the article's vocabulary annotated above each line.

SeShutdownPrivilege -- a privilege, in the kernel sense of a pre-checked superpower; bookkeeping rather than power.

SeIncreaseWorkingSetPrivilege -- the same. Most of the twenty rows are housekeeping that the kernel checks at specific call sites to gate non-security-critical operations.

The five rows that matter are easy to spot once you know what to look for. SeDebugPrivilege -- Mimikatz starts here. SeImpersonatePrivilege -- the entire Potato lineage starts here. SeAssignPrimaryTokenPrivilege -- the second half of every token-replay attack. SeBackupPrivilege -- HiveNightmare's privilege class. SeRestorePrivilege -- service-binary replacement. The kernel reads the same column on every securable operation, billions of times a second, and the answer to "can this code do this?" is built out of this list every time.

Now run icacls C:\Windows\System32\drivers\etc\hosts again. BUILTIN\Administrators:(F) is an allow ACE granting full control to the well-known SID S-1-5-32-544. NT AUTHORITY\SYSTEM:(F) is an allow ACE granting full control to S-1-5-18. BUILTIN\Users:(R) is an allow ACE granting FILE_GENERIC_READ to S-1-5-32-545. The DACL is in canonical order: explicit entries before inherited entries, deny entries (none here) before allow entries within each group. SeAccessCheck will walk this DACL on every read of hosts 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.

The article'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's address space and tells the Credential Guard story in detail [69]. The Pluton article roots the hardware identity chain that the successor architectures all eventually verify against [76]. 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 [77, 78]. Each successor was scoped to close a specific gap the access-control model could not close from inside.

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 "frozen" was always going to mean "attackable from below." 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.

SeAccessCheck decides every time. The next decade decides what it decides about.

Study guide

Key terms

SeAccessCheck
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.
Security Reference Monitor (SRM)
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.
Access Token
A kernel object that names the security identity of a thread or process. Carries the user'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.
Discretionary Access Control List (DACL)
The ordered list of allow / deny ACEs attached to a securable object. The object's owner controls the contents, in contrast to a mandatory list.
Mandatory Integrity Control (MIC)
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.
User Account Control (UAC)
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's own servicing criteria, an enforced security boundary.
SeImpersonatePrivilege
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.
Confused Deputy
Norm Hardy'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's behalf because the system cannot distinguish authority the service has from authority the service is being asked to use.
VBS Trustlet
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.
---WRITER METRICS---
Word count: 17108
Citations: 150
Mermaid diagrams: 4
Definitions: 11
Sidenotes: 6
FAQ questions: 7
---END WRITER METRICS---

References

  1. Microsoft Access Control. https://learn.microsoft.com/en-us/windows/win32/secauthz/access-control - Index page for the Windows Win32 access-control surface; lists the C2-level, Access Control Model, SDDL, Privileges, Audit Generation, Securable Objects, and Low-level Access Control sub-pages.
  2. Wikipedia contributors Windows NT 3.1. https://en.wikipedia.org/wiki/Windows_NT_3.1 - NT 3.1 RTM date (July 27, 1993); Cutler / NT-team origin.
  3. Benjamin Delpy gentilkiwi/mimikatz. https://github.com/gentilkiwi/mimikatz - Mimikatz repository; privilege::debug, token::elevate, sekurlsa::logonpasswords, lsadump::sam command surface.
  4. Andrea Pierini & Giuseppe Trotta (2018). ohpe/juicy-potato. https://github.com/ohpe/juicy-potato - Juicy Potato (2018); CLSID enumeration; configurable RPC port.
  5. hFireF0x hfiref0x/UACME. https://github.com/hfiref0x/UACME - Institutional catalogue of UAC AutoElevate-whitelist redirect bypasses; 70+ methods with structured metadata.
  6. Microsoft How DACLs Control Access to an Object. https://learn.microsoft.com/en-us/windows/win32/secauthz/how-dacls-control-access-to-an-object - Canonical SeAccessCheck DACL evaluation algorithm, deny-first sequencing, NULL DACL vs empty DACL distinction.
  7. NIST National Vulnerability Database (2021). CVE-2021-36934 (HiveNightmare / SeriousSAM). https://nvd.nist.gov/vuln/detail/CVE-2021-36934 - Overly permissive ACLs on the SAM database; KB5005357 plus manual shadow-copy deletion.
  8. Microsoft Security Response Center Microsoft Security Servicing Criteria for Windows. https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria - The security-boundary definition; the kernel-mode / user-mode separation as a classic boundary; UAC and admin-to-kernel are not in the enumerated list.
  9. Butler W. Lampson (1974). Protection. ACM SIGOPS Operating Systems Review, 8(1), 18-24. https://doi.org/10.1145/775265.775268 - The original access-matrix paper; cite via DOI form, mirror via Microsoft Research publications page.
  10. Microsoft Research Butler Lampson -- Publications. https://www.microsoft.com/en-us/research/people/blampson/publications/ - Microsoft Research publications listing for Butler Lampson; authorship metadata for the 1971 Princeton paper / 1974 ACM OSR reprint.
  11. NIST Computer Security Resource Center (1985). DoD Rainbow Series. https://csrc.nist.gov/pubs/other/1985/12/26/dod-rainbow-series/final - Listing of DoD 5200.28-STD (Orange Book, December 26, 1985) and Rainbow Series companion volumes.
  12. Norm Hardy (1988). The Confused Deputy (or why capabilities might have been invented). ACM SIGOPS Operating Systems Review, 22(4), 36-38. https://doi.org/10.1145/54289.871709 - Theoretical-limit paper for ambient-authority systems; mirror text via Wayback cap-lore snapshot.
  13. Norm Hardy (1988). The Confused Deputy (Wayback mirror of cap-lore.com). https://web.archive.org/web/2024/https://www.cap-lore.com/CapTheory/ConfusedDeputy.html - Verbatim text of the 1988 paper, mirrored on the Internet Archive Wayback Machine.
  14. Wikipedia contributors Confused Deputy Problem. https://en.wikipedia.org/wiki/Confused_deputy_problem - Hardy 1988 framing; the ACL-vs-capability comparison; Samy worm and CSRF as modern instances.
  15. Wikipedia contributors Dave Cutler. https://en.wikipedia.org/wiki/Dave_Cutler - Cutler left DEC for Microsoft in October 1988; led RSX-11M, VMS, VAXELN, MICA at DEC; Microsoft Senior Technical Fellow.
  16. Wikipedia contributors OpenVMS. https://en.wikipedia.org/wiki/OpenVMS - VAX/VMS announced October 25, 1977 alongside the VAX-11/780; V1.0 released August 1978; specific VAX/VMS versions evaluated at TCSEC Class C2.
  17. G. Pascal Zachary (1994). Showstopper!: The Breakneck Race to Create Windows NT. The Free Press. ISBN 0-02-935671-7. - Cited for cultural / narrative context on Cutler and the NT team.
  18. Wikipedia contributors Windows NT 3.5. https://en.wikipedia.org/wiki/Windows_NT_3.5 - NT 3.5 RTM September 21, 1994; SP3 June 21, 1995; in July 1995 NSA rated NT 3.5 SP3 as complying with TCSEC C2 criteria.
  19. Microsoft Access Tokens. https://learn.microsoft.com/en-us/windows/win32/secauthz/access-tokens - Token contents; primary-vs-impersonation distinction; OpenProcessToken / DuplicateTokenEx / AdjustTokenPrivileges API surface.
  20. Microsoft SeAccessCheck routine (wdm.h). https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-seaccesscheck - Kernel-mode SeAccessCheck reference: SecurityDescriptor, SubjectSecurityContext, SubjectContextLocked, DesiredAccess, PreviouslyGrantedAccess, Privileges (out), GenericMapping, AccessMode, GrantedAccess (out), AccessStatus (out).
  21. Microsoft AccessCheck function (securitybaseapi.h). https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-accesscheck - User-mode AccessCheck mirror: pSecurityDescriptor, ClientToken, DesiredAccess, GenericMapping, PrivilegeSet (optional out), PrivilegeSetLength (in/out), GrantedAccess (out), AccessStatus (out).
  22. Microsoft Mandatory Integrity Control. https://learn.microsoft.com/en-us/windows/win32/secauthz/mandatory-integrity-control - MIC four-level lattice, SYSTEM_MANDATORY_LABEL_ACE in SACL, AppContainer Low-IL exception.
  23. Microsoft ACE Strings. https://learn.microsoft.com/en-us/windows/win32/secauthz/ace-strings - SDDL ACE type strings (XA, XD, XU, ZA conditional callbacks; ML mandatory label).
  24. Microsoft Order of ACEs in a DACL. https://learn.microsoft.com/en-us/windows/win32/secauthz/order-of-aces-in-a-dacl - The four-step preferred ACE order; caller responsibility note.
  25. James Forshaw (2020). Sharing a Logon Session a Little Too Much. https://www.tiraniddo.dev/2020/04/sharing-logon-session-little-too-much.html - LSASS token-cache primitive; "S-1-1-0 is NOT A SECURITY BOUNDARY" framing.
  26. Microsoft Security Identifiers. https://learn.microsoft.com/en-us/windows/win32/secauthz/security-identifiers - SID structure and uniqueness; SID API surface; well-known-SIDs cross-link.
  27. Wikipedia contributors Mandatory Integrity Control. https://en.wikipedia.org/wiki/Mandatory_Integrity_Control - Verbatim integrity-level SID values (Low S-1-16-4096, Medium S-1-16-8192, High S-1-16-12288, System S-1-16-16384); UIPI cross-link.
  28. James Forshaw (2017). The Art of Becoming TrustedInstaller. https://www.tiraniddo.dev/2017/08/the-art-of-becoming-trustedinstaller.html - Service SIDs are the SHA-1 of the uppercased service name; RtlCreateServiceSid.
  29. (2026). "Who Is This Code?" -- The Quiet 33-Year Reinvention of App Identity in Windows. https://paragmali.com/blog/who-is-this-code----the-quiet-33-year-reinvention-of-app-ide/ - Sibling article on Authenticode, AppContainer, Package SID derivation, and the layered code-identity stack.
  30. Clément Labro (itm4n) (2020). PrintSpoofer -- Abusing Impersonation Privileges on Windows 10 and Server 2019. https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/ - PrintSpoofer technique blog; named-pipe path-validation bypass; the canonical decoder_it quote.
  31. Microsoft Restricted Tokens. https://learn.microsoft.com/en-us/windows/win32/secauthz/restricted-tokens - Two-pass access check; deny-only / restricting SIDs / privilege removal; desktop-isolation requirement.
  32. Microsoft Privileges. https://learn.microsoft.com/en-us/windows/win32/secauthz/privileges - Privilege-vs-right distinction; default-disabled rule; AdjustTokenPrivileges to enable.
  33. Wikipedia contributors Mimikatz. https://en.wikipedia.org/wiki/Mimikatz - Mimikatz first-release date (May 2011); Benjamin Delpy attribution.
  34. NIST National Vulnerability Database (2019). CVE-2019-16098 (RTCore64.sys). https://nvd.nist.gov/vuln/detail/CVE-2019-16098 - Micro-Star MSI Afterburner driver allows arbitrary memory read/write; signed driver used by BlackByte ransomware to disable EDR.
  35. Sophos News (2022). BlackByte ransomware returns. https://news.sophos.com/en-us/2022/10/04/blackbyte-ransomware-returns/ - October 2022 BlackByte BYOVD via RTCore64.sys (CVE-2019-16098); disables ~1000 security drivers; references mhyprot2.sys / aswArPot.sys precedents.
  36. NIST National Vulnerability Database (2018). CVE-2018-19320 (GIGABYTE gdrv.sys). https://nvd.nist.gov/vuln/detail/CVE-2018-19320 - gdrv.sys exposes ring0 memcpy-like functionality; CISA Known Exploited Vulnerabilities Catalog listing 2022-10-24, due date 2022-11-14.
  37. Microsoft Microsoft Recommended Driver Block Rules. https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/microsoft-recommended-driver-block-rules - Vulnerable-driver blocklist enabled by default since the Windows 11 2022 update; covers BYOVD attack class.
  38. SentinelLabs (2022). Vulnerabilities in Avast and AVG Put Millions at Risk. https://www.sentinelone.com/labs/vulnerabilities-in-avast-and-avg-put-millions-at-risk/ - CVE-2022-26522 + CVE-2022-26523 in aswArPot.sys; reported December 2021; security-product driver as BYOVD precedent.
  39. Wikipedia contributors Windows Vista. https://en.wikipedia.org/wiki/Windows_Vista - Vista released to manufacturing November 8, 2006; general availability January 30, 2007; first release with UAC, MIC, and IE Protected Mode.
  40. Microsoft SYSTEM_MANDATORY_LABEL_ACE structure. https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_mandatory_label_ace - Mandatory-label ACE in the SACL; mask values SYSTEM_MANDATORY_LABEL_NO_WRITE_UP (0x1), NO_READ_UP (0x2), NO_EXECUTE_UP (0x4).
  41. Microsoft ACCESS_MASK. https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask - 32-bit ACCESS_MASK layout: specific bits 0-15, standard bits 16-23 (WRITE_DAC bit 18, WRITE_OWNER bit 19), ACCESS_SYSTEM_SECURITY bit 24, generic bits 28-31.
  42. Microsoft File Access Rights Constants. https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants - FILE_READ_DATA = 1, FILE_WRITE_DATA = 2, FILE_APPEND_DATA = 4 and the rest of the file-specific access mask constants.
  43. Skywing (Ken Johnson) (2007). Getting Out of Jail: Escaping Internet Explorer Protected Mode. https://web.archive.org/web/20080926082628/http://uninformed.org/index.cgi?v=8&a=6 - Uninformed Volume 8 Article 6; first public reverse-engineering of MIC and the IE Protected Mode broker pattern.
  44. Wikipedia contributors Internet Explorer 7. https://en.wikipedia.org/wiki/Internet_Explorer_7 - IE7 released October 18, 2006; standalone for Windows XP SP2 / Server 2003; bundled with Windows Vista where it gained Protected Mode.
  45. Microsoft How User Account Control Works. https://learn.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works - UAC split-token model; filtered Medium-IL token vs full High-IL token; explorer.exe as parent of all user processes.
  46. Microsoft The COM Elevation Moniker. https://learn.microsoft.com/en-us/windows/win32/com/the-com-elevation-moniker - COM elevation moniker syntax (Elevation:Administrator!new:{guid}, Elevation:Highest!new:{guid}); supported run levels Administrator and Highest; produces a High-IL admin caller, not SYSTEM.
  47. Leo Davidson (2009). Windows 7 UAC Whitelist: Code-Injection Issue, Anti-Competitive API, Security Theatre. https://www.pretentiousname.com/misc/win7_uac_whitelist2.html - The original 2009 sysprep / IFileOperation / cryptbase.dll UAC bypass writeup.
  48. Matt Nelson (enigma0x3) (2016). Fileless UAC Bypass Using eventvwr.exe and Registry Hijacking. https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/ - August 15, 2016; the canonical fileless-UAC-bypass template; mscfile HKCU registry redirect.
  49. Mark Russinovich (2007). Inside Windows Vista User Account Control. https://web.archive.org/web/20070715040322/http://technet.microsoft.com/en-us/magazine/cc138019.aspx - Mark Russinovich TechNet Magazine cover story (June 2007); canonical practitioner walkthrough of the split-token model.
  50. NIST National Vulnerability Database (2020). CVE-2020-0668 (Service Tracing Elevation of Privilege). https://nvd.nist.gov/vuln/detail/CVE-2020-0668 - Service Tracing arbitrary file move EoP via symbolic-link manipulation; references ZDI-20-257; itm4n disclosure.
  51. itm4n (Clément Labro) (2020). CVE-2020-0668 -- A Trivial Privilege Escalation Bug in Windows Service Tracing. https://itm4n.github.io/cve-2020-0668-windows-service-tracing-eop/ - Original disclosure of CVE-2020-0668; exploitation via mountpoint to \RPC Control plus two object-manager symbolic links.
  52. NIST National Vulnerability Database (2015). CVE-2015-4481 (Mozilla Maintenance Service hard-link race). https://nvd.nist.gov/vuln/detail/CVE-2015-4481 - Race condition in the Mozilla Maintenance Service in Mozilla Firefox before 40.0 and Firefox ESR 38.x before 38.2 on Windows allows local users to write to arbitrary files via vectors involving a hard link to a log file during an update; CWE-362.
  53. Wikipedia contributors Capability-Based Security. https://en.wikipedia.org/wiki/Capability-based_security - Capability theory background; KeyKOS / EROS / seL4 lineage; ambient-authority distinction.
  54. James Forshaw & Google Project Zero googleprojectzero/sandbox-attacksurface-analysis-tools. https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools - NtObjectManager PowerShell module; the modern empirical-enumeration platform for the Windows access-control surface.
  55. Stephen Breen (2016). Hot Potato. https://foxglovesecurity.com/2016/01/16/hot-potato/ - Hot Potato disclosure (January 16, 2016); references Project Zero issue 222 as prior art seed.
  56. Stephen Breen (2016). foxglovesec/Potato (Hot Potato source). https://github.com/foxglovesec/Potato - Original Hot Potato (2016); NTLM HTTP-to-SMB relay plus NBNS spoofing on localhost.
  57. Stephen Breen & Chris Mallz (2016). Rotten Potato -- Privilege Escalation from Service Accounts to SYSTEM. https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/ - Rotten Potato three-step attack flow; explicit attribution to James Forshaw Project Zero issue 325 and BlackHat talk.
  58. Stephen Breen & Chris Mallz (2016). foxglovesec/RottenPotato. https://github.com/foxglovesec/RottenPotato - Rotten Potato source repository; DerbyCon 2016.
  59. Antonio Cocomazzi & Andrea Pierini (2020). antonioCoco/RoguePotato. https://github.com/antonioCoco/RoguePotato - Rogue Potato (2020); remote OXID resolver; bypass for the loopback-OXID mitigation.
  60. Andrea Pierini (decoder_it) (2020). No more JuicyPotato? Old story, welcome RoguePotato. https://decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/ - Rogue Potato disclosure narrative; identification of the Windows 10 1809 / Server 2019 loopback-OXID mitigation.
  61. Clément Labro (itm4n) (2020). itm4n/PrintSpoofer. https://github.com/itm4n/PrintSpoofer - PrintSpoofer source; LOCAL/NETWORK SERVICE to SYSTEM via SeImpersonatePrivilege.
  62. Antonio Cocomazzi & Andrea Pierini (2021). antonioCoco/RemotePotato0. https://github.com/antonioCoco/RemotePotato0 - RemotePotato0 (2021); cross-session DCOM activation; cross-protocol RPC-to-LDAP relay; partial fix October 2022.
  63. Lionel Gilles (topotam77) (2021). topotam/PetitPotam. https://github.com/topotam/PetitPotam - PetitPotam coercion via MS-EFSRPC EfsRpcOpenFileRaw and other LSARPC-bound functions.
  64. NIST National Vulnerability Database (2021). CVE-2021-36942 (PetitPotam). https://nvd.nist.gov/vuln/detail/CVE-2021-36942 - PetitPotam advisory; KB5005413; CISA KEV.
  65. bugch3ck (2021). bugch3ck/SharpEfsPotato. https://github.com/bugch3ck/SharpEfsPotato - SharpEfsPotato; built atop SweetPotato (EthicalChaos) and SharpEfsTrigger (cube0x0). Replaces the original scope file pointer to ly4k/SharpEfsPotato (HTTP 404).
  66. NIST National Vulnerability Database (2021). CVE-2021-34527 (PrintNightmare). https://nvd.nist.gov/vuln/detail/CVE-2021-34527 - Print Spooler RCE adjacent to PrintSpoofer; CISA KEV; KB5005010.
  67. (2026). When SYSTEM Isn't Enough: The Windows Secure Kernel and the End of Total Kernel Trust. https://paragmali.com/blog/when-system-isnt-enough-the-windows-secure-kernel-and-the-en/ - Sibling article on VBS / IUM / VTL0-VTL1, Trustlet API, Credential Guard, HVCI.
  68. Microsoft Dynamic Access Control: Scenario Overview. https://learn.microsoft.com/en-us/windows-server/identity/solution-guides/dynamic-access-control-overview - DAC architecture; user / device / resource claims; Central Access Rules and Policies; AD/Kerberos compound-ID dependency.
  69. Microsoft Dynamic Access Control: Scenario Overview. https://learn.microsoft.com/en-us/windows-server/identity/solution-guides/dynamic-access-control--scenario-overview - DAC scenarios: classification, central access policies, audit policies; Kerberos compound-ID claims dependency.
  70. Microsoft Administrator Protection (Adminless). https://learn.microsoft.com/en-us/windows/security/application-security/application-control/administrator-protection/ - Adminless / Administrator Protection on Windows 11; just-in-time elevation via Windows Hello plus a hidden, profile-separated user account that issues an isolated admin token.
  71. Microsoft Tech Community (2024). Administrator Protection on Windows 11. https://techcommunity.microsoft.com/blog/windows-itpro-blog/administrator-protection-on-windows-11/4303482 - Modified November 19, 2024; the Administrator Protection feature announcement; just-in-time admin privileges via Windows Hello.
  72. Microsoft Tech Community (2023). The Evolution of Windows Authentication. https://techcommunity.microsoft.com/blog/windows-itpro-blog/the-evolution-of-windows-authentication/3926848 - NTLM future-disablement announcement; local KDC / IAKerb for both local and domain accounts; future disablement of NTLM in Windows 11.
  73. Microsoft Support (2025). Overview of NTLM auditing enhancements in Windows 11 24H2 and Windows Server 2025. https://support.microsoft.com/en-us/topic/overview-of-ntlm-auditing-enhancements-in-windows-11-version-24h2-and-windows-server-2025-b7ead732-6fc5-46a3-a943-27a4571d9e7b - Original publish date July 11, 2025; KB5064479; new audit logs in Microsoft\Windows\NTLM\Operational; client / server / domain-controller NTLMv1 audit channels.
  74. (2026). Pluton: A TPM On Silicon Microsoft Can Patch. https://paragmali.com/blog/pluton-a-tpm-on-silicon-microsoft-can-patch/ - Sibling article on Pluton-rooted attestation; the in-die security processor.
  75. (2026). The TPM in Windows: One Primitive, Twenty-Five Years, and the Chip Microsoft Bet On Twice. https://paragmali.com/blog/the-tpm-in-windows-one-primitive-twenty-five-years-and-the-c/ - Sibling article on TPM 2.0 architecture, measured boot, SRK / EK / AIK.
  76. (2026). Secure Boot in Windows: The Chain From Sector Zero to Userinit, and Every Place It Has Broken. https://paragmali.com/blog/secure-boot-in-windows-the-chain-from-sector-zero-to-userini/ - Sibling article on the static-time verification chain that runs before the access-control model boots.