NtAdjustPrivilegesToken
Enables or disables privileges in a specified access token.
Prototype
NTSTATUS NtAdjustPrivilegesToken( HANDLE TokenHandle, BOOLEAN DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, ULONG BufferLength, PTOKEN_PRIVILEGES PreviousState, PULONG ReturnLength );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| TokenHandle | HANDLE | in | Handle to a token opened with TOKEN_ADJUST_PRIVILEGES (and TOKEN_QUERY if PreviousState is requested). |
| DisableAllPrivileges | BOOLEAN | in | If TRUE, disables every privilege in the token; NewState is ignored. |
| NewState | PTOKEN_PRIVILEGES | in | Array of LUID_AND_ATTRIBUTES describing which privileges to enable/disable/remove. |
| BufferLength | ULONG | in | Size in bytes of the PreviousState buffer. 0 if PreviousState is NULL. |
| PreviousState | PTOKEN_PRIVILEGES | out | Optional buffer receiving the prior state of modified privileges, useful for revert. |
| ReturnLength | PULONG | out | Receives the number of bytes actually written to PreviousState. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x41 | win10-1507 |
| Win10 1607 | 0x41 | win10-1607 |
| Win10 1703 | 0x41 | win10-1703 |
| Win10 1709 | 0x41 | win10-1709 |
| Win10 1803 | 0x41 | win10-1803 |
| Win10 1809 | 0x41 | win10-1809 |
| Win10 1903 | 0x41 | win10-1903 |
| Win10 1909 | 0x41 | win10-1909 |
| Win10 2004 | 0x41 | win10-2004 |
| Win10 20H2 | 0x41 | win10-20h2 |
| Win10 21H1 | 0x41 | win10-21h1 |
| Win10 21H2 | 0x41 | win10-21h2 |
| Win10 22H2 | 0x41 | win10-22h2 |
| Win11 21H2 | 0x41 | win11-21h2 |
| Win11 22H2 | 0x41 | win11-22h2 |
| Win11 23H2 | 0x41 | win11-23h2 |
| Win11 24H2 | 0x41 | win11-24h2 |
| Server 2016 | 0x41 | winserver-2016 |
| Server 2019 | 0x41 | winserver-2019 |
| Server 2022 | 0x41 | winserver-2022 |
| Server 2025 | 0x41 | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 41 00 00 00 mov eax, 0x41 F6 04 25 08 03 FE 7F 01 test byte ptr [0x7FFE0308], 1 75 03 jne short +3 0F 05 syscall C3 ret CD 2E int 2Eh C3 ret
Undocumented notes
NtAdjustPrivilegesToken is what `advapi32!AdjustTokenPrivileges` wraps. A subtle but critical behaviour: the function returns `STATUS_SUCCESS` (`0`) even when one or more requested privileges were *not* held — callers must check `GetLastError() == ERROR_NOT_ALL_ASSIGNED` (or `STATUS_NOT_ALL_ASSIGNED` from NTSTATUS). Failing to do so produces silent privilege-escalation bugs in defensive tooling. The SSN has been **stable at `0x41`** from Windows 10 1507 through Windows 11 24H2, making it a popular pick for hardcoded SSN tables.
Common malware usage
The mandatory step before any cross-process token manipulation: enable SeDebugPrivilege so the implant can `NtOpenProcess` LSASS or other protected services. Other privileges commonly enabled by malware: SeImpersonatePrivilege (required by the Potato family for token impersonation), SeAssignPrimaryTokenPrivilege (required by `CreateProcessWithTokenW`), SeTcbPrivilege (LSASS-grade), SeBackupPrivilege / SeRestorePrivilege (registry hive theft for SAM/SYSTEM dumps as in `reg save HKLM\SAM`). Mimikatz' `privilege::debug` is literally a wrapper around this syscall.
Detection opportunities
Windows Security Event ID **4703** (Token Right Adjusted) records every successful privilege adjustment, including which privileges were enabled and the calling process. Event 4672 (Special Privileges Assigned) fires at logon and again when sensitive privileges (SeDebug, SeTcb, SeImpersonate, SeAssignPrimaryToken, SeLoadDriver, SeBackup, SeRestore, SeTakeOwnership, SeCreateToken) are enabled in any new process. Correlating 4703 + subsequent 4663/4673 against LSASS is a very high-fidelity pattern for credential-dumping detection. ETW provider `Microsoft-Windows-Kernel-Audit-API-Calls` also covers it.
Direct syscall examples
asmx64 direct stub (stable SSN 0x41)
NtAdjustPrivilegesToken PROC
mov r10, rcx
mov eax, 41h ; SSN stable across all builds
syscall
ret
NtAdjustPrivilegesToken ENDPcEnable SeDebugPrivilege the classic way
// Enable SeDebugPrivilege via direct Nt* calls — required before opening LSASS.
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tp = { 0 };
NtOpenProcessTokenEx(NtCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
0, &hToken);
LookupPrivilegeValueW(NULL, L"SeDebugPrivilege", &luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
NTSTATUS s = NtAdjustPrivilegesToken(hToken,
FALSE,
&tp,
0, NULL, NULL);
// IMPORTANT: also test STATUS_NOT_ALL_ASSIGNED (0x06000000+) — success isn't enough.
if (s == 0x00000106 /* STATUS_NOT_ALL_ASSIGNED */) {
// Privilege wasn't held — abort the elevation chain.
}rustPrivilege-enable helper inside an impersonation chain
// Tiny helper used right after NtOpenProcessTokenEx, before NtDuplicateToken.
use windows_sys::Win32::Foundation::{HANDLE, LUID};
use windows_sys::Win32::Security::{LookupPrivilegeValueW, TOKEN_PRIVILEGES, SE_PRIVILEGE_ENABLED};
pub unsafe fn enable_privilege(token: HANDLE, name: &str) -> bool {
let wide: Vec<u16> = name.encode_utf16().chain(std::iter::once(0)).collect();
let mut luid: LUID = std::mem::zeroed();
if LookupPrivilegeValueW(std::ptr::null(), wide.as_ptr(), &mut luid) == 0 {
return false;
}
let mut tp = TOKEN_PRIVILEGES {
PrivilegeCount: 1,
Privileges: [windows_sys::Win32::Security::LUID_AND_ATTRIBUTES {
Luid: luid,
Attributes: SE_PRIVILEGE_ENABLED,
}],
};
// Direct syscall stub elsewhere; signature matches NtAdjustPrivilegesToken.
extern "system" { fn NtAdjustPrivilegesToken(
h: HANDLE, disable_all: u8,
new_state: *mut TOKEN_PRIVILEGES, len: u32,
prev: *mut TOKEN_PRIVILEGES, ret_len: *mut u32) -> i32; }
let s = NtAdjustPrivilegesToken(token, 0, &mut tp, 0, std::ptr::null_mut(), std::ptr::null_mut());
// STATUS_NOT_ALL_ASSIGNED == 0x00000106 — treat as failure for elevation.
s == 0
}MITRE ATT&CK mappings
- T1134Access Token Manipulation
- T1134.001Token Impersonation/Theft
- T1003OS Credential Dumping
- T1106Native API
Last verified: 2026-05-20