NtFilterToken
Creates a restricted (filtered) copy of an existing access token by disabling SIDs, deleting privileges, or adding restricted SIDs.
Prototype
NTSTATUS NtFilterToken( HANDLE ExistingTokenHandle, ULONG Flags, PTOKEN_GROUPS SidsToDisable, PTOKEN_PRIVILEGES PrivilegesToDelete, PTOKEN_GROUPS RestrictedSids, PHANDLE NewTokenHandle );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| ExistingTokenHandle | HANDLE | in | Handle to the source token (needs TOKEN_DUPLICATE access). |
| Flags | ULONG | in | Combination of DISABLE_MAX_PRIVILEGE, SANDBOX_INERT, LUA_TOKEN, WRITE_RESTRICTED. |
| SidsToDisable | PTOKEN_GROUPS | in | Optional list of SIDs to mark as deny-only in the new token (NULL leaves them enabled). |
| PrivilegesToDelete | PTOKEN_PRIVILEGES | in | Optional list of privileges to strip from the new token (e.g. SeDebugPrivilege, SeImpersonatePrivilege). |
| RestrictedSids | PTOKEN_GROUPS | in | Optional list of restricted SIDs added to the new token; the kernel will AND-check these against every DACL. |
| NewTokenHandle | PHANDLE | out | Receives the handle to the newly filtered token on success. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0xD5 | win10-1507 |
| Win10 1607 | 0xD8 | win10-1607 |
| Win10 1703 | 0xDB | win10-1703 |
| Win10 1709 | 0xDC | win10-1709 |
| Win10 1803 | 0xDD | win10-1803 |
| Win10 1809 | 0xDE | win10-1809 |
| Win10 1903 | 0xDF | win10-1903 |
| Win10 1909 | 0xDF | win10-1909 |
| Win10 2004 | 0xE4 | win10-2004 |
| Win10 20H2 | 0xE4 | win10-20h2 |
| Win10 21H1 | 0xE4 | win10-21h1 |
| Win10 21H2 | 0xE5 | win10-21h2 |
| Win10 22H2 | 0xE5 | win10-22h2 |
| Win11 21H2 | 0xEA | win11-21h2 |
| Win11 22H2 | 0xEB | win11-22h2 |
| Win11 23H2 | 0xEB | win11-23h2 |
| Win11 24H2 | 0xED | win11-24h2 |
| Server 2016 | 0xD8 | winserver-2016 |
| Server 2019 | 0xDE | winserver-2019 |
| Server 2022 | 0xE9 | winserver-2022 |
| Server 2025 | 0xED | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 ED 00 00 00 mov eax, 0xED 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
NtFilterToken is the kernel implementation behind advapi32!CreateRestrictedToken. Given a source token it returns a new primary or impersonation token with selected SIDs flipped to deny-only, selected privileges removed, and optional *restricted SIDs* attached. Restricted SIDs are powerful: when the kernel performs an access check the granted access is ANDed against what the restricted-SID list alone would grant, so the resulting token can be locked far below the user's normal rights. The SSN drifts almost every feature update (`0xD5` on Win10 1507, `0xED` on Win11 24H2), so dynamic resolution is mandatory.
Common malware usage
Used legitimately by the Chromium / Edge / Acrobat sandboxes and by AppContainer brokers. Offensive use is real but rarer: an attacker holding a high-privileged token (SYSTEM, an impersonated user with SeDebugPrivilege) can deliberately *downgrade* a child process or impersonation context to look like a low-privilege user — a form of token demotion that defeats detections keyed on "SYSTEM did X". A handful of red-team frameworks and a small number of malware-research samples wrap NtFilterToken for exactly this reason. It is *not* a strong offensive signal by itself.
Detection opportunities
Token-event coverage is the right place to start: the Microsoft-Windows-Security-Auditing 4696/4624 events surface logon-token changes, and ETW Microsoft-Windows-Kernel-Audit-API-Calls covers token creation. EDRs typically hook NtFilterToken to feed token-derivation graphs. Useful blue-team signal: NtFilterToken called *with* a high-privilege source token *from* an unusual process (non-browser, non-broker). Combine with subsequent NtCreateUserProcess + AssignPrimaryToken to spot deliberate token demotion before child-process execution.
Direct syscall examples
asmx64 direct stub (Win11 24H2 SSN)
; Direct syscall stub for NtFilterToken (SSN 0xED on Win11 24H2 / Server 2025)
NtFilterToken PROC
mov r10, rcx ; syscall convention
mov eax, 0EDh ; SSN for win11-24h2
syscall
ret
NtFilterToken ENDPcToken demotion: strip privileges from current token
// Take the current process token and produce a filtered copy with SeDebugPrivilege
// and SeImpersonatePrivilege removed — useful for spawning a less-suspicious child.
HANDLE hCurrent = NULL;
NtOpenProcessToken(NtCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hCurrent);
LUID seDebug, seImpersonate;
LookupPrivilegeValueW(NULL, L"SeDebugPrivilege", &seDebug);
LookupPrivilegeValueW(NULL, L"SeImpersonatePrivilege", &seImpersonate);
struct {
DWORD Count;
LUID_AND_ATTRIBUTES Priv[2];
} toDelete = { 2, {{seDebug, 0}, {seImpersonate, 0}} };
HANDLE hFiltered = NULL;
NtFilterToken(hCurrent,
DISABLE_MAX_PRIVILEGE, // also strip every non-mandatory privilege
NULL, // SidsToDisable
(PTOKEN_PRIVILEGES)&toDelete,
NULL, // RestrictedSids
&hFiltered);rustwindows-sys + naked syscall stub
// Cargo: windows-sys = "0.59" (Win32_Security)
use std::arch::asm;
#[unsafe(naked)]
unsafe extern "system" fn nt_filter_token_stub() {
asm!(
"mov r10, rcx",
"mov eax, 0xED", // Win11 24H2; resolve dynamically for other builds
"syscall",
"ret",
options(noreturn),
);
}MITRE ATT&CK mappings
Last verified: 2026-05-20