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
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadHandle | PHANDLE | out | Receives the handle to the newly created thread on success. |
| DesiredAccess | ACCESS_MASK | in | Access rights for the returned thread handle, typically THREAD_ALL_ACCESS. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Optional object attributes; almost always NULL for thread creation. |
| ProcessHandle | HANDLE | in | Handle to the target process that will own the thread (PROCESS_CREATE_THREAD required). |
| ClientId | PCLIENT_ID | out | Receives the (PID, TID) pair identifying the newly created thread. |
| ThreadContext | PCONTEXT | in | Fully populated CONTEXT structure with RIP/RSP for the new thread — caller-supplied initial register state. |
| InitialTeb | PINITIAL_TEB | in | Manually-built INITIAL_TEB describing the user stack (base, limit, allocation base). The reason this API is painful. |
| CreateSuspended | BOOLEAN | in | If TRUE the thread is created in suspended state and must be resumed with NtResumeThread. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x4E | win10-1507 |
| Win10 1607 | 0x4E | win10-1607 |
| Win10 1703 | 0x4E | win10-1703 |
| Win10 1709 | 0x4E | win10-1709 |
| Win10 1803 | 0x4E | win10-1803 |
| Win10 1809 | 0x4E | win10-1809 |
| Win10 1903 | 0x4E | win10-1903 |
| Win10 1909 | 0x4E | win10-1909 |
| Win10 2004 | 0x4E | win10-2004 |
| Win10 20H2 | 0x4E | win10-20h2 |
| Win10 21H1 | 0x4E | win10-21h1 |
| Win10 21H2 | 0x4E | win10-21h2 |
| Win10 22H2 | 0x4E | win10-22h2 |
| Win11 21H2 | 0x4E | win11-21h2 |
| Win11 22H2 | 0x4E | win11-22h2 |
| Win11 23H2 | 0x4E | win11-23h2 |
| Win11 24H2 | 0x4E | win11-24h2 |
| Server 2016 | 0x4E | winserver-2016 |
| Server 2019 | 0x4E | winserver-2019 |
| Server 2022 | 0x4E | winserver-2022 |
| Server 2025 | 0x4E | winserver-2025 |
Kernel module
Related APIs
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 ENDPcLegacy 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