> Windows Syscalls
ntoskrnl.exeT1055.012T1055T1106

NtCreateProcess

Legacy NT-style process creation from a pre-built section object — predecessor of NtCreateProcessEx and NtCreateUserProcess.

Prototype

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

Arguments

NameTypeDirDescription
ProcessHandlePHANDLEoutReceives the handle to the newly created process.
DesiredAccessACCESS_MASKinRequested process access mask (PROCESS_ALL_ACCESS for full control).
ObjectAttributesPOBJECT_ATTRIBUTESinOptional object-manager name and security descriptor.
ParentProcessHANDLEinHandle to the parent process. PEB and inheritable handles are cloned from it.
InheritObjectTableBOOLEANinTRUE to inherit the parent's handle table; FALSE for an empty table.
SectionHandleHANDLEinHandle to an image SEC_IMAGE section that will become the process's primary image.
DebugPortHANDLEinOptional debug port (LPC/ALPC) that receives DBG events; typically NULL.
ExceptionPortHANDLEinOptional exception port for second-chance exceptions; typically NULL on modern Windows.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
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

Kernel module

ntoskrnl.exeNtCreateProcess

Related APIs

CreateProcessWCreateProcessAsUserWNtCreateProcessExNtCreateUserProcessNtCreateSectionNtCreateThreadCsrClientCallServer

Syscall stub

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

Undocumented notes

The *original* NT process-creation syscall — present since Windows NT 3.1. It is the lowest-level primitive: the caller is required to have already created the image section (`NtCreateSection(SEC_IMAGE, …)`), and the kernel only constructs the EPROCESS/KPROCESS, copies the parent's PEB skeleton, and clones the address space. It does **not** create the initial thread (you must follow up with `NtCreateThread`), it does **not** load any DLL beyond the main image, and it does **not** invoke CSRSS to register the new process — meaning the resulting process is **not yet a Win32 process**. To get a console/Win32-visible process you must call `CsrClientCallServer(BasepCreateProcess)` manually. `NtCreateProcessEx` (Windows XP+) added `JobMemberLevel` and section-less creation; `NtCreateUserProcess` (Vista+) replaced both as the one-shot CreateProcess implementation that handles section, thread, PEB, CSRSS, AppContainer and mitigation policies in a single syscall. Modern `kernel32!CreateProcessW` calls `NtCreateUserProcess` exclusively — `NtCreateProcess` is reachable only via direct invocation.

Common malware usage

Used historically (pre-2010s) in **manual process hollowing** chains that wanted maximum control over PEB initialisation, mitigation-policy bypass, or detection-evasion against tools that hooked `CreateProcessW` but not the lower NT layer. The canonical pattern: `NtCreateSection(SEC_IMAGE)` on a benign target binary → `NtCreateProcess` with that section as the image → `NtCreateThread` at the new image entry point. Because the resulting process never talks to CSRSS, it lacks a console subsystem entry, doesn't appear in `EnumProcesses` until CSRSS notification, and on legacy systems was effectively invisible to Task Manager for a brief window. Modern `Process Notify Routine` callbacks (`PsSetCreateProcessNotifyRoutineEx`) fire from within `PspInsertProcess` regardless of which user-mode wrapper called it, so the visibility-evasion benefit is gone, and `NtCreateUserProcess` is universally preferred. Today, calls to `NtCreateProcess` from anything except a handful of MS test binaries and certain Cygwin/MSYS2 `fork()` emulations are essentially a malware fingerprint.

Detection opportunities

The single strongest signal is *any* invocation outside a known allowlist — Cygwin's `cygwin1.dll`, MSYS2 `msys-2.0.dll`, the WSL1 LXSS layer, and Microsoft's own NT subsystem test tools. Kernel `PsSetCreateProcessNotifyRoutineEx` callbacks receive a `PS_CREATE_NOTIFY_INFO` with `FileObject != NULL` only when the image was opened by the kernel — `NtCreateProcess` callers supply the section themselves, so the `ImageFileName` is the section's source file and the *caller* context tells you who built it. ETW `Microsoft-Windows-Kernel-Process` Event ID 1 fires uniformly; correlate the `CreateRoutine` field (visible in the kernel call stack of the threaded view) to distinguish `NtCreateUserProcess` from `NtCreateProcessEx` / `NtCreateProcess`. Any sysmon Event 1 whose ParentImage is unusual and whose `OriginalFileName` does not match `ImageFileName` is high signal.

Direct syscall examples

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 }
}

MITRE ATT&CK mappings

Last verified: 2026-05-20