NtCreateThread
Syscall heredado de creación de hilos que requiere un INITIAL_TEB construido manualmente; sustituido por NtCreateThreadEx.
Prototipo
NTSTATUS NtCreateThread( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PCLIENT_ID ClientId, PCONTEXT ThreadContext, PINITIAL_TEB InitialTeb, BOOLEAN CreateSuspended );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadHandle | PHANDLE | out | Recibe el handle del hilo recién creado si tiene éxito. |
| DesiredAccess | ACCESS_MASK | in | Derechos de acceso para el handle de hilo devuelto, normalmente THREAD_ALL_ACCESS. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Atributos de objeto opcionales; casi siempre NULL para la creación de hilos. |
| ProcessHandle | HANDLE | in | Handle al proceso objetivo que poseerá el hilo (PROCESS_CREATE_THREAD requerido). |
| ClientId | PCLIENT_ID | out | Recibe el par (PID, TID) que identifica al hilo recién creado. |
| ThreadContext | PCONTEXT | in | Estructura CONTEXT totalmente poblada con RIP/RSP para el nuevo hilo — estado inicial de registros provisto por el llamador. |
| InitialTeb | PINITIAL_TEB | in | INITIAL_TEB construido manualmente que describe la pila de usuario (base, límite, base de asignación). Por eso esta API es engorrosa. |
| CreateSuspended | BOOLEAN | in | Si TRUE el hilo se crea suspendido y debe reanudarse con NtResumeThread. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | 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 |
Módulo del kernel
APIs relacionadas
Stub del syscall
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
Notas no documentadas
NtCreateThread es la primitiva original de creación de hilos, anterior a Vista. A diferencia de su sucesor NtCreateThreadEx, no está documentada en los encabezados públicos y obliga al llamador a construir manualmente un INITIAL_TEB — describiendo base, límite y base de asignación de la pila — más un CONTEXT totalmente poblado con RIP/RSP. El SSN `0x4E` ha permanecido estable en todas las builds soportadas de Windows 10 y Windows 11, lo cual es inusual y refleja que Microsoft ya no toca esta superficie. El kernel sigue implementándola por compatibilidad pero hoy CreateThread, RtlCreateUserThread y NtCreateThreadEx pasan todos por NtCreateThreadEx.
Uso común por malware
Usada por herramientas de inyección antiguas anteriores a las listas de atributos de NtCreateThreadEx — algunas variantes de PassTheHash, las primeras primitivas de robo de token de Mimikatz y alternativas históricas a CreateRemoteThread. Una pequeña parte del herramental ofensivo moderno elige NtCreateThread precisamente porque pocos EDR la hookean: la API se invoca tan rara vez por código legítimo que la cobertura de los proveedores es irregular, lo que la convierte en un bypass de nicho contra pilas de hooks de userland ingenuas. La contrapartida es que el llamador debe asignar y montar una pila en el proceso objetivo antes de emitir la llamada.
Oportunidades de detección
ETW Microsoft-Windows-Threat-Intelligence emite eventos ThreadCreate desde PspInsertThread sin importar si el punto de entrada de userland fue NtCreateThread o NtCreateThreadEx — la telemetría a nivel kernel es idéntica. Sysmon Event ID 8 (CreateRemoteThread) también se dispara para cualquier inyección de hilo entre procesos. El único discriminador útil está en userland: un EDR que solo hookee ntdll!NtCreateThreadEx perderá las rutas que pasan por NtCreateThread. Los defensores deben asegurarse de instrumentar ambos stubs y vigilar la rara combinación de NtCreateThread + ProcessHandle remoto.
Ejemplos de syscalls directos
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),
);
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20