> Windows Syscalls
ntoskrnl.exeT1134.004T1106T1055

NtCreateUserProcess

Crée un nouveau processus en mode utilisateur et son thread initial à partir d'une image exécutable.

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
ProcessHandlePHANDLEoutReçoit un handle vers le nouvel objet processus.
ThreadHandlePHANDLEoutReçoit un handle vers le thread principal du nouveau processus.
ProcessDesiredAccessACCESS_MASKinMasque d'accès désiré pour le handle de processus retourné (ex. PROCESS_ALL_ACCESS).
ThreadDesiredAccessACCESS_MASKinMasque d'accès désiré pour le handle de thread retourné (ex. THREAD_ALL_ACCESS).
ProcessObjectAttributesPOBJECT_ATTRIBUTESinOBJECT_ATTRIBUTES optionnels pour l'objet processus (nom, descripteur de sécurité, répertoire racine).
ThreadObjectAttributesPOBJECT_ATTRIBUTESinOBJECT_ATTRIBUTES optionnels pour l'objet thread principal.
ProcessFlagsULONGinPROCESS_CREATE_FLAGS_* (BREAKAWAY, INHERIT_HANDLES, NO_DEBUG_INHERIT, SUSPENDED, ...).
ThreadFlagsULONGinTHREAD_CREATE_FLAGS_* (CREATE_SUSPENDED, SKIP_THREAD_ATTACH, HIDE_FROM_DEBUGGER, ...).
ProcessParametersPRTL_USER_PROCESS_PARAMETERSinBloc de paramètres construit par RtlCreateProcessParametersEx (chemin image, ligne de commande, env, handles std).
CreateInfoPPS_CREATE_INFOin/outMachine d'état PS_CREATE_INFO : en entrée PsCreateInitialState, en sortie l'état final plus les handles section / fichier image ouverts.
AttributeListPPS_ATTRIBUTE_LISTinTableau d'entrées PS_ATTRIBUTE : ImageName, ParentProcess, MitigationOptions, JobList, Token, ChildProcessPolicy, ...

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
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

Module noyau

ntoskrnl.exeNtCreateUserProcess

APIs liées

CreateProcessWCreateProcessAsUserWCreateProcessWithTokenWCreateProcessWithLogonWNtCreateProcessNtCreateProcessExRtlCreateProcessParametersEx

Stub du syscall

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

Notes non documentées

NtCreateUserProcess est le point d'entrée noyau moderne sous toute la famille Win32 CreateProcess* depuis Vista. Il remplace la chorégraphie héritée NtCreateProcess / NtCreateProcessEx + NtCreateThread + LdrLoadDll par une transaction noyau unique qui ouvre l'image, construit la section, amorce le PEB, applique les mitigations et démarre le thread initial. Le SSN dérive presque à chaque build (0xBA → 0xD1 sur la fenêtre supportée) à cause d'insertions/retraits voisins ; la résolution dynamique est obligatoire. Le prototype à 11 paramètres est dominé par deux grandes structures : PS_CREATE_INFO (machine d'état que le noyau parcourt lors du parsing de l'image) et PS_ATTRIBUTE_LIST (porteur de tout ce que les paramètres legacy lpStartupInfo / lpProcessAttributes / Token / Job de `CreateProcess` véhiculaient séparément).

Usage courant par les malwares

Deux abus phares. **(1) Spoofing de PPID (T1134.004)** : poser PS_ATTRIBUTE_PARENT_PROCESS dans AttributeList sur un handle dupliqué de tout processus accessible via OpenProcess(PROCESS_CREATE_PROCESS). L'enfant apparaît sous ce parent dans Process Explorer, Sysmon Event ID 1 et les arbres de processus des EDR — classique pour cacher un Beacon sous explorer.exe ou services.exe. **(2) Downgrade de mitigations / abus de child-process policy** : PS_ATTRIBUTE_MITIGATION_OPTIONS permet d'*activer* des mitigations sur l'enfant (bloquer les DLL non Microsoft pour empêcher l'injection EDR dans un processus auxiliaire est une astuce red-team documentée) ou de coupler avec PS_ATTRIBUTE_CHILD_PROCESS_POLICY pour interdire d'autres enfants, compliquant l'analyse. Usage plus subtil : appeler NtCreateUserProcess directement saute kernel32!CreateProcessInternalW, où beaucoup d'EDR accrochent leur hook de télémétrie usermode.

Opportunités de détection

La télémétrie noyau est excellente ici. PsSetCreateProcessNotifyRoutineEx2 se déclenche à chaque retour avec un PS_CREATE_NOTIFY_INFO contenant le *vrai* PID parent (`CreatingThreadId.UniqueProcess`) indépendamment de tout spoof PS_ATTRIBUTE_PARENT_PROCESS. Sysmon Event ID 1 enregistre `ParentProcessGuid` (depuis le ProcessParameters du PEB) et le créateur réel issu du noyau — des valeurs divergentes sont un signal de spoof à haute fidélité. Les événements ETW `Microsoft-Windows-Kernel-Process/ProcessStart` exposent les mêmes champs. Les EDR ne hookant que CreateProcessInternalW ou NtCreateProcessEx rateront les appelants en syscalls directs — vérifier les deux surfaces.

Exemples de syscalls directs

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

Mappings MITRE ATT&CK

Last verified: 2026-05-20