NtAccessCheck
Performs a security access check of a security descriptor against an impersonation token, returning the granted access mask.
Prototype
NTSTATUS NtAccessCheck( PSECURITY_DESCRIPTOR SecurityDescriptor, HANDLE ClientToken, ACCESS_MASK DesiredAccess, PGENERIC_MAPPING GenericMapping, PPRIVILEGE_SET PrivilegeSet, PULONG PrivilegeSetLength, PACCESS_MASK GrantedAccess, PNTSTATUS AccessStatus );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| SecurityDescriptor | PSECURITY_DESCRIPTOR | in | Self-relative security descriptor of the protected resource to evaluate. |
| ClientToken | HANDLE | in | Handle to an impersonation token (TOKEN_QUERY) — must be an impersonation token, not a primary token. |
| DesiredAccess | ACCESS_MASK | in | Access rights the caller wants to check (may include generic rights — those are mapped via GenericMapping). |
| GenericMapping | PGENERIC_MAPPING | in | Object-type-specific mapping from GENERIC_READ/WRITE/EXECUTE/ALL to concrete access bits. |
| PrivilegeSet | PPRIVILEGE_SET | out | Receives the privileges the kernel actually used during the check (e.g. SeSecurityPrivilege when reading SACLs). |
| PrivilegeSetLength | PULONG | in/out | In: size in bytes of the PrivilegeSet buffer. Out: size actually required. |
| GrantedAccess | PACCESS_MASK | out | Receives the access mask that would actually be granted to ClientToken. |
| AccessStatus | PNTSTATUS | out | Receives STATUS_SUCCESS if access would be granted, STATUS_ACCESS_DENIED otherwise. Distinct from the function's own NTSTATUS. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x0 | win10-1507 |
| Win10 1607 | 0x0 | win10-1607 |
| Win10 1703 | 0x0 | win10-1703 |
| Win10 1709 | 0x0 | win10-1709 |
| Win10 1803 | 0x0 | win10-1803 |
| Win10 1809 | 0x0 | win10-1809 |
| Win10 1903 | 0x0 | win10-1903 |
| Win10 1909 | 0x0 | win10-1909 |
| Win10 2004 | 0x0 | win10-2004 |
| Win10 20H2 | 0x0 | win10-20h2 |
| Win10 21H1 | 0x0 | win10-21h1 |
| Win10 21H2 | 0x0 | win10-21h2 |
| Win10 22H2 | 0x0 | win10-22h2 |
| Win11 21H2 | 0x0 | win11-21h2 |
| Win11 22H2 | 0x0 | win11-22h2 |
| Win11 23H2 | 0x0 | win11-23h2 |
| Win11 24H2 | 0x0 | win11-24h2 |
| Server 2016 | 0x0 | winserver-2016 |
| Server 2019 | 0x0 | winserver-2019 |
| Server 2022 | 0x0 | winserver-2022 |
| Server 2025 | 0x0 | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 00 00 00 00 mov eax, 0x00 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
NtAccessCheck holds the literal first slot in the System Service Descriptor Table — SSN `0x0` on every Windows build since NT 4 — which makes it a delightful curiosity but useless as an offensive primitive. It is the user-mode entrypoint for *manual* access-check logic: a service that has just impersonated a client uses NtAccessCheck (typically via the Win32 AccessCheck wrapper) to decide whether the client *would* be allowed to perform an operation against a resource the service controls. It does not itself open anything — it is a pure evaluation function over a SECURITY_DESCRIPTOR + token + GENERIC_MAPPING.
Common malware usage
Almost never used for offense directly. The two patterns that do show up: (1) post-exploitation tools enumerating which DACL'd objects a stolen impersonation token can reach without actually opening them (silent reconnaissance — no audit fires unless SACLs are configured); (2) exploit code that probes whether a forged or relogged token grants the intended access. Honest assessment: NtAccessCheck is dominated by legitimate usage and is a weak malware signal.
Detection opportunities
Almost no useful telemetry exists. NtAccessCheck does not itself trigger object-access auditing (Event ID 4663) — only the subsequent open does. ETW Microsoft-Windows-Security-Auditing's privilege-use events can fire if the call references a SACL-bearing object during evaluation, but coverage is sparse. Useful as a *correlation* signal: an unusual process performing many NtAccessCheck calls back-to-back against varied security descriptors, followed by selective NtOpen* calls only on the ones that succeeded, is a classic silent-reconnaissance pattern.
Direct syscall examples
asmx64 direct stub (SSN 0x0 — first in the SSDT)
; Direct syscall stub for NtAccessCheck (SSN 0x00 on every Windows build)
NtAccessCheck PROC
mov r10, rcx ; syscall convention
mov eax, 0 ; SSN
syscall
ret
NtAccessCheck ENDPcService-style: impersonate client and probe access
// After ImpersonateNamedPipeClient / RpcImpersonateClient, the service evaluates
// whether the caller would be allowed FILE_WRITE_DATA on a private resource.
HANDLE hClientToken = NULL;
NtOpenThreadToken(NtCurrentThread(), TOKEN_QUERY, FALSE, &hClientToken);
GENERIC_MAPPING fileMapping = {
.GenericRead = FILE_GENERIC_READ,
.GenericWrite = FILE_GENERIC_WRITE,
.GenericExecute = FILE_GENERIC_EXECUTE,
.GenericAll = FILE_ALL_ACCESS,
};
BYTE privBuf[256];
ULONG privLen = sizeof(privBuf);
ACCESS_MASK granted = 0;
NTSTATUS accessStatus = 0;
NtAccessCheck(pResourceSD,
hClientToken,
FILE_WRITE_DATA,
&fileMapping,
(PPRIVILEGE_SET)privBuf,
&privLen,
&granted,
&accessStatus);
if (NT_SUCCESS(accessStatus) && (granted & FILE_WRITE_DATA)) {
// proceed under impersonation
}rustwindows-sys + naked syscall stub
// Cargo: windows-sys = "0.59" (Win32_Security)
use std::arch::asm;
#[unsafe(naked)]
unsafe extern "system" fn nt_access_check_stub() {
asm!(
"mov r10, rcx",
"mov eax, 0x00",
"syscall",
"ret",
options(noreturn),
);
}MITRE ATT&CK mappings
Last verified: 2026-05-20