> Windows Syscalls
ntoskrnl.exeT1055T1106

NtCreateThread

Legacy thread-creation syscall requiring a manually-built INITIAL_TEB; superseded by NtCreateThreadEx.

Prototype

NTSTATUS NtCreateThread(
  PHANDLE            ThreadHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes,
  HANDLE             ProcessHandle,
  PCLIENT_ID         ClientId,
  PCONTEXT           ThreadContext,
  PINITIAL_TEB       InitialTeb,
  BOOLEAN            CreateSuspended
);

Arguments

NameTypeDirDescription
ThreadHandlePHANDLEoutReceives the handle to the newly created thread on success.
DesiredAccessACCESS_MASKinAccess rights for the returned thread handle, typically THREAD_ALL_ACCESS.
ObjectAttributesPOBJECT_ATTRIBUTESinOptional object attributes; almost always NULL for thread creation.
ProcessHandleHANDLEinHandle to the target process that will own the thread (PROCESS_CREATE_THREAD required).
ClientIdPCLIENT_IDoutReceives the (PID, TID) pair identifying the newly created thread.
ThreadContextPCONTEXTinFully populated CONTEXT structure with RIP/RSP for the new thread — caller-supplied initial register state.
InitialTebPINITIAL_TEBinManually-built INITIAL_TEB describing the user stack (base, limit, allocation base). The reason this API is painful.
CreateSuspendedBOOLEANinIf TRUE the thread is created in suspended state and must be resumed with NtResumeThread.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x4Ewin10-1507
Win10 16070x4Ewin10-1607
Win10 17030x4Ewin10-1703
Win10 17090x4Ewin10-1709
Win10 18030x4Ewin10-1803
Win10 18090x4Ewin10-1809
Win10 19030x4Ewin10-1903
Win10 19090x4Ewin10-1909
Win10 20040x4Ewin10-2004
Win10 20H20x4Ewin10-20h2
Win10 21H10x4Ewin10-21h1
Win10 21H20x4Ewin10-21h2
Win10 22H20x4Ewin10-22h2
Win11 21H20x4Ewin11-21h2
Win11 22H20x4Ewin11-22h2
Win11 23H20x4Ewin11-23h2
Win11 24H20x4Ewin11-24h2
Server 20160x4Ewinserver-2016
Server 20190x4Ewinserver-2019
Server 20220x4Ewinserver-2022
Server 20250x4Ewinserver-2025

Kernel module

ntoskrnl.exeNtCreateThread

Related APIs

CreateThreadCreateRemoteThreadRtlCreateUserThreadNtCreateThreadExNtResumeThread

Syscall stub

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

NtCreateThread is the original pre-Vista thread-creation primitive. Unlike its successor NtCreateThreadEx, it is undocumented in the public headers and forces the caller to build an INITIAL_TEB by hand — describing stack base, stack limit and allocation base — plus a fully populated CONTEXT containing RIP/RSP. The SSN `0x4E` has been stable across every supported Windows 10 and Windows 11 build, which is unusual and reflects the fact that Microsoft no longer touches this surface. The kernel still implements it for backwards compatibility but Win32 CreateThread, RtlCreateUserThread and Nt CreateThreadEx all dispatch through NtCreateThreadEx today.

Common malware usage

Used by older injection tooling that predates NtCreateThreadEx attribute lists — some PassTheHash variants, early Mimikatz token-stealing primitives and legacy CreateRemoteThread alternatives. A small subset of modern offensive tooling deliberately picks NtCreateThread precisely because so few EDRs bother hooking it: the API is so rarely exercised by legitimate code that vendor coverage is patchy, making it a niche bypass for naive userland hook stacks. The trade-off is that the caller must allocate and stitch together a stack in the target process before issuing the call.

Detection opportunities

ETW Microsoft-Windows-Threat-Intelligence emits ThreadCreate events from PspInsertThread regardless of whether the userland entry point was Nt CreateThread or NtCreateThreadEx — so kernel-level telemetry is identical. Sysmon Event ID 8 (CreateRemoteThread) likewise fires for any cross-process thread injection. The only meaningful discriminator is in userland: an EDR that only hooks ntdll!NtCreateThreadEx will miss code paths going through NtCreateThread. Defenders should ensure both stubs are instrumented and watch for the rare combination of NtCreateThread + a remote ProcessHandle.

Direct syscall examples

asmx64 direct stub

; Direct syscall stub for NtCreateThread (SSN 0x4E, Win10 1507+ through Win11 24H2)
NtCreateThread PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 4Eh          ; SSN
    syscall
    ret
NtCreateThread ENDP

cLegacy remote thread with hand-built INITIAL_TEB

// Caller must allocate stack in the target process and populate both CONTEXT and INITIAL_TEB.
typedef struct _INITIAL_TEB {
    PVOID PreviousStackBase;
    PVOID PreviousStackLimit;
    PVOID StackBase;
    PVOID StackLimit;
    PVOID AllocatedStackBase;
} INITIAL_TEB, *PINITIAL_TEB;

SIZE_T stackSize = 0x10000;
PVOID  remoteStack = NULL;
NtAllocateVirtualMemory(hProcess, &remoteStack, 0, &stackSize,
                        MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

INITIAL_TEB teb = { 0 };
teb.StackBase          = (PBYTE)remoteStack + stackSize;
teb.StackLimit         = remoteStack;
teb.AllocatedStackBase = remoteStack;

CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_FULL;
ctx.Rip = (DWORD64)pShellcode;
ctx.Rsp = (DWORD64)teb.StackBase - 0x28;

HANDLE   hThread = NULL;
CLIENT_ID cid    = { 0 };
NtCreateThread(&hThread, THREAD_ALL_ACCESS, NULL, hProcess,
               &cid, &ctx, &teb, FALSE);

rustwindows-sys + naked syscall stub

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

#[unsafe(naked)]
unsafe extern "system" fn nt_create_thread_stub() {
    asm!(
        "mov r10, rcx",
        "mov eax, 0x4E",
        "syscall",
        "ret",
        options(noreturn),
    );
}

MITRE ATT&CK mappings

Last verified: 2026-05-20