> Windows Syscalls
ntoskrnl.exeT1106T1033

NtPrivilegeCheck

Tests whether the privileges named in a PRIVILEGE_SET are enabled in an impersonation token.

Prototype

NTSTATUS NtPrivilegeCheck(
  HANDLE         ClientToken,
  PPRIVILEGE_SET RequiredPrivileges,
  PBOOLEAN       Result
);

Arguments

NameTypeDirDescription
ClientTokenHANDLEinHandle to an impersonation token (TOKEN_QUERY). Primary tokens are rejected with STATUS_NO_IMPERSONATION_TOKEN.
RequiredPrivilegesPPRIVILEGE_SETin/outPrivileges to test. Control flag PRIVILEGE_SET_ALL_NECESSARY requires every entry; otherwise any single match satisfies the check. Each LUID_AND_ATTRIBUTES gets its Attributes field updated with SE_PRIVILEGE_USED_FOR_ACCESS on return.
ResultPBOOLEANoutReceives TRUE if the privilege check passes, FALSE otherwise. The function's NTSTATUS only reports errors in the call itself.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x122win10-1507
Win10 16070x128win10-1607
Win10 17030x12Cwin10-1703
Win10 17090x12Ewin10-1709
Win10 18030x130win10-1803
Win10 18090x131win10-1809
Win10 19030x132win10-1903
Win10 19090x132win10-1909
Win10 20040x137win10-2004
Win10 20H20x137win10-20h2
Win10 21H10x137win10-21h1
Win10 21H20x138win10-21h2
Win10 22H20x138win10-22h2
Win11 21H20x13Ewin11-21h2
Win11 22H20x140win11-22h2
Win11 23H20x140win11-23h2
Win11 24H20x142win11-24h2
Server 20160x128winserver-2016
Server 20190x131winserver-2019
Server 20220x13Dwinserver-2022
Server 20250x142winserver-2025

Kernel module

ntoskrnl.exeNtPrivilegeCheck

Related APIs

PrivilegeCheckGetTokenInformationAdjustTokenPrivilegesNtQueryInformationTokenNtAdjustPrivilegesTokenNtAccessCheck

Syscall stub

4C 8B D1            mov r10, rcx
B8 42 01 00 00      mov eax, 0x142
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

NtPrivilegeCheck is the kernel-level companion to AccessCheck: it asks `does this impersonation token currently have these privileges *enabled*?`. The PRIVILEGE_SET passed in carries a Control field — PRIVILEGE_SET_ALL_NECESSARY demands every listed privilege, otherwise the kernel returns TRUE on any single hit. On return each LUID_AND_ATTRIBUTES has SE_PRIVILEGE_USED_FOR_ACCESS set for the privileges that satisfied the check, which feeds privilege-use auditing. Strictly an *enabled* check: privileges that are present but disabled count as missing.

Common malware usage

Attackers do not really call NtPrivilegeCheck for offensive purposes — it grants nothing, only reports. Where it does appear in malware is when adversaries write detection-aware payloads that probe their own token before attempting a privileged operation (e.g. verifying SeDebugPrivilege is actually enabled before opening LSASS), so they can fall back silently if it isn't. Honest assessment: this is a legitimate-services-and-defenders syscall; treat it as a low-confidence offensive signal.

Detection opportunities

NtPrivilegeCheck does not by itself emit privilege-use audit events (4674) — those fire when a privileged operation actually executes. The most useful angle is EDR-side: hook ntdll!NtPrivilegeCheck and correlate `process X repeatedly probed for SeDebugPrivilege without holding it` (recon / capability discovery), or `process X probed for SeImpersonatePrivilege immediately before forging a token`. On a domain controller, repeated checks for SeBackupPrivilege/SeRestorePrivilege from non-backup software are notable. The signal is weak in isolation; useful in chains.

Direct syscall examples

asmx64 direct stub (Win11 24H2 SSN)

; Direct syscall stub for NtPrivilegeCheck (SSN 0x142 on Win11 24H2 / Server 2025)
NtPrivilegeCheck PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 142h         ; SSN for win11-24h2
    syscall
    ret
NtPrivilegeCheck ENDP

cProbe SeDebugPrivilege before opening LSASS

// Defensive *or* offensive recon: only proceed if the token already has SeDebug enabled.
HANDLE hThreadToken = NULL;
NtOpenThreadToken(NtCurrentThread(), TOKEN_QUERY, TRUE, &hThreadToken);
if (!hThreadToken) {
    NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hThreadToken);
    // primary token — must duplicate to impersonation before NtPrivilegeCheck
}

LUID seDebug;
LookupPrivilegeValueW(NULL, L"SeDebugPrivilege", &seDebug);

struct {
    DWORD             PrivilegeCount;
    DWORD             Control;
    LUID_AND_ATTRIBUTES Privilege[1];
} req = {
    1,
    PRIVILEGE_SET_ALL_NECESSARY,
    {{ seDebug, SE_PRIVILEGE_ENABLED }}
};

BOOLEAN granted = FALSE;
NtPrivilegeCheck(hThreadToken, (PPRIVILEGE_SET)&req, &granted);
if (granted) {
    // SeDebugPrivilege is enabled — safe to attempt PROCESS_VM_READ on LSASS
}

rustwindows-sys + naked syscall stub

// Cargo: windows-sys = "0.59"  (Win32_Security)
use std::arch::asm;

#[unsafe(naked)]
unsafe extern "system" fn nt_privilege_check_stub() {
    asm!(
        "mov r10, rcx",
        "mov eax, 0x142",   // Win11 24H2; resolve dynamically for other builds
        "syscall",
        "ret",
        options(noreturn),
    );
}

MITRE ATT&CK mappings

Last verified: 2026-05-20