> Windows Syscalls
ntoskrnl.exeT1055.012T1055T1106

NtCreateProcess

Creación de proceso NT heredada desde un objeto section preconstruido — predecesor de NtCreateProcessEx y NtCreateUserProcess.

Prototipo

NTSTATUS NtCreateProcess(
  PHANDLE            ProcessHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes,
  HANDLE             ParentProcess,
  BOOLEAN            InheritObjectTable,
  HANDLE             SectionHandle,
  HANDLE             DebugPort,
  HANDLE             ExceptionPort
);

Argumentos

NameTypeDirDescription
ProcessHandlePHANDLEoutRecibe el handle al proceso recién creado.
DesiredAccessACCESS_MASKinMáscara de acceso al proceso solicitada (PROCESS_ALL_ACCESS para control total).
ObjectAttributesPOBJECT_ATTRIBUTESinNombre del Object Manager y descriptor de seguridad opcionales.
ParentProcessHANDLEinHandle al proceso padre. PEB y handles heredables se clonan de él.
InheritObjectTableBOOLEANinTRUE para heredar la tabla de handles del padre; FALSE para una tabla vacía.
SectionHandleHANDLEinHandle a una section SEC_IMAGE de imagen que será la imagen principal del proceso.
DebugPortHANDLEinPuerto de debug opcional (LPC/ALPC) que recibe eventos DBG; típicamente NULL.
ExceptionPortHANDLEinPuerto de excepción opcional para excepciones second-chance; típicamente NULL en Windows moderno.

IDs de syscalls por versión de Windows

Versión de WindowsID de syscallBuild
Win10 15070xADwin10-1507
Win10 16070xAFwin10-1607
Win10 17030xB2win10-1703
Win10 17090xB3win10-1709
Win10 18030xB4win10-1803
Win10 18090xB4win10-1809
Win10 19030xB5win10-1903
Win10 19090xB5win10-1909
Win10 20040xB9win10-2004
Win10 20H20xB9win10-20h2
Win10 21H10xB9win10-21h1
Win10 21H20xBAwin10-21h2
Win10 22H20xBAwin10-22h2
Win11 21H20xBDwin11-21h2
Win11 22H20xBEwin11-22h2
Win11 23H20xBEwin11-23h2
Win11 24H20xC0win11-24h2
Server 20160xAFwinserver-2016
Server 20190xB4winserver-2019
Server 20220xBCwinserver-2022
Server 20250xC0winserver-2025

Módulo del kernel

ntoskrnl.exeNtCreateProcess

APIs relacionadas

CreateProcessWCreateProcessAsUserWNtCreateProcessExNtCreateUserProcessNtCreateSectionNtCreateThreadCsrClientCallServer

Stub del syscall

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

El syscall *original* de creación de procesos NT — presente desde Windows NT 3.1. Es la primitiva de más bajo nivel: el llamante debe haber creado ya la section de imagen (`NtCreateSection(SEC_IMAGE, …)`), y el kernel solo construye el EPROCESS/KPROCESS, copia el esqueleto de PEB del padre y clona el espacio de direcciones. **No** crea el hilo inicial (debe seguir con `NtCreateThread`), **no** carga ninguna DLL más allá de la imagen principal, y **no** invoca a CSRSS para registrar el nuevo proceso — lo que significa que el proceso resultante **aún no es un proceso Win32**. Para obtener un proceso consola/Win32 visible debe llamar `CsrClientCallServer(BasepCreateProcess)` manualmente. `NtCreateProcessEx` (Windows XP+) añadió `JobMemberLevel` y la creación sin section; `NtCreateUserProcess` (Vista+) reemplazó ambos como la implementación one-shot de CreateProcess que maneja section, hilo, PEB, CSRSS, AppContainer y policies de mitigación en un único syscall. El `kernel32!CreateProcessW` moderno llama exclusivamente `NtCreateUserProcess` — `NtCreateProcess` solo es alcanzable por invocación directa.

Uso común por malware

Usado históricamente (pre-2010s) en cadenas de **process hollowing manual** que querían control máximo sobre la inicialización del PEB, bypass de policy de mitigación o evasión contra herramientas que hookeaban `CreateProcessW` pero no la capa NT inferior. El patrón canónico: `NtCreateSection(SEC_IMAGE)` sobre un binario objetivo benigno → `NtCreateProcess` con esa section como imagen → `NtCreateThread` en el entry point de la nueva imagen. Como el proceso resultante nunca habla con CSRSS, carece de una entrada en el subsistema consola, no aparece en `EnumProcesses` hasta notificación CSRSS, y en sistemas legacy era efectivamente invisible al Task Manager durante una breve ventana. Los callbacks modernos `Process Notify Routine` (`PsSetCreateProcessNotifyRoutineEx`) disparan desde `PspInsertProcess` independientemente del wrapper user-mode que llamó, así que el beneficio de evasión de visibilidad se acabó, y `NtCreateUserProcess` se prefiere universalmente. Hoy, los llamados a `NtCreateProcess` desde cualquier cosa salvo un puñado de binarios de test MS y ciertas emulaciones Cygwin/MSYS2 de `fork()` son esencialmente una huella de malware.

Oportunidades de detección

La señal más fuerte es *cualquier* invocación fuera de una allowlist conocida — `cygwin1.dll` de Cygwin, `msys-2.0.dll` de MSYS2, la capa LXSS de WSL1 y las herramientas de test del subsistema NT de Microsoft. Los callbacks kernel `PsSetCreateProcessNotifyRoutineEx` reciben un `PS_CREATE_NOTIFY_INFO` con `FileObject != NULL` solo cuando la imagen fue abierta por el kernel — los llamantes de `NtCreateProcess` proveen la section ellos mismos, así que el `ImageFileName` es el archivo fuente de la section y el contexto *llamante* dice quién la construyó. El evento ETW `Microsoft-Windows-Kernel-Process` ID 1 dispara uniformemente; correlacione el campo `CreateRoutine` (visible en el stack de kernel de la vista de hilos) para distinguir `NtCreateUserProcess` de `NtCreateProcessEx` / `NtCreateProcess`. Cualquier evento Sysmon 1 cuyo ParentImage sea inusual y cuyo `OriginalFileName` no coincida con `ImageFileName` es alta señal.

Ejemplos de syscalls directos

cMinimal legacy hollow-skeleton (NtCreateSection -> NtCreateProcess -> NtCreateThread)

// Minimal legacy-style hollow. Modern EDR sees the PspInsertProcess callback
// regardless, so this is documented for reverse-engineers, not as a working
// evasion in 2026.
#include <windows.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI* pNtCreateProcess)(
    PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, HANDLE, BOOLEAN,
    HANDLE, HANDLE, HANDLE);

void legacy_hollow(HANDLE hImageSection) {
    pNtCreateProcess NtCreateProcess = (pNtCreateProcess)GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtCreateProcess");

    HANDLE hProc = NULL;
    NTSTATUS st = NtCreateProcess(
        &hProc,
        PROCESS_ALL_ACCESS,
        NULL,
        NtCurrentProcess(),   // parent = self → inherits address-space template
        FALSE,                // do not inherit handles
        hImageSection,        // SEC_IMAGE backing
        NULL,                 // no debug port
        NULL);                // no exception port

    // At this point the process exists but has zero threads and has not been
    // announced to CSRSS. Caller now creates the initial thread at the image
    // entry point with NtCreateThread.
}

asmx64 direct stub (Win11 24H2 / Server 2025, SSN 0xC0)

NtCreateProcess PROC
    mov  r10, rcx
    mov  eax, 0C0h
    syscall
    ret
NtCreateProcess ENDP

rustResolve and call via Halo's Gate

// Hardcoded SSNs drift; resolve at runtime by walking ntdll exports.
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};

unsafe fn nt_create_process_ssn() -> u32 {
    let h = GetModuleHandleA(b"ntdll.dll\0".as_ptr());
    let p = GetProcAddress(h, b"NtCreateProcess\0".as_ptr()) as *const u8;
    // mov r10, rcx ; mov eax, IMM32 ; ...
    if p.read() == 0x4C && p.add(1).read() == 0x8B && p.add(2).read() == 0xD1 && p.add(3).read() == 0xB8 {
        u32::from_le_bytes([p.add(4).read(), p.add(5).read(), p.add(6).read(), p.add(7).read()])
    } else { 0 }
}

Mapeos MITRE ATT&CK

Last verified: 2026-05-20