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
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | PHANDLE | out | Reçoit un handle vers le nouvel objet processus. |
| ThreadHandle | PHANDLE | out | Reçoit un handle vers le thread principal du nouveau processus. |
| ProcessDesiredAccess | ACCESS_MASK | in | Masque d'accès désiré pour le handle de processus retourné (ex. PROCESS_ALL_ACCESS). |
| ThreadDesiredAccess | ACCESS_MASK | in | Masque d'accès désiré pour le handle de thread retourné (ex. THREAD_ALL_ACCESS). |
| ProcessObjectAttributes | POBJECT_ATTRIBUTES | in | OBJECT_ATTRIBUTES optionnels pour l'objet processus (nom, descripteur de sécurité, répertoire racine). |
| ThreadObjectAttributes | POBJECT_ATTRIBUTES | in | OBJECT_ATTRIBUTES optionnels pour l'objet thread principal. |
| 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 | Bloc de paramètres construit par RtlCreateProcessParametersEx (chemin image, ligne de commande, env, handles std). |
| CreateInfo | PPS_CREATE_INFO | in/out | Machine d'état PS_CREATE_INFO : en entrée PsCreateInitialState, en sortie l'état final plus les handles section / fichier image ouverts. |
| AttributeList | PPS_ATTRIBUTE_LIST | in | Tableau d'entrées PS_ATTRIBUTE : ImageName, ParentProcess, MitigationOptions, JobList, Token, ChildProcessPolicy, ... |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | 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 |
Module noyau
APIs liées
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 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);
}Mappings MITRE ATT&CK
Last verified: 2026-05-20