# "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.

*Published: 2026-05-10*
*Canonical: https://paragmali.com/blog/can-this-code-do-this----twenty-five-years-of-attacks-on-the*
*License: CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/*

---
<TLDR>
Windows answers the question *can this code do this?* with one kernel function, `SeAccessCheck`, evaluated against five inputs: a security descriptor, an access token, a desired-access mask, a generic-mapping table, and any previously-granted access. The function and its inputs have not structurally changed since July 27, 1993. Every famous Windows local-privilege-escalation tool of the last twenty-five years -- Mimikatz, JuicyPotato and seven other Potatoes, the seventy AutoElevate-redirect methods catalogued in UACMe -- attacks one of those inputs. This article tells that story as one system, names the five structural limits Microsoft has publicly conceded, and explains why Adminless, NTLMless, VBS Trustlets, and Credential Guard are the four non-overlapping ways to close them.
</TLDR>

## 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 [@ms-learn-access-control].

This article is about the model behind those two outputs. A model that has not structurally changed since July 27, 1993, when Windows NT 3.1 shipped from Redmond [@en-wiki-windows-nt-3-1]. A model that every famous Windows local-privilege-escalation tool of the last twenty-five years -- Mimikatz, JuicyPotato, fodhelper.exe, the seventy methods in the open-source UACMe catalogue -- exists to attack [@github-gentilkiwi-mimikatz, @github-ohpe-juicy-potato, @github-hfiref0x-uacme].

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 [@ms-learn-how-dacls-control-access].
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 [@nvd-cve-2021-36934]. The vocabulary scales.
3. **The model has five structural limits its keepers have publicly conceded** [@msrc-servicing-criteria], 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 [@acm-doi-lampson, @ms-research-lampson]. 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 [@nist-csrc-rainbow]. 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 [@nist-csrc-rainbow]. 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)" [@acm-doi-hardy, @wayback-cap-lore-hardy]. Hardy described a privileged compiler that wrote billing records to a system file. A user could trick the compiler into writing the user'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" [@en-wiki-confused-deputy]. Hold this paper. It will come back in Section 10.

<Aside label="Cutler, VMS, and the security-by-design culture">
The team that built Windows NT was not assembled in Redmond. David Cutler arrived in October 1988 from Digital Equipment Corporation [@en-wiki-cutler], where he had led VMS and the cancelled Mica successor, and brought with him a fraction of his old DEC team.

The cultural import mattered: VAX/VMS, announced October 25, 1977 alongside the VAX-11/780 (V1.0 shipped August 1978) [@en-wiki-openvms], introduced UIC-based file protection and a kernel-mode security architecture, and by the mid-1980s the VAX/VMS line had been evaluated at TCSEC Class C2 [@en-wiki-openvms], by which time the system had been hardened with per-object ACLs, audit channels, and an explicit reference monitor. That C2-hardened VMS was the cultural reference Cutler brought with him to Microsoft. G. Pascal Zachary's *Showstopper!* tells the story of the four-year build of NT 3.1 from that team [@showstopper-zachary].

The point for this article is narrower. NT 3.1's nine access-control primitives -- Security Reference Monitor, security identifier, access token, security descriptor, DACL, SACL, ACE, privileges, audit channel -- did not arrive piecemeal. They were specified together, before the first line of `SeAccessCheck` was written, against a procurement standard the team intended to clear.
</Aside>

NT 3.1 released to manufacturing on July 27, 1993 [@en-wiki-windows-nt-3-1]. NT 3.5, released to manufacturing on September 21, 1994 [@en-wiki-nt-3-5], was hardened through Service Pack 3 (June 21, 1995) and was rated by the National Security Agency in July 1995 as complying with TCSEC C2 criteria against the standalone single-user configuration [@en-wiki-nt-3-5]; NT 4.0 Service Pack 6a later cleared the C2 network class against the Red Book's Trusted Network Interpretation [@nist-csrc-rainbow]. 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 [@ms-learn-access-control, @ms-learn-access-tokens]. The shape is the same in both directions:

$$
\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 [@ms-learn-seaccesscheck-ddi, @ms-learn-accesscheck-win32]).

<Definition term="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.
</Definition>

<Definition term="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 [@ms-learn-seaccesscheck-ddi, @ms-learn-accesscheck-win32].) It returns the subset of the desired-access mask that the kernel grants and a status code. Every call site that opens or modifies a securable object eventually reaches this function [@ms-learn-how-dacls-control-access].
</Definition>

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 [@ms-learn-mic]. 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) [@ms-learn-access-tokens].

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..." [@ms-learn-access-tokens].

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.

<Mermaid caption="A single OpenObject call traced through the kernel's access-control plane: every primitive in this article fires for one access decision.">
sequenceDiagram
    participant App as User-mode caller
    participant ObjMgr as Object Manager
    participant SRM as Security Reference Monitor
    participant Audit as Audit channel
    App->>ObjMgr: OpenObject(name, DesiredAccess, ImpersonationToken)
    ObjMgr->>ObjMgr: Resolve name (\BaseNamedObjects, \Device, \??)
    ObjMgr->>ObjMgr: Fetch security descriptor from object header
    ObjMgr->>SRM: SeAccessCheck(SD, Token, DesiredAccess)
    SRM->>SRM: Map generic rights via type mapping
    SRM->>SRM: Check mandatory integrity label
    SRM->>SRM: Privilege-bypass short-circuit (SeBackup, SeRestore, SeDebug)
    SRM->>SRM: Walk DACL in canonical order, deny-first
    SRM-->>ObjMgr: GrantedAccess + STATUS code
    ObjMgr->>Audit: Emit SACL audit ACE if matched
    ObjMgr-->>App: HANDLE or STATUS_ACCESS_DENIED
</Mermaid>

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" [@ms-learn-how-dacls-control-access].

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.

<Definition term="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.
</Definition>

<Definition term="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 [@ms-learn-ace-strings].
</Definition>

<RunnableCode lang="js" title="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));
`}</RunnableCode>

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.

> **Note:** Newly-written code that "creates a file with no protection" almost always wants an empty DACL but ends up with a NULL DACL because of how the SECURITY_DESCRIPTOR initialisation defaults work. Verify with `Get-Acl` or `icacls` after creation; a NULL DACL surface in `icacls` looks like `Everyone:(F)` and is almost always a bug, not a feature [@ms-learn-how-dacls-control-access].

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 [@ms-learn-order-of-aces]: "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!" [@tiraniddo-sharing-logon-session]. 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) [@ms-learn-security-identifiers]. 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) [@en-wiki-mic, @ms-learn-mic]. 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.

<Definition term="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 [@ms-learn-security-identifiers].
</Definition>

<Sidenote>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 [@tiraniddo-trustedinstaller-blog].</Sidenote>

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](/blog/who-is-this-code----the-quiet-33-year-reinvention-of-app-ide/) in this series carries the canonical derivation, including the Crockford-Base32 PublisherId derivation that produces a Package SID from an MSIX package signature [@app-identity-sibling]. Section 5 of *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 [@ms-learn-access-tokens]. 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.

<Definition term="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) [@ms-learn-access-tokens]. The kernel consults a token on every access check for the thread that holds it.
</Definition>

<Definition term="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 [@ms-learn-access-tokens].
</Definition>

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 [@itm4n-printspoofer-blog].

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 [@ms-learn-restricted-tokens].

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" [@ms-learn-restricted-tokens]. 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" [@ms-learn-restricted-tokens]. Few applications can spare the desktop overhead.

<Sidenote>`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.</Sidenote>

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.

> **Key idea:** **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" [@ms-learn-privileges]. The same page makes the operational consequence clear: "Most privileges are disabled by default" [@ms-learn-privileges]. 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.

<Definition term="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 [@ms-learn-privileges].
</Definition>

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 [@github-gentilkiwi-mimikatz, @en-wiki-mimikatz]. 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* [@itm4n-printspoofer-blog]. 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.

<PullQuote>
"if you have SeAssignPrimaryToken or SeImpersonate privilege, you are SYSTEM." -- decoder_it, quoted by itm4n in the PrintSpoofer disclosure [@itm4n-printspoofer-blog]
</PullQuote>

`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" [@nvd-cve-2021-36934]. 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" [@nvd-cve-2021-36934]. 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 [@nvd-cve-2019-16098]), the MSI Afterburner driver that "allows any authenticated user to read and write to arbitrary memory, I/O ports, and MSRs" [@nvd-cve-2019-16098]. In October 2022, the threat actors behind the BlackByte ransomware weaponised the primitive at scale [@sophos-blackbyte]: 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" [@sophos-blackbyte].

The kernel-code-execution half of the class is GIGABYTE's `gdrv.sys` (CVE-2018-19320 [@nvd-cve-2018-19320]). 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" [@nvd-cve-2018-19320]. 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 [@nvd-cve-2018-19320]. The U.S. federal-civilian executive branch had two weeks to remediate; the rest of the install base did not.

Microsoft's structural answer is the Microsoft-recommended driver blocklist, enabled by default on every device since the Windows 11 2022 update [@ms-learn-driver-blocklist]. The Learn page is exact about coverage: the blocklist targets drivers with "known security vulnerabilities that an attacker could exploit to elevate privileges in the Windows kernel", and explicitly catches drivers whose behaviours "circumvent the Windows Security Model" [@ms-learn-driver-blocklist].

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 [@sentinelone-avast-avg]) 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 [@sentinelone-avast-avg].

> **Note:** On a Windows machine, holding any of `SeDebugPrivilege`, `SeImpersonatePrivilege`, `SeAssignPrimaryTokenPrivilege`, `SeBackupPrivilege`, or `SeRestorePrivilege` is operationally indistinguishable from being SYSTEM. The other privileges in the standard token (the long tail of `SeShutdown`, `SeIncreaseWorkingSet`, `SeTimeZone`, `SeChangeNotify`, `SeUndock`, `SeIncreaseQuota`...) are housekeeping. Audit the holders of the five accordingly: any non-LocalSystem-equivalent account that holds them is a target.

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" [@ms-learn-mic]. The same page enumerates the levels: "Windows defines four integrity levels: low, medium, high, and system" [@ms-learn-mic]. The levels are encoded as four well-known SIDs: Low (S-1-16-4096), Medium (S-1-16-8192), High (S-1-16-12288), System (S-1-16-16384) [@en-wiki-mic].

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

<Definition term="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) [@en-wiki-mic].
</Definition>

The default policy is *no-write-up*. A process at integrity level $L$ cannot modify an object at integrity level greater than $L$, 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 [@ms-learn-mic].

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

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 [@ms-learn-access-mask]) 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) [@ms-learn-file-access-rights].

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 [@ms-learn-mic]. A medium-IL user running a low-IL executable gets a low-IL process. This is the rule that lets Internet Explorer 7 run at Low even when launched from a Medium user session.

The first MIC consumer was IE7 *Protected Mode*, which shipped with Windows Vista RTM (released to manufacturing November 8, 2006) [@wayback-skywing-uninformed-v8a6, @en-wiki-windows-vista]. (IE7 standalone for Windows XP, released October 18, 2006 [@en-wiki-ie7], runs without Protected Mode -- the feature depends on Vista'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" [@wayback-skywing-uninformed-v8a6]. 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.

<Sidenote>*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 [@en-wiki-mic].</Sidenote>

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" [@ms-learn-mic]. The Package SID and capability SID derivations are the subject of the App Identity sibling article in this series; we will not redefine them here [@app-identity-sibling].

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 [@msrc-servicing-criteria]. 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 [@ms-learn-uac]: "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."

<Definition term="User Account Control (UAC) and Split-Token Elevation">
A Windows mechanism, introduced with Vista (released to manufacturing November 8, 2006 [@en-wiki-windows-vista]), in which an administrative user receives two linked tokens at logon: a *filtered* Medium-IL token without administrative privileges or SIDs, used by `explorer.exe` and every process descended from it, and a *full* High-IL administrative counterpart that the kernel hands out only after the user clicks through a consent prompt or supplies credentials. The two tokens reference each other through the `LinkedToken` field. UAC is a UX-and-default-behaviour mechanism, not an enforced security boundary [@ms-learn-uac, @msrc-servicing-criteria].
</Definition>

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` [@ms-learn-com-elevation-moniker]. 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 [@msrc-servicing-criteria]. The criteria document then enumerates which boundaries Microsoft commits to servicing. UAC and admin-to-kernel are not on the enumerated list.

<PullQuote>
"A security boundary provides a logical separation between the code and data of security domains with different levels of trust... the separation between kernel mode and user mode is a classic [...] security boundary. Microsoft software depends on multiple security boundaries to isolate devices on the network, virtual machines, and applications on a device." -- Microsoft Security Servicing Criteria for Windows [@msrc-servicing-criteria]
</PullQuote>

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.

> **Key idea:** **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 [@pretentiousname-davidson, @github-hfiref0x-uacme]. 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" [@pretentiousname-davidson]. 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)" [@github-hfiref0x-uacme].

**Method 25 -- Matt Nelson (enigma0x3), August 15, 2016.** Seven years after Davidson, Nelson published "Fileless UAC Bypass Using `eventvwr.exe` and Registry Hijacking" [@enigma0x3-eventvwr-blog]. 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" [@enigma0x3-eventvwr-blog]. 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" [@github-hfiref0x-uacme]. *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" [@github-hfiref0x-uacme]. 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" [@github-hfiref0x-uacme]. Most of the methods reduce to the same primitive: an AutoElevated Microsoft-signed binary performs a lookup that the standard user can redirect, the standard user supplies an attacker-controlled answer, and the auto-elevated binary executes attacker-controlled work.

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

<Aside label="Mark Russinovich on UAC, June 2007">
Mark Russinovich's June 2007 *TechNet Magazine* cover story, "Inside Windows Vista User Account Control," is the canonical practitioner walkthrough of the split-token model and is preserved on the Wayback Machine [@wayback-russinovich-uac-technet]. Russinovich opens by naming the misunderstanding: "User Account Control (UAC) is an often misunderstood feature in Windows Vista... In this article I discuss the problems UAC solves and describe the architecture and implementation of its component technologies." The framing throughout the article is that UAC's purpose is to create the *expectation* that consumer software would run as a standard user, and to push the developer community to refactor away from gratuitous administrator requirements. That framing -- UAC as a UX and migration mechanism -- is consistent with the eventual MSRC servicing-criteria position: not a defended boundary, but a behaviour gate.
</Aside>

> **Note:** Microsoft's servicing-criteria position means a UAC bypass that does not also cross a serviced security boundary is not, by policy, eligible for a security update. Mitigations land when the operational footprint of a particular bypass becomes large enough to justify one. Track UAC mitigations by KB number, not by feature description; consult the UACMe README's `Fixed in:` field as the institutional memory [@github-hfiref0x-uacme, @msrc-servicing-criteria].

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 [@nvd-cve-2021-36934].

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 [@nvd-cve-2020-0668, @itm4n-cve-2020-0668-blog]. 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" [@itm4n-cve-2020-0668-blog]. 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 [@github-projectzero-symlink-tools] 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 NTFS hard-link case predates the symbolic-link case by half a decade. James Forshaw's December 2015 Project Zero post, "Between a Rock and a Hard Link," is the canonical primary source [@projectzero-blog-rockandhardlink]. Forshaw observes that hard links have been a feature of NTFS "since it was originally designed", and that their relevance to local privilege escalation is exactly the lookup-vs-access-check sequencing this section describes: "Why are hard links useful for local privilege escalation? One type of vulnerability is exploited by a file planting attack, where a privilege service tries to write to a file in a known location" [@projectzero-blog-rockandhardlink].

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 [@nvd-cve-2015-4481, @projectzero-blog-rockandhardlink]. 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 [@en-wiki-capability-based-security]. 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.

<Sidenote>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 [@github-projectzero-sandbox-attacksurface]. The repository's banner is exact: "NtObjectManager: A powershell module which uses NtApiDotNet to expose the NT object manager."</Sidenote>

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" [@tiraniddo-sharing-logon-session]. The cached token plus a named-pipe path-validation bug becomes a non-DCOM SYSTEM-token primitive that no DACL touches.

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" [@foxglove-hot-potato-blog]. 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.

<Mermaid caption="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.">
sequenceDiagram
    participant Attacker as Attacker (service account, SeImpersonate)
    participant Coercer as Coercion primitive (NBNS / DCOM / EFSRPC / Spooler RPC)
    participant SYSTEM as SYSTEM service
    participant Endpoint as Attacker-controlled local endpoint
    Attacker->>Coercer: Trigger coercion (e.g. EfsRpcOpenFileRaw, BITS CoGetInstanceFromIStorage)
    Coercer->>SYSTEM: Tell SYSTEM to authenticate
    SYSTEM->>Endpoint: NTLM authentication to attacker endpoint
    Endpoint->>Endpoint: Accept NTLM, build impersonation token
    Attacker->>Attacker: ImpersonateLoggedOnUser(SYSTEM token)
    Attacker->>Attacker: CreateProcessAsUser(SYSTEM token, "cmd.exe")
    Note over Attacker: SYSTEM shell
</Mermaid>

Walk the lineage one paragraph at a time.

**Hot Potato (Stephen Breen / Foxglove, January 2016)** [@foxglove-hot-potato-blog, @github-foxglovesec-potato]. 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" [@foxglove-hot-potato-blog].

**Rotten Potato (Stephen Breen and Chris Mallz / Foxglove, September 2016)** [@foxglove-rotten-potato-blog, @github-foxglovesec-rottenpotato]. 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" [@foxglove-rotten-potato-blog]. 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" [@foxglove-rotten-potato-blog].

**Juicy Potato (decoder_it and ohpe, 2018)** [@github-ohpe-juicy-potato]. 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" [@github-ohpe-juicy-potato].

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" [@github-ohpe-juicy-potato]. Juicy Potato was killed by a Windows 10 1809 / Server 2019 mitigation that prevented the OXID resolver from being queried on a port other than 135. The mitigation was the first time Microsoft had touched the underlying primitive.

**Rogue Potato (Antonio Cocomazzi and Andrea Pierini, May 2020)** [@github-antoniococo-roguepotato, @decoder-rogue-potato-blog]. 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" [@decoder-rogue-potato-blog]. 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)** [@github-itm4n-printspoofer, @itm4n-printspoofer-blog]. The same week as Rogue Potato, a different no-DCOM, no-OXID variant lands. PrintSpoofer drops DCOM entirely. It uses the Print Spooler RPC method `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" [@github-itm4n-printspoofer]. 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'" [@itm4n-printspoofer-blog].

**RemotePotato0 (Cocomazzi and Pierini, 2021)** [@github-antoniococo-remotepotato0]. 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" [@github-antoniococo-remotepotato0]. *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)** [@github-topotam-petitpotam, @nvd-cve-2021-36942]. Not strictly a local-LPE Potato, but the source of the EFSRPC coercion primitive that local-LPE Potatoes consume. PetitPotam exploits the Encrypting File System remote-procedure-call protocol'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" [@github-topotam-petitpotam]. 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)** [@github-bugch3ck-sharpefspotato]. 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`" [@github-bugch3ck-sharpefspotato]. 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`.

<Sidenote>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` [@github-bugch3ck-sharpefspotato].</Sidenote>

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.

| Tool | Year | Author(s) | Coercion vector | Mitigation that broke it | Mitigation that did not |
|---|---|---|---|---|---|
| Hot Potato | 2016 | Breen | NBT-NS + WPAD + HTTP-to-SMB relay | Disable WPAD; KB3146965 | `SeImpersonate` on services |
| Rotten Potato | 2016 | Breen, Mallz | DCOM `CoGetInstanceFromIStorage` (BITS) | (none specific until Juicy fix) | `SeImpersonate` on services |
| Juicy Potato | 2018 | decoder_it, ohpe | DCOM CLSID brute-list, configurable port | Loopback-OXID restriction (1809 / 2019) | `SeImpersonate` on services |
| Rogue Potato | 2020 | Cocomazzi, Pierini | Remote OXID resolver via `socat` | Cross-session DCOM partial fix | `SeImpersonate` on services |
| PrintSpoofer | 2020 | Labro | Spooler RPC + named-pipe path bypass | KB5005010 (PrintNightmare-era spooler hardening) [@nvd-cve-2021-34527] | `SeImpersonate` on services |
| RemotePotato0 | 2021 | Cocomazzi, Pierini | Cross-session DCOM + RPC-to-LDAP relay | RPC-to-LDAP relay fix (October 2022) | `SeImpersonate` on services; remaining relay targets |
| PetitPotam | 2021 | Gilles | EFSRPC coercion via LSARPC | KB5005413 partial; ADCS hardening [@nvd-cve-2021-36942] | `SeImpersonate` on services; other relay targets |
| SharpEfsPotato | 2021 | bugch3ck | Local 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.

<Aside label="The Potato lineage as evidence of a structural failure">
Eight tools in six years against the same underlying primitive is not a tooling coincidence. It is the empirical signature of the bearer-credential property and the omnipresent service-account `SeImpersonate` privilege. The Hot Potato post's verbatim "hard to fix without breaking backward compatibility" admission [@foxglove-hot-potato-blog] is the same argument Microsoft eventually formalised in the security-servicing-criteria position: this surface is intentionally retained for compatibility, and structural changes belong in a different architecture. The article earns the bridge to the Adminless and NTLMless successors here, six years before the calendar gets there.
</Aside>

> **Note:** A service account holding `SeImpersonatePrivilege` plus *any* RPC interface that authenticates to attacker-controllable endpoints equals SYSTEM. Eight Potatoes in six years prove this is structural, not a tooling fad. Audit every server: any non-LocalSystem-equivalent process holding `SeImpersonate` or `SeAssignPrimaryToken` should be treated as a Potato target until proven otherwise. Pre-deploy per-service SIDs and Group Managed Service Accounts where possible to constrain the blast radius [@github-itm4n-printspoofer].

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 [@en-wiki-mimikatz]. Wikipedia's biographical summary is precise: "He released the first version of the software in May 2011 as closed source software" [@en-wiki-mimikatz]. 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` [@github-gentilkiwi-mimikatz].

`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](/blog/when-system-isnt-enough-the-windows-secure-kernel-and-the-en/) in this series [@secure-kernel-sibling]. 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 [@msrc-servicing-criteria]. The DACL evaluation algorithm does not protect against a caller who can read and write arbitrary kernel memory. The privilege list does not protect against a caller who can rewrite the privilege check. The integrity check does not protect against a caller who can edit the integrity label. Every primitive in the model is, by construction, defenceless against an attacker who has crossed the boundary the model considers itself responsible for defending.

> **Note:** With administrator rights and `SeDebugPrivilege`, the Windows 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. Mimikatz `token::elevate` is the canonical demonstration. The structural fix for *selected* secrets is Credential Guard, which moves the secret out of the NT kernel's address space entirely. See the Secure Kernel sibling article for the architecture [@secure-kernel-sibling, @msrc-servicing-criteria].

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 [@ms-learn-dac]. 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 [@ms-learn-ace-strings]. A conditional ACE carries an expression in addition to a SID and an access mask, and the kernel evaluates the expression against the token'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" [@ms-learn-dac].

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 [@ms-learn-dac-scenario].

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" [@ms-learn-dac]. Heterogeneous environments fall back to classic DAC. Airgapped environments without a working AD plus AD FS plane have no claims to evaluate. Conditional ACEs are a real extension of the model's subject dimension; they are also a real bet that the AD-and-Kerberos plane is healthy enough to evaluate them on every access.

<Sidenote>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 [@ms-learn-dac, @app-identity-sibling].</Sidenote>

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.

<Mermaid caption="The 2026 access-control plane: every primitive in this article composes for a single OpenObject decision.">
flowchart LR
    A[User-mode caller] -->|OpenObject name, DesiredAccess, Token| B[Object Manager]
    B -->|Resolve name in namespace| C[Namespace lookup]
    C -->|Fetch security descriptor| D[Object header SD]
    D --> E[SeAccessCheck]
    E --> F[Generic-to-specific mapping]
    F --> G[Mandatory Integrity Control check]
    G -->|Pass| H[AppContainer / capability check]
    H --> I[Privilege bypass: SeBackup / SeRestore / SeDebug]
    I --> J[DACL walk in canonical order]
    J --> K[Conditional ACE expression evaluation]
    K --> L[GrantedAccess accumulated]
    L --> M[SACL audit ACE emit if matched]
    M --> N[Return HANDLE or STATUS_ACCESS_DENIED]
</Mermaid>

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:

| # | Primitive | Section introduced | Year shipped | Canonical primary citation | Canonical attack |
|---|---|---|---|---|---|
| 1 | Security Reference Monitor | 3 | 1993 | [@ms-learn-access-control] | (Underlying surface; not directly attacked) |
| 2 | Security Identifier (SID) | 4 | 1993 | [@ms-learn-security-identifiers] | Misused well-known SIDs ("Everyone is just a SID") |
| 3 | Access Token | 5 | 1993 | [@ms-learn-access-tokens] | The Potato lineage; Mimikatz `token::elevate` |
| 4 | Security Descriptor | 3 | 1993 | [@ms-learn-how-dacls-control-access] | HiveNightmare (CVE-2021-36934) |
| 5 | DACL + ACE | 4 | 1993 | [@ms-learn-how-dacls-control-access, @ms-learn-order-of-aces] | NULL DACL misconfigurations; out-of-order ACEs |
| 6 | SACL + audit | 3 | 1993 | [@ms-learn-access-control] | Tools that copy DACL but not SACL silently drop integrity labels |
| 7 | Privilege | 6 | 1993 | [@ms-learn-privileges] | Mimikatz `privilege::debug`; `SeBackup` abuse |
| 8 | Mandatory Integrity Control | 7 | 2007 | [@ms-learn-mic] | IE7 Protected Mode broker bypasses |
| 9 | UAC split-token | 8 | 2007 | [@ms-learn-uac] | UACMe: 70+ AutoElevate-redirect methods [@github-hfiref0x-uacme] |
| 10 | Conditional ACE / DAC | 11 | 2012 | [@ms-learn-dac, @ms-learn-ace-strings] | Falls back to classic DAC in heterogeneous environments |

> **Note:** The Windows access-control model is one decision plane, not a feature catalogue. Every securable Windows operation resolves through `SeAccessCheck` against five fixed inputs. Every famous escalation tool of the last twenty-five years attacks one of those inputs. Recognising the model as a single plane is the key to using its vocabulary against any specific attack.

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" [@msrc-servicing-criteria]. The criteria document then enumerates which boundaries Microsoft commits to servicing. The kernel-mode / user-mode boundary qualifies. UAC and admin-to-kernel are not in the enumerated list. Once that admission is on the public record, the model'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 [@secure-kernel-sibling].

**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 [@ms-learn-administrator-protection, @techcommunity-admin-protection]. 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 [@ms-learn-administrator-protection, @techcommunity-admin-protection].

**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 [@techcommunity-windows-auth-evolution]. The on-ramp is the NTLM auditing channel introduced in Windows 11 24H2 and Windows Server 2025 (KB5064479, original publish date July 11, 2025), which records NTLMv1 usage in `Microsoft\Windows\NTLM\Operational` and gives administrators a per-workload deprecation telemetry [@ms-support-ntlm-auditing]. The forthcoming NTLMless article in this series will cover the architecture in detail.

**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 [@en-wiki-confused-deputy] 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 [@en-wiki-capability-based-security]. 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.

> **Note:** Hardy's 1988 paper [@wayback-cap-lore-hardy] and the Wikipedia summary [@en-wiki-confused-deputy] both say the same thing: ACL systems are structurally vulnerable to confused-deputy attacks; capability systems are not. The gap is not asymptotic. ACL engineering does not close it. The Potato lineage is what the gap looks like in the field, repeated against eight different coercion primitives over six years.

> **Key idea:** **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](/blog/your-face-is-not-your-password-inside-windows-hellos-hardwar/) plus a hidden, system-generated, profile-separated user account that issues an isolated admin token, and the resulting token is bounded in time and scope [@ms-learn-administrator-protection].

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" [@techcommunity-admin-protection]. *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 [@techcommunity-windows-auth-evolution]. 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" [@techcommunity-windows-auth-evolution].

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 [@ms-support-ntlm-auditing]. The local-NTLM-to-self attack class -- coerce a SYSTEM service to authenticate with the local-machine credential, accept the challenge, relay it back to a local service that trusts the credential -- ends when the local-machine NTLM credential ends. *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 [@secure-kernel-sibling].

**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 [@secure-kernel-sibling].

**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](/blog/pluton-a-tpm-on-silicon-microsoft-can-patch/) in this series covers the architecture and the Caliptra root-of-trust direction it foreshadows [@pluton-sibling]. The [TPM 2.0 architecture](/blog/the-tpm-in-windows-one-primitive-twenty-five-years-and-the-c/) that the same chain extends and the [Secure Boot chain](/blog/secure-boot-in-windows-the-chain-from-sector-zero-to-userini/) that runs before the access-control model boots are covered in their own sibling articles in this series [@tpm-sibling, @secure-boot-sibling].

<Aside label="Why these are the four">
The five limits enumerated in Section 13 and the four successor articles in this section are in one-to-one correspondence: Adminless closes #2 and #3, NTLMless closes #4, VBS Trustlets close #1, and Credential Guard is the canonical first Trustlet that demonstrates #1 closing for a specific high-value secret. Limit #5 -- the DACL is local -- is operational rather than architectural and is closed by deployment investment in AD plus AD FS rather than by a new mechanism. The correspondence is not a coincidence. Each successor was scoped to close a specific gap the access-control model could not close from inside.
</Aside>

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.

> **Note:** `whoami /all` prints the SIDs in the calling thread's token, the integrity level, every privilege with its `Enabled` / `Disabled` / `Default Enabled` state, and -- on AD-joined machines with claims -- the user and device claim set. It is the single most useful diagnostic command for understanding what a session can do. Read the `Enabled` column carefully: an available-but-disabled privilege does not affect any access check until the process explicitly enables it [@ms-learn-access-tokens].

> **Note:** `icacls <path>` prints the DACL on a file or directory; the mass-rights letters are `(F)` full, `(M)` modify, `(RX)` read and execute, `(R)` read, `(W)` write, `(D)` delete, `(GA)` generic all, `(GR)` generic read, `(GW)` generic write [@ms-learn-how-dacls-control-access]. PowerShell's `Get-Acl` returns the same descriptor as a structured object that can be filtered and audited at scale. Sysinternals `accesschk.exe` answers the inverted query (which paths grant a given SID a given right) and is the right tool for catching descriptor misconfigurations across a large file system. Treat NULL DACL and empty DACL surfaces as the most-likely misconfiguration vectors.

> **Note:** On every Windows server, enumerate the principals whose tokens hold `SeImpersonatePrivilege` or `SeAssignPrimaryTokenPrivilege` in their *Default* or *Available* lists. Treat any non-LocalSystem-or-equivalent holder as a Potato target until proven otherwise. Where a service must hold the privilege (most managed-service workloads do), constrain the blast radius with per-service SIDs and Group Managed Service Accounts so that a compromise of one service does not extend to a compromise of every service that shares the host's identity [@github-itm4n-printspoofer].

> **Note:** The UACMe README is the institutional memory for the seventy-method bypass canon. Every method's `Fixed in:` field cites a specific Windows version or `unfixed`. Before declaring a binary "patched," consult the README; a method with a `Fixed in: unfixed` annotation is structurally available on every supported Windows version. The institutional position is that UAC bypasses do not, by Microsoft's own servicing-criteria policy, earn CVEs of their own, so the mitigations are issued per-redirect rather than per-feature [@github-hfiref0x-uacme, @msrc-servicing-criteria].

> **Note:** Windows Event ID 4688 ("A new process has been created") is the most-cited detection signal for the Potato lineage and the UAC bypass tradition, because almost every member of both families ends in a `CreateProcessAsUser` or a redirected AutoElevate launch with a command-line argument that does not match the legitimate use of the parent binary. Enable command-line auditing under *Audit Process Creation* and forward the log; Sysmon Event ID 1 is the equivalent and richer signal in environments that deploy Sysinternals' Sysmon.

> **Note:** The access matrix is the part of the model with deliberately extensible *subjects*. New code that lives behind an AppContainer SID gets a Low-IL token, a Package SID, and a capability list that constrain what it can touch even when the user running it is an administrator. New file shares that need attribute-based authorization should use conditional ACEs and Dynamic Access Control rather than ad-hoc group membership. Cross-link to the [App Identity sibling article](/blog/who-is-this-code----the-quiet-33-year-reinvention-of-app-ide/) for Package SID derivation [@app-identity-sibling] and to the Dynamic Access Control overview [@ms-learn-dac].

<Spoiler kind="solution" label="A simulator for the full access-control plane">

<RunnableCode lang="js" title="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
`}</RunnableCode>

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.
</Spoiler>

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.

<MarginNote>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.</MarginNote>

## 16. Frequently Asked Questions

<FAQ title="Frequently asked questions">
<FAQItem question="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 [@msrc-servicing-criteria]; bypasses are not, by policy, eligible for security servicing as boundary violations. The seventy-plus methods catalogued in UACMe are the empirical consequence of the classification, not a long string of bugs in a feature that was meant to defend against the techniques [@github-hfiref0x-uacme].
</FAQItem>
<FAQItem question="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" [@tiraniddo-sharing-logon-session].
</FAQItem>
<FAQItem question="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 [@ms-learn-how-dacls-control-access]. Verify with `Get-Acl` or `icacls` after creation; an `icacls` output of `Everyone:(F)` on an object you intended to lock down is almost always a NULL DACL, not the policy you meant to write.
</FAQItem>
<FAQItem question="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 [@ms-learn-mic]. For AppContainer, no -- AppContainer-bound objects require the capability SID, not just an administrator SID. For VBS Trustlets, no -- secrets in VTL1 are unreachable from VTL0 even with administrator rights, which is the whole point of the architecture [@secure-kernel-sibling].
</FAQItem>
<FAQItem question="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 [@ms-learn-privileges].
</FAQItem>
<FAQItem question="JuicyPotato was patched. Is the Potato chapter closed?">
No. The underlying NTLM-to-self surface is still open. SharpEfsPotato (2021) [@github-bugch3ck-sharpefspotato] is the most recent member of the lineage; new tools using fresh coercion primitives (EFSRPC, the spooler, the schedule-task COM interface, cross-session DCOM) appear every twelve to eighteen months. Microsoft's own Hot Potato post called the underlying issue "hard to fix without breaking backward compatibility" [@foxglove-hot-potato-blog]; the structural fix is the Adminless and NTLMless successor articles, not a point patch on any one primitive.
</FAQItem>
<FAQItem question="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 [@app-identity-sibling].
</FAQItem>
</FAQ>

## 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 [@secure-kernel-sibling]. The Pluton article roots the hardware identity chain that the successor architectures all eventually verify against [@pluton-sibling]. The TPM article and the Secure Boot article cover the static-time and boot-time chains that run before the access-control model even loads [@tpm-sibling, @secure-boot-sibling]. 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.

<StudyGuide slug="windows-access-control-twenty-five-years" keyTerms={[
  { term: "SeAccessCheck", definition: "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." },
  { term: "Security Reference Monitor (SRM)", definition: "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." },
  { term: "Access Token", definition: "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." },
  { term: "Discretionary Access Control List (DACL)", definition: "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." },
  { term: "Mandatory Integrity Control (MIC)", definition: "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." },
  { term: "User Account Control (UAC)", definition: "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." },
  { term: "SeImpersonatePrivilege", definition: "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." },
  { term: "Confused Deputy", definition: "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." },
  { term: "VBS Trustlet", definition: "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---
```
