NtCreateProcess
Legacy-NT-Prozesserzeugung aus einem vorab gebauten Section-Objekt — Vorgänger von NtCreateProcessEx und NtCreateUserProcess.
Prototyp
NTSTATUS NtCreateProcess( PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, BOOLEAN InheritObjectTable, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | PHANDLE | out | Empfängt das Handle auf den neu erstellten Prozess. |
| DesiredAccess | ACCESS_MASK | in | Angeforderte Prozess-Zugriffsmaske (PROCESS_ALL_ACCESS für vollen Zugriff). |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Optionaler Object-Manager-Name und Security-Descriptor. |
| ParentProcess | HANDLE | in | Handle auf den Eltern-Prozess. PEB und vererbbare Handles werden von ihm geklont. |
| InheritObjectTable | BOOLEAN | in | TRUE, um die Handle-Tabelle des Elternprozesses zu erben; FALSE für eine leere Tabelle. |
| SectionHandle | HANDLE | in | Handle auf eine SEC_IMAGE-Image-Section, die zum primären Image des Prozesses wird. |
| DebugPort | HANDLE | in | Optionaler Debug-Port (LPC/ALPC), der DBG-Events empfängt; meist NULL. |
| ExceptionPort | HANDLE | in | Optionaler Exception-Port für Second-Chance-Exceptions; auf modernem Windows meist NULL. |
Syscall-IDs pro 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-Modul
Verwandte 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
Undokumentierte Hinweise
Der *originale* NT-Prozesserzeugungs-Syscall — seit Windows NT 3.1 vorhanden. Es ist die niedrigststufige Primitive: der Aufrufer muss bereits die Image-Section erstellt haben (`NtCreateSection(SEC_IMAGE, …)`), und der Kernel baut nur das EPROCESS/KPROCESS, kopiert das PEB-Skelett des Elternteils und klont den Adressraum. Er erzeugt **keinen** Initial-Thread (Folge-Aufruf mit `NtCreateThread` erforderlich), lädt **keine** DLL über das Hauptimage hinaus und ruft **kein** CSRSS, um den neuen Prozess zu registrieren — der entstehende Prozess ist also **noch kein Win32-Prozess**. Um einen Konsolen-/Win32-sichtbaren Prozess zu bekommen, muss man `CsrClientCallServer(BasepCreateProcess)` manuell aufrufen. `NtCreateProcessEx` (Windows XP+) ergänzte `JobMemberLevel` und Section-lose Erzeugung; `NtCreateUserProcess` (Vista+) ersetzte beide als One-Shot-CreateProcess-Implementierung, die Section, Thread, PEB, CSRSS, AppContainer und Mitigation-Policies in einem einzigen Syscall erledigt. Das moderne `kernel32!CreateProcessW` ruft ausschließlich `NtCreateUserProcess` — `NtCreateProcess` ist nur durch direkten Aufruf erreichbar.
Häufige Malware-Nutzung
Historisch (vor 2010ern) in **manuellen Process-Hollowing**-Ketten genutzt, die maximale Kontrolle über PEB-Initialisierung, Mitigation-Policy-Bypass oder Evasion gegen Werkzeuge wollten, die `CreateProcessW` hookten, aber nicht die untere NT-Schicht. Das kanonische Muster: `NtCreateSection(SEC_IMAGE)` auf einer harmlosen Zielbinary → `NtCreateProcess` mit dieser Section als Image → `NtCreateThread` am Entry-Point des neuen Images. Weil der entstehende Prozess nie mit CSRSS spricht, fehlt ihm ein Konsolen-Subsystem-Eintrag, er erscheint nicht in `EnumProcesses`, bis die CSRSS-Benachrichtigung folgt, und auf Legacy-Systemen war er für ein kurzes Zeitfenster effektiv unsichtbar für den Task Manager. Moderne `Process Notify Routine`-Callbacks (`PsSetCreateProcessNotifyRoutineEx`) feuern aus `PspInsertProcess` unabhängig vom User-Mode-Wrapper, der Sichtbarkeits-Evasion-Vorteil ist also weg, und `NtCreateUserProcess` wird universell bevorzugt. Heute sind Aufrufe von `NtCreateProcess` aus anderem als einer Handvoll MS-Testbinaries und gewissen Cygwin-/MSYS2-`fork()`-Emulationen im Wesentlichen ein Malware-Fingerprint.
Erkennungsmöglichkeiten
Das stärkste Einzelsignal ist *jede* Invokation außerhalb einer bekannten Allowlist — Cygwins `cygwin1.dll`, MSYS2 `msys-2.0.dll`, die WSL1-LXSS-Schicht und Microsofts eigene NT-Subsystem-Testtools. Kernel-Callbacks `PsSetCreateProcessNotifyRoutineEx` erhalten ein `PS_CREATE_NOTIFY_INFO` mit `FileObject != NULL` nur, wenn das Image vom Kernel geöffnet wurde — Aufrufer von `NtCreateProcess` liefern die Section selbst, daher ist `ImageFileName` die Quelldatei der Section und der *aufrufende* Kontext sagt, wer sie gebaut hat. ETW `Microsoft-Windows-Kernel-Process` Event ID 1 feuert einheitlich; korrelieren Sie das `CreateRoutine`-Feld (im Kernel-Stack der Thread-Ansicht sichtbar), um `NtCreateUserProcess` von `NtCreateProcessEx` / `NtCreateProcess` zu unterscheiden. Jedes Sysmon-Event 1, dessen ParentImage ungewöhnlich ist und dessen `OriginalFileName` nicht mit `ImageFileName` übereinstimmt, ist ein starkes Signal.
Direkte Syscall-Beispiele
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