> Windows Syscalls
ntoskrnl.exeT1134.004T1106T1055

NtCreateUserProcess

Creates a new user-mode process and its initial thread from an executable image.

Prototype

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
);

Arguments

NameTypeDirDescription
ProcessHandlePHANDLEoutReceives a handle to the new process object.
ThreadHandlePHANDLEoutReceives a handle to the new process's primary thread.
ProcessDesiredAccessACCESS_MASKinDesired access mask for the returned process handle (e.g. PROCESS_ALL_ACCESS).
ThreadDesiredAccessACCESS_MASKinDesired access mask for the returned thread handle (e.g. THREAD_ALL_ACCESS).
ProcessObjectAttributesPOBJECT_ATTRIBUTESinOptional OBJECT_ATTRIBUTES for the process object (name, security descriptor, root directory).
ThreadObjectAttributesPOBJECT_ATTRIBUTESinOptional OBJECT_ATTRIBUTES for the primary thread object.
ProcessFlagsULONGinPROCESS_CREATE_FLAGS_* (BREAKAWAY, INHERIT_HANDLES, NO_DEBUG_INHERIT, SUSPENDED, ...).
ThreadFlagsULONGinTHREAD_CREATE_FLAGS_* (CREATE_SUSPENDED, SKIP_THREAD_ATTACH, HIDE_FROM_DEBUGGER, ...).
ProcessParametersPRTL_USER_PROCESS_PARAMETERSinParameter block built by RtlCreateProcessParametersEx (image path, command line, env, std handles).
CreateInfoPPS_CREATE_INFOin/outPS_CREATE_INFO state machine: in PsCreateInitialState, returns final state plus opened image section / file handles.
AttributeListPPS_ATTRIBUTE_LISTinArray of PS_ATTRIBUTE entries: ImageName, ParentProcess, MitigationOptions, JobList, Token, ChildProcessPolicy, ...

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070xBAwin10-1507
Win10 16070xBDwin10-1607
Win10 17030xC0win10-1703
Win10 17090xC1win10-1709
Win10 18030xC2win10-1803
Win10 18090xC3win10-1809
Win10 19030xC4win10-1903
Win10 19090xC4win10-1909
Win10 20040xC8win10-2004
Win10 20H20xC8win10-20h2
Win10 21H10xC8win10-21h1
Win10 21H20xC9win10-21h2
Win10 22H20xC9win10-22h2
Win11 21H20xCEwin11-21h2
Win11 22H20xCFwin11-22h2
Win11 23H20xCFwin11-23h2
Win11 24H20xD1win11-24h2
Server 20160xBDwinserver-2016
Server 20190xC3winserver-2019
Server 20220xCDwinserver-2022
Server 20250xD1winserver-2025

Kernel module

ntoskrnl.exeNtCreateUserProcess

Related APIs

CreateProcessWCreateProcessAsUserWCreateProcessWithTokenWCreateProcessWithLogonWNtCreateProcessNtCreateProcessExRtlCreateProcessParametersEx

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

Undocumented notes

NtCreateUserProcess is the modern kernel entry point underneath every Win32 CreateProcess* family call since Vista. It replaces the legacy NtCreateProcess / NtCreateProcessEx + NtCreateThread + LdrLoadDll dance with a single kernel transaction that opens the image, builds the section, primes the PEB, applies mitigations, and starts the initial thread. The SSN drifts almost every build (0xBA → 0xD1 over the supported window) because adjacent syscalls are inserted or removed; dynamic resolution is mandatory. The 11-parameter prototype is dominated by two large structures: PS_CREATE_INFO (a multi-state machine the kernel walks while it parses the image) and PS_ATTRIBUTE_LIST (the carrier for everything the legacy `CreateProcess` lpStartupInfo / lpProcessAttributes / Token / Job parameters used to convey separately).

Common malware usage

Two headline abuses. **(1) PPID spoofing (T1134.004)**: set PS_ATTRIBUTE_PARENT_PROCESS in AttributeList to a duplicated handle of any process you can OpenProcess(PROCESS_CREATE_PROCESS). The child appears under that parent in Process Explorer, Sysmon Event ID 1, and EDR process trees — a classic way to hide a Beacon under explorer.exe or services.exe. **(2) Mitigation downgrade / child-process policy abuse**: PS_ATTRIBUTE_MITIGATION_OPTIONS lets the caller *enable* mitigations on the child (block-non-Microsoft-DLLs to stop EDR injection into a helper process is a documented red-team trick) or pair with PS_ATTRIBUTE_CHILD_PROCESS_POLICY to assert no further children, complicating analysis. A subtler third use: calling NtCreateUserProcess directly skips the kernel32!CreateProcessInternalW wrapper, which is where many EDRs hang their user-mode telemetry hook.

Detection opportunities

Kernel-mode telemetry is excellent here. PsSetCreateProcessNotifyRoutineEx2 fires on every successful return with PS_CREATE_NOTIFY_INFO that includes the *real* parent PID (`CreatingThreadId.UniqueProcess`) regardless of any PS_ATTRIBUTE_PARENT_PROCESS spoof. Sysmon Event ID 1 records both `ParentProcessGuid` (from PEB ProcessParameters) and the kernel-derived creating-process — diverging values are a high-fidelity spoof signal. ETW `Microsoft-Windows-Kernel-Process/ProcessStart` events surface the same fields. EDRs hooking only CreateProcessInternalW or NtCreateProcessEx will miss direct-syscall callers — verify both surfaces.

Direct syscall examples

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 ENDP

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