Integrity Levels
Mandatory Integrity Control (MIC), IL levels from Untrusted to System, the mandatory policy and how it blocks write-up, UAC elevation, and privilege escalation detection
A browser exploit runs shellcode at Low Integrity — the browser's sandbox. The shellcode can read files and registry keys at Medium Integrity (the user's documents, mail, etc.) because reading cross-IL is allowed. But it can't write to any Medium Integrity location — not the registry, not AppData, not the temp folder. To do anything persistent, it must escape the sandbox to at least Medium Integrity. This is the role of integrity levels: containing a compromised low-integrity process even when it runs as the same user as Medium Integrity processes.
Mandatory Integrity Control
Mandatory Integrity Control (MIC), introduced in Windows Vista, adds a second layer of access control on top of discretionary access control (DACL). It's "mandatory" because users can't override it — unlike DACLs, you can't grant yourself access to a higher-integrity resource from a lower-integrity process. MIC operates on a no-write-up principle derived from the Bell-LaPadula security model.
MIC Access Rule:
Subject IL compared against Object IL
Subject IL > Object IL:
Write: ALLOWED (unless NO_WRITE_UP in object policy)
Read: ALLOWED
Subject IL == Object IL:
Both Read and Write: ALLOWED (DACL still applies)
Subject IL < Object IL (the common case at sandbox boundary):
Write: BLOCKED by MIC (even if DACL grants write permission)
Read: ALLOWED (by default; NO_READ_UP in policy blocks this too)
Execute: ALLOWED (by default)
The mandatory policy on the object overrides: tokens can add
NO_READ_UP or NO_EXECUTE_UP to restrict even cross-level reads.
Integrity Levels
| Level | SID | Value | Who runs here |
|---|---|---|---|
| Untrusted | S-1-16-0 | 0x0000 | Anonymous logon processes; SRP Untrusted |
| Low | S-1-16-4096 | 0x1000 | Internet Explorer Protected Mode, Chrome GPU/Renderer, sandbox processes |
| Medium | S-1-16-8192 | 0x2000 | Standard user processes: Explorer, Office, cmd.exe, malware running as standard user |
| Medium Plus | S-1-16-8448 | 0x2100 | Windows Color System, some COM servers |
| High | S-1-16-12288 | 0x3000 | Admin processes after UAC elevation; elevated cmd.exe |
| System | S-1-16-16384 | 0x4000 | SYSTEM services, lsass, drivers |
| Protected Process | S-1-16-20480 | 0x5000 | PPL processes (WinDefender, etc.) |
Mandatory Policy Flags
Objects (files, registry keys, processes, named pipes) can carry a System Access Control List (SACL) that includes a mandatory label ACE. The policy flags on that ACE control the exact MIC enforcement:
| Flag | Value | Effect |
|---|---|---|
| SYSTEM_MANDATORY_LABEL_NO_WRITE_UP | 0x01 | Lower-IL subjects cannot write to this object (the default for most resources) |
| SYSTEM_MANDATORY_LABEL_NO_READ_UP | 0x02 | Lower-IL subjects cannot read this object (used for secrets: lsass, DPAPI keys) |
| SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | 0x04 | Lower-IL subjects cannot execute this object |
UAC and the Two-Token Model
When a member of the Administrators group logs on interactively, Windows creates two tokens for the session:
- Filtered (standard) token: Medium Integrity, most admin-specific group memberships removed or disabled, admin privileges (SeDebugPrivilege, etc.) absent. Used for Explorer and all normal processes.
- Elevated (linked) token: High Integrity, full Administrator memberships enabled, all privileges present. Used only when UAC prompt is accepted for elevation.
The two tokens are "linked" — one can query the other via GetTokenInformation(TokenLinkedToken). A process at Medium Integrity cannot use the linked token directly — elevation must go through the UAC consent UI (or a UAC bypass).
UAC Bypass Techniques
| Technique | Mechanism | MITRE |
|---|---|---|
| Auto-elevation of trusted executables | Signed MS executables with autoElevate=true in their manifest elevate without prompt; abuse their functionality to execute code | T1548.002 |
| COM elevation moniker | Create an elevated COM object via Elevation:Administrator!new:{CLSID} to run code in an auto-elevated COM server | T1548.002 |
| DLL planting for auto-elevated exe | Side-load a DLL into an auto-elevated executable by exploiting its DLL search order | T1574.002 + T1548.002 |
| Registry hijack (fodhelper, eventvwr) | Many auto-elevated executables check user-writable registry keys at startup; writing a malicious command there causes the auto-elevated process to execute it | T1548.002 |
| Token manipulation (linked token) | A process with SeDebugPrivilege can open the linked token directly via NtQueryInformationToken and duplicate it to High IL | T1134 |
Detection
# Python: query a process's integrity level
import ctypes, ctypes.wintypes, struct
TokenIntegrityLevel = 25
def get_process_il(pid: int) -> str:
hProc = ctypes.windll.kernel32.OpenProcess(0x0400, 0, pid)
hToken = ctypes.wintypes.HANDLE()
ctypes.windll.advapi32.OpenProcessToken(
hProc, 0x0008, ctypes.byref(hToken)) # TOKEN_QUERY
buf = (ctypes.c_byte * 64)()
size = ctypes.c_ulong()
ctypes.windll.advapi32.GetTokenInformation(
hToken, TokenIntegrityLevel, buf, 64, ctypes.byref(size))
# TOKEN_MANDATORY_LABEL: {SID_AND_ATTRIBUTES sid_and_attr}
# SID_AND_ATTRIBUTES: {PSID Sid; DWORD Attributes}
# Sid is a pointer to SID in the buffer at +8 (x64)
sid_ptr = struct.unpack_from('Q', buf, 0)[0]
# GetTokenInformation fills buffer in-place; use GetSidSubAuthority
# For simplicity: read last DWORD of the SID in-buffer
# SID starts at offset 0 in buffer for this query (it's a flat SID)
il_val = struct.unpack_from('I', buf, 12)[0] # SubAuthority[0]
ctypes.windll.kernel32.CloseHandle(hToken)
ctypes.windll.kernel32.CloseHandle(hProc)
return {
0x0000: "Untrusted", 0x1000: "Low",
0x2000: "Medium", 0x2100: "Medium+",
0x3000: "High", 0x4000: "System",
}.get(il_val, f"Unknown(0x{il_val:X})")
Q & A
If MIC blocks write-up by default, how can a Low Integrity browser render process affect the disk at all — for downloads, caches, etc.?
Low Integrity processes can write to a small set of explicitly Low Integrity-labeled locations. When IE Protected Mode and Chrome sandboxes were designed, Microsoft (and Google) created specific low-integrity-accessible locations: (1) The user's AppData\LocalLow directory is labeled Low Integrity — browser caches and downloads land there, not AppData\Local (Medium). (2) Specific registry keys under HKCU that are labeled Low Integrity allow the browser to store some settings. (3) Temp Internet Files are stored in a Low Integrity path. Outside these locations: a Low Integrity renderer can't write to AppData\Local, AppData\Roaming, the Desktop, Documents, or any Medium/High Integrity path. Downloads requested by the user go through a broker process — the sandbox communicates with a Medium Integrity broker (which CAN write), and the broker writes the file on behalf of the sandbox. This broker IPC is the sandbox "escape" mechanism that's designed-in; vulnerabilities in the broker are sandbox escapes. The key takeaway for detection: a Low Integrity process writing to non-Low-Integrity paths is either exploiting a privilege escalation vulnerability or has already escaped the sandbox.
How does Windows enforce that a process stays at its integrity level — what stops a Medium process from just relabeling itself?
The integrity level is stored in the process's primary token, which is a kernel object. A process cannot write to its own token directly — tokens are read-only from user mode via GetTokenInformation. The only way to change the token is to create a new one (via logon, DuplicateTokenEx, etc.) and assign it to a new process. A running process cannot change its own IL mid-flight. More specifically: (1) SetTokenInformation with TokenIntegrityLevel can be called, but only to lower the IL, not raise it. A process can label itself as Low IL (to create a sandboxed child process), but cannot label itself High IL. (2) Raising IL requires: starting as admin + UAC elevation → creates a High IL token that is then used to spawn the elevated process. Or: token theft via SeDebugPrivilege of a High/System process — but SeDebugPrivilege is only present in the High IL token in the first place. (3) This creates the bootstrap problem attackers must solve: to get High IL, you need a High IL token; to get a High IL token, you need High IL or a UAC bypass. UAC bypasses work by getting an auto-elevated process to execute code for you — you never touch the token, but your code runs inside a process that already has the elevated token.