Privileges
Windows privilege mechanics — what privileges mean, the most abused privileges in post-exploitation, how to enable them programmatically, and detection via Event ID 4672
Code is running inside a Windows service account token. The token has SeImpersonatePrivilege listed as Enabled. A PrintSpoofer exploit tricks the spooler service into connecting to a named pipe controlled by the attacker. The attacker calls ImpersonateNamedPipeClient to assume the spooler's SYSTEM identity. With a SYSTEM impersonation token, they call DuplicateTokenEx and CreateProcessWithTokenW to launch a SYSTEM shell. Total time from SeImpersonatePrivilege to SYSTEM: about 2 seconds.
What Is a Privilege
A privilege is a system-level right assigned to an account that allows it to perform an operation that goes beyond normal access control. Unlike permissions (which are attached to objects), privileges are attached to subjects (tokens). Common examples: the right to debug other processes, the right to load kernel drivers, the right to back up files ignoring DACL.
Each privilege has:
- A name (e.g.,
SeDebugPrivilege) — used in policy settings and APIs - A LUID — a locally unique identifier assigned at boot, used internally
- An attribute — SE_PRIVILEGE_ENABLED (active), SE_PRIVILEGE_ENABLED_BY_DEFAULT, or disabled (present but inactive)
Key Offensive Privileges
| Privilege | What it grants | Typical holders | Attack use |
|---|---|---|---|
| SeDebugPrivilege | Open any process with any access rights, including SYSTEM and lsass | Administrators (disabled by default, can be enabled) | Token theft from SYSTEM process; lsass memory dump for credential harvest |
| SeImpersonatePrivilege | Impersonate a client after authentication | Service accounts (IIS, SQL Server, NETWORK SERVICE) | Potato attacks: trick privileged service to connect, impersonate its SYSTEM token |
| SeAssignPrimaryTokenPrivilege | Assign primary token to a process | Service accounts, LOCAL SERVICE | CreateProcessWithTokenW (together with SeImpersonatePrivilege) |
| SeTakeOwnershipPrivilege | Take ownership of any securable object (ignoring DACL) | Administrators | Take ownership of a protected file or registry key, then modify its DACL |
| SeLoadDriverPrivilege | Load and unload kernel drivers | Administrators | Load a vulnerable/malicious driver → kernel code execution |
| SeBackupPrivilege | Read any file/registry key, bypassing DACL, for backup purposes | Backup Operators group | Read SAM, SECURITY, SYSTEM hives → offline credential hash extraction |
| SeRestorePrivilege | Write any file/registry key, bypassing DACL, for restore purposes | Backup Operators group | Replace system files, write registry run keys even with restricted DACL |
| SeCreateSymbolicLinkPrivilege | Create symbolic links | Administrators, Developer Mode | Symlink attacks to redirect file operations |
| SeSecurityPrivilege | Manage auditing and security log; access SACL of objects | Administrators | Clear Security event log; modify audit policy to hide activity |
| SeTcbPrivilege | "Act as part of the operating system" | SYSTEM only | Create logon tokens without interactive logon; extremely powerful |
Enabling Privileges Programmatically
// Enable a privilege in the current process token
BOOL EnablePrivilege(const char *privName, BOOL enable)
{
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return FALSE;
LUID luid;
if (!LookupPrivilegeValueA(NULL, privName, &luid)) {
CloseHandle(hToken);
return FALSE;
}
TOKEN_PRIVILEGES tp = {};
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
BOOL ok = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
DWORD err = GetLastError();
CloseHandle(hToken);
// AdjustTokenPrivileges returns TRUE even on partial success;
// ERROR_NOT_ALL_ASSIGNED means the privilege wasn't in the token at all
return ok && err != ERROR_NOT_ALL_ASSIGNED;
}
// Usage:
EnablePrivilege(SE_DEBUG_NAME, TRUE); // "SeDebugPrivilege"
// Now OpenProcess(PROCESS_ALL_ACCESS, ...) will succeed on SYSTEM processes
Privilege Abuse Chains
The most common post-exploitation privilege chains in detection engineering:
| Start | Technique | Outcome |
|---|---|---|
| SeImpersonatePrivilege (service account) | PrintSpoofer / RoguePotato / EfsPotato named pipe trick | SYSTEM impersonation token → SYSTEM shell |
| SeDebugPrivilege (admin) | Open lsass with PROCESS_VM_READ; ReadProcessMemory | Credential hashes / Kerberos tickets from lsass |
| SeBackupPrivilege (Backup Operators) | Use NtCreateFile with FILE_FLAG_BACKUP_SEMANTICS to read SAM/SECURITY hives | Offline NT hash extraction, Pass-the-Hash |
| SeLoadDriverPrivilege (admin) | Load vulnerable driver (e.g., RTCore64.sys) via NtLoadDriver | Kernel code execution via IOCTL to the vulnerable driver |
| SeSecurityPrivilege (admin) | ClearEventLog (Security channel) via EvtClearLog | Log tampering, covering tracks |
Detection
# Key event IDs for privilege monitoring
# 4672: Special privileges assigned to new logon
# Fires when: any of the following privileges are in a new logon token:
# SeAssignPrimaryTokenPrivilege, SeAuditPrivilege, SeBackupPrivilege,
# SeCreateTokenPrivilege, SeDebugPrivilege, SeImpersonatePrivilege,
# SeLoadDriverPrivilege, SeSecurityPrivilege, SeTakeOwnershipPrivilege,
# SeTcbPrivilege, and more
# Look for: these on unexpected accounts (not on service accounts that
# should have SeImpersonatePrivilege — those are noise; focus on
# SeDebugPrivilege on non-admin accounts, or SeTcbPrivilege anywhere)
# 4673: A privileged service was called
# Fires when a privilege is used, not just present
# 4674: An operation was attempted on a privileged object
# Fires when a privilege check was performed
# Sigma: detect SeDebugPrivilege enabled on non-admin process
title: SeDebugPrivilege Enabled on Non-Admin Process
logsource:
product: windows
service: security
detection:
selection:
EventID: 4673
PrivilegeName: 'SeDebugPrivilege'
filter_admin:
SubjectUserSid|endswith:
- '-500' # Built-in admin RID
condition: selection and not filter_admin
fields:
- SubjectUserName
- SubjectUserSid
- ProcessName
Q & A
Why does SeDebugPrivilege allow reading lsass memory — isn't there a separate protection for lsass?
SeDebugPrivilege is a blanket override of the access check that would otherwise deny OpenProcess on a process you don't own. Without SeDebugPrivilege, OpenProcess on lsass fails with ACCESS_DENIED because your token's SID doesn't match and the SYSTEM token can't be delegated. With SeDebugPrivilege enabled, the access check succeeds regardless of the lsass DACL. Lsass doesn't have special protection above the normal DACL/IL scheme in a default Windows configuration. The protections that do exist: (1) PPL (Protected Process Light) — if lsass runs as PPL (enabled via HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL = 1), then even SeDebugPrivilege is insufficient to open it with PROCESS_VM_READ. PPL processes can only be opened by other PPL processes at the same or higher signer level. (2) Virtualization-Based Security (Credential Guard) — LSASS runs in a virtualized secure environment (VTL1), and the credentials (NT hashes, Kerberos tickets) never appear in the normal VTL0 process memory. Even if you read lsass VTL0 process memory with SeDebugPrivilege, the credentials aren't there. On default Windows 11 systems without these protections enabled, SeDebugPrivilege → lsass dump → NTLM hashes is a known, documented attack path.
What is the Backup Operators group and why is it a path to domain admin?
Backup Operators is a built-in Windows group whose members have SeBackupPrivilege and SeRestorePrivilege. These privileges let them read and write any file regardless of DACL — the intended use is allowing backup software to read protected files without needing admin rights. The path to Domain Admin: (1) Backup Operators members can read the Active Directory database (ntds.dit) from a domain controller via backup-semantic file access — the same way backup software does. (2) ntds.dit contains all user password hashes for the domain, encrypted with the SYSTEM key from the SECURITY hive. (3) Also read the SYSTEM hive (SeBackupPrivilege) to get the SYSTEM boot key (SYSKEY). (4) Use impacket or similar tool to extract NT hashes from the offline ntds.dit + SYSTEM hive. (5) Pass-the-Hash to authenticate as Domain Admin. In practice: an account in Backup Operators that's compromised is as dangerous as a Domain Admin. Defense: treat Backup Operators with the same sensitivity as Domain Admins; minimize who has membership; monitor Event 4672 for SeBackupPrivilege on domain controllers; and audit Backup Operators group membership changes (Event 4732: A member was added to a security-enabled local group).