NtCreateUserProcess
Erzeugt einen neuen User-Mode-Prozess und dessen initialen Thread aus einem Executable-Image.
Prototyp
NTSTATUS NtCreateUserProcess( PHANDLE ProcessHandle, PHANDLE ThreadHandle, ACCESS_MASK ProcessDesiredAccess, ACCESS_MASK ThreadDesiredAccess, POBJECT_ATTRIBUTES ProcessObjectAttributes, POBJECT_ATTRIBUTES ThreadObjectAttributes, ULONG ProcessFlags, ULONG ThreadFlags, PRTL_USER_PROCESS_PARAMETERS ProcessParameters, PPS_CREATE_INFO CreateInfo, PPS_ATTRIBUTE_LIST AttributeList );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | PHANDLE | out | Erhält ein Handle auf das neue Prozessobjekt. |
| ThreadHandle | PHANDLE | out | Erhält ein Handle auf den Haupt-Thread des neuen Prozesses. |
| ProcessDesiredAccess | ACCESS_MASK | in | Gewünschte Zugriffsmaske für das zurückgegebene Prozess-Handle (z. B. PROCESS_ALL_ACCESS). |
| ThreadDesiredAccess | ACCESS_MASK | in | Gewünschte Zugriffsmaske für das zurückgegebene Thread-Handle (z. B. THREAD_ALL_ACCESS). |
| ProcessObjectAttributes | POBJECT_ATTRIBUTES | in | Optionale OBJECT_ATTRIBUTES für das Prozessobjekt (Name, Security-Descriptor, Root-Verzeichnis). |
| ThreadObjectAttributes | POBJECT_ATTRIBUTES | in | Optionale OBJECT_ATTRIBUTES für das primäre Thread-Objekt. |
| ProcessFlags | ULONG | in | PROCESS_CREATE_FLAGS_* (BREAKAWAY, INHERIT_HANDLES, NO_DEBUG_INHERIT, SUSPENDED, ...). |
| ThreadFlags | ULONG | in | THREAD_CREATE_FLAGS_* (CREATE_SUSPENDED, SKIP_THREAD_ATTACH, HIDE_FROM_DEBUGGER, ...). |
| ProcessParameters | PRTL_USER_PROCESS_PARAMETERS | in | Parameterblock von RtlCreateProcessParametersEx (Image-Pfad, Kommandozeile, Environment, Standard-Handles). |
| CreateInfo | PPS_CREATE_INFO | in/out | PS_CREATE_INFO-Zustandsmaschine: Eingabe PsCreateInitialState, Ausgabe Endzustand plus geöffnete Image-Section-/Datei-Handles. |
| AttributeList | PPS_ATTRIBUTE_LIST | in | Array von PS_ATTRIBUTE-Einträgen: ImageName, ParentProcess, MitigationOptions, JobList, Token, ChildProcessPolicy, ... |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0xBA | win10-1507 |
| Win10 1607 | 0xBD | win10-1607 |
| Win10 1703 | 0xC0 | win10-1703 |
| Win10 1709 | 0xC1 | win10-1709 |
| Win10 1803 | 0xC2 | win10-1803 |
| Win10 1809 | 0xC3 | win10-1809 |
| Win10 1903 | 0xC4 | win10-1903 |
| Win10 1909 | 0xC4 | win10-1909 |
| Win10 2004 | 0xC8 | win10-2004 |
| Win10 20H2 | 0xC8 | win10-20h2 |
| Win10 21H1 | 0xC8 | win10-21h1 |
| Win10 21H2 | 0xC9 | win10-21h2 |
| Win10 22H2 | 0xC9 | win10-22h2 |
| Win11 21H2 | 0xCE | win11-21h2 |
| Win11 22H2 | 0xCF | win11-22h2 |
| Win11 23H2 | 0xCF | win11-23h2 |
| Win11 24H2 | 0xD1 | win11-24h2 |
| Server 2016 | 0xBD | winserver-2016 |
| Server 2019 | 0xC3 | winserver-2019 |
| Server 2022 | 0xCD | winserver-2022 |
| Server 2025 | 0xD1 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 D1 00 00 00 mov eax, 0xD1 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
NtCreateUserProcess ist seit Vista der moderne Kernel-Entrypoint unter der gesamten Win32-CreateProcess*-Familie. Es ersetzt die Legacy-Choreographie NtCreateProcess / NtCreateProcessEx + NtCreateThread + LdrLoadDll durch eine einzelne Kerneltransaktion, die das Image öffnet, die Section baut, das PEB primiert, Mitigations anwendet und den Initial-Thread startet. Die SSN driftet fast bei jedem Build (0xBA → 0xD1 im unterstützten Fenster), weil benachbarte Syscalls eingefügt oder entfernt werden; dynamische Auflösung ist Pflicht. Der 11-Parameter-Prototyp wird von zwei großen Strukturen dominiert: PS_CREATE_INFO (Zustandsmaschine, die der Kernel beim Image-Parsing durchläuft) und PS_ATTRIBUTE_LIST (Träger für alles, was die Legacy-`CreateProcess`-Parameter lpStartupInfo / lpProcessAttributes / Token / Job einzeln transportiert haben).
Häufige Malware-Nutzung
Zwei prominente Missbräuche. **(1) PPID-Spoofing (T1134.004)**: PS_ATTRIBUTE_PARENT_PROCESS in der AttributeList auf ein dupliziertes Handle eines beliebigen mit OpenProcess(PROCESS_CREATE_PROCESS) erreichbaren Prozesses setzen. Das Kind erscheint in Process Explorer, Sysmon Event ID 1 und EDR-Prozessbäumen unter diesem Parent — Klassiker, um einen Beacon unter explorer.exe oder services.exe zu verstecken. **(2) Mitigation-Downgrade / Child-Process-Policy-Missbrauch**: PS_ATTRIBUTE_MITIGATION_OPTIONS erlaubt es, Mitigations am Kind zu *aktivieren* (Block-Non-Microsoft-DLLs, um EDR-Injection in einen Helfer-Prozess zu verhindern, ist ein dokumentierter Red-Team-Trick) oder kombiniert mit PS_ATTRIBUTE_CHILD_PROCESS_POLICY weitere Kinder zu verbieten und Analyse zu erschweren. Subtiler dritter Effekt: NtCreateUserProcess direkt aufzurufen umgeht kernel32!CreateProcessInternalW, wo viele EDRs ihre User-Mode-Telemetrie-Hook platzieren.
Erkennungsmöglichkeiten
Kernel-Telemetrie ist hier exzellent. PsSetCreateProcessNotifyRoutineEx2 feuert bei jedem erfolgreichen Return mit einem PS_CREATE_NOTIFY_INFO, das die *echte* Parent-PID enthält (`CreatingThreadId.UniqueProcess`), unabhängig von jedem PS_ATTRIBUTE_PARENT_PROCESS-Spoof. Sysmon Event ID 1 zeichnet sowohl `ParentProcessGuid` (aus PEB-ProcessParameters) als auch den kernelseitig ermittelten Erzeuger auf — abweichende Werte sind ein hochauflösendes Spoof-Signal. Die ETW-Events `Microsoft-Windows-Kernel-Process/ProcessStart` exponieren dieselben Felder. EDRs, die nur CreateProcessInternalW oder NtCreateProcessEx hooken, verpassen Direct-Syscall-Aufrufer — beide Flächen prüfen.
Direkte Syscall-Beispiele
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtCreateUserProcess (SSN 0xD1 on 24H2/2025)
; SSN moves nearly every release — resolve dynamically.
NtCreateUserProcess PROC
mov r10, rcx ; syscall convention
mov eax, 0D1h ; SSN
syscall
ret
NtCreateUserProcess ENDPcPPID-spoof skeleton
// Minimal PPID-spoof skeleton: launch notepad.exe as a child of explorer.exe.
// Error handling and full PS_ATTRIBUTE / RTL_USER_PROCESS_PARAMETERS setup omitted for brevity.
#include <windows.h>
#include <winternl.h>
#define PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 0x00020000
void SpoofParent(DWORD parent_pid) {
SIZE_T attr_size = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &attr_size);
LPPROC_THREAD_ATTRIBUTE_LIST attr =
(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attr_size);
InitializeProcThreadAttributeList(attr, 1, 0, &attr_size);
HANDLE parent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, parent_pid);
UpdateProcThreadAttribute(attr, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&parent, sizeof(HANDLE), NULL, NULL);
STARTUPINFOEXW si = { 0 };
si.StartupInfo.cb = sizeof si;
si.lpAttributeList = attr;
PROCESS_INFORMATION pi;
// CreateProcess wraps NtCreateUserProcess; the AttributeList carries
// PS_ATTRIBUTE_PARENT_PROCESS through to the kernel.
CreateProcessW(L"C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL,
FALSE, EXTENDED_STARTUPINFO_PRESENT,
NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
DeleteProcThreadAttributeList(attr);
CloseHandle(parent);
}rustblock-non-Microsoft-DLLs mitigation
// Cargo: windows-sys = "0.59"
// Launch a helper process with the Microsoft-signed-DLLs-only mitigation —
// blocks EDR userland DLLs from being injected into the child.
use windows_sys::Win32::System::Threading::*;
use windows_sys::core::PCWSTR;
const PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY: usize = 0x00020007;
const PROCESS_CREATION_MITIGATION_POLICY2_LOADER_INTEGRITY_CONTINUITY_ALWAYS_ON: u64
= 0x0000_0001_0000_0000;
pub unsafe fn launch_locked_down(image: PCWSTR) {
let mut attr_size = 0usize;
InitializeProcThreadAttributeList(core::ptr::null_mut(), 1, 0, &mut attr_size);
let attr = std::alloc::alloc(
std::alloc::Layout::from_size_align(attr_size, 8).unwrap()) as *mut _;
InitializeProcThreadAttributeList(attr, 1, 0, &mut attr_size);
let mut policy: u64 =
PROCESS_CREATION_MITIGATION_POLICY2_LOADER_INTEGRITY_CONTINUITY_ALWAYS_ON;
UpdateProcThreadAttribute(attr, 0,
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
&mut policy as *mut _ as _, 8,
core::ptr::null_mut(), core::ptr::null_mut());
let mut si: STARTUPINFOEXW = core::mem::zeroed();
si.StartupInfo.cb = core::mem::size_of_val(&si) as u32;
si.lpAttributeList = attr;
let mut pi: PROCESS_INFORMATION = core::mem::zeroed();
CreateProcessW(image, core::ptr::null_mut(), core::ptr::null(),
core::ptr::null(), 0, EXTENDED_STARTUPINFO_PRESENT,
core::ptr::null(), core::ptr::null(),
&si as *const _ as _, &mut pi);
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20