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
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | PHANDLE | out | Receives the handle to the newly created process. |
| DesiredAccess | ACCESS_MASK | in | Requested process access mask (PROCESS_ALL_ACCESS for full control). |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Optional object-manager name and security descriptor. |
| ParentProcess | HANDLE | in | Handle to the parent process. PEB and inheritable handles are cloned from it. |
| InheritObjectTable | BOOLEAN | in | TRUE to inherit the parent's handle table; FALSE for an empty table. |
| SectionHandle | HANDLE | in | Handle to an image SEC_IMAGE section that will become the process's primary image. |
| DebugPort | HANDLE | in | Optional debug port (LPC/ALPC) that receives DBG events; typically NULL. |
| ExceptionPort | HANDLE | in | Optional exception port for second-chance exceptions; typically NULL on modern Windows. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0xAD | win10-1507 |
| Win10 1607 | 0xAF | win10-1607 |
| Win10 1703 | 0xB2 | win10-1703 |
| Win10 1709 | 0xB3 | win10-1709 |
| Win10 1803 | 0xB4 | win10-1803 |
| Win10 1809 | 0xB4 | win10-1809 |
| Win10 1903 | 0xB5 | win10-1903 |
| Win10 1909 | 0xB5 | win10-1909 |
| Win10 2004 | 0xB9 | win10-2004 |
| Win10 20H2 | 0xB9 | win10-20h2 |
| Win10 21H1 | 0xB9 | win10-21h1 |
| Win10 21H2 | 0xBA | win10-21h2 |
| Win10 22H2 | 0xBA | win10-22h2 |
| Win11 21H2 | 0xBD | win11-21h2 |
| Win11 22H2 | 0xBE | win11-22h2 |
| Win11 23H2 | 0xBE | win11-23h2 |
| Win11 24H2 | 0xC0 | win11-24h2 |
| Server 2016 | 0xAF | winserver-2016 |
| Server 2019 | 0xB4 | winserver-2019 |
| Server 2022 | 0xBC | winserver-2022 |
| Server 2025 | 0xC0 | winserver-2025 |
Kernel module
Related APIs
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 ENDPrustResolve 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