NtCreateThread
Legacy-Syscall zur Thread-Erzeugung mit manuell aufgebauter INITIAL_TEB; abgelöst durch NtCreateThreadEx.
Prototyp
NTSTATUS NtCreateThread( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PCLIENT_ID ClientId, PCONTEXT ThreadContext, PINITIAL_TEB InitialTeb, BOOLEAN CreateSuspended );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadHandle | PHANDLE | out | Erhält das Handle des neu erzeugten Threads bei Erfolg. |
| DesiredAccess | ACCESS_MASK | in | Zugriffsrechte für das zurückgegebene Thread-Handle, typischerweise THREAD_ALL_ACCESS. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Optionale Objektattribute; bei der Thread-Erzeugung fast immer NULL. |
| ProcessHandle | HANDLE | in | Handle auf den Zielprozess, dem der Thread gehören wird (PROCESS_CREATE_THREAD erforderlich). |
| ClientId | PCLIENT_ID | out | Erhält das (PID, TID)-Paar zur Identifikation des neuen Threads. |
| ThreadContext | PCONTEXT | in | Vollständig gefüllte CONTEXT-Struktur mit RIP/RSP für den neuen Thread — vom Aufrufer bereitgestellter Registerzustand. |
| InitialTeb | PINITIAL_TEB | in | Manuell aufgebaute INITIAL_TEB, die den User-Stack beschreibt (Base, Limit, Allocation Base). Der Grund, warum diese API mühsam ist. |
| CreateSuspended | BOOLEAN | in | Bei TRUE wird der Thread suspendiert erzeugt und muss mit NtResumeThread fortgesetzt werden. |
Syscall-IDs pro 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-Modul
Verwandte 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
Undokumentierte Hinweise
NtCreateThread ist die ursprüngliche, vor Vista eingeführte Thread-Erzeugungsprimitive. Im Gegensatz zu ihrem Nachfolger NtCreateThreadEx ist sie in den öffentlichen Headern undokumentiert und zwingt den Aufrufer, eine INITIAL_TEB von Hand aufzubauen — mit Stack-Base, Stack-Limit und Allocation-Base — sowie einen vollständig gefüllten CONTEXT mit RIP/RSP. Die SSN `0x4E` ist über alle unterstützten Windows 10- und Windows 11-Builds stabil geblieben, was ungewöhnlich ist und widerspiegelt, dass Microsoft an dieser Oberfläche nichts mehr ändert. Der Kernel implementiert sie weiterhin aus Kompatibilitätsgründen, doch Win32 CreateThread, RtlCreateUserThread und NtCreateThreadEx dispatchen heute alle über NtCreateThreadEx.
Häufige Malware-Nutzung
Verwendet von älteren Injection-Tools, die vor den Attribute-Listen von NtCreateThreadEx entstanden — einige PassTheHash-Varianten, frühe Token-Stealing-Primitiven von Mimikatz und historische Alternativen zu CreateRemoteThread. Eine kleine Untermenge moderner Offensiv-Tools wählt NtCreateThread bewusst, weil so wenige EDRs sie hooken: die API wird von legitimer Software so selten genutzt, dass die Herstellerabdeckung lückenhaft ist und sie zu einem Nischen-Bypass gegen naive Userland-Hook-Stacks wird. Der Preis ist, dass der Aufrufer im Zielprozess einen Stack allokieren und zusammenfügen muss, bevor er den Aufruf absetzt.
Erkennungsmöglichkeiten
ETW Microsoft-Windows-Threat-Intelligence emittiert ThreadCreate-Events aus PspInsertThread heraus, unabhängig davon, ob der Userland-Einstiegspunkt NtCreateThread oder NtCreateThreadEx war — die kernelseitige Telemetrie ist also identisch. Sysmon Event ID 8 (CreateRemoteThread) feuert ebenfalls bei jeder prozessübergreifenden Thread-Injektion. Der einzige sinnvolle Diskriminator liegt im Userland: ein EDR, das nur ntdll!NtCreateThreadEx hookt, übersieht Pfade über NtCreateThread. Verteidiger sollten beide Stubs instrumentieren und die seltene Kombination NtCreateThread + Remote-ProcessHandle beobachten.
Direkte Syscall-Beispiele
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