NtCreateProcessEx
Crée un nouveau processus à partir d'un handle de section sans exécuter l'initialisation ntdll — brique du process hollowing.
Prototype
NTSTATUS NtCreateProcessEx( PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, ULONG JobMemberLevel );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | PHANDLE | out | Reçoit le handle du nouveau processus en cas de succès. |
| DesiredAccess | ACCESS_MASK | in | Droits d'accès pour le handle de processus retourné, typiquement PROCESS_ALL_ACCESS. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Attributs d'objet optionnels (nom, répertoire racine). Généralement NULL. |
| ParentProcess | HANDLE | in | Handle vers le processus parent (NtCurrentProcess() pour soi-même). Permet le spoofing de PID parent s'il pointe vers un autre processus. |
| Flags | ULONG | in | Drapeaux de création incluant PROCESS_CREATE_FLAGS_INHERIT_HANDLES, PROCESS_CREATE_FLAGS_BREAKAWAY, PROCESS_CREATE_FLAGS_SUSPENDED. |
| SectionHandle | HANDLE | in | Handle vers une section SEC_IMAGE qui porte l'image du nouveau processus. NULL hérite de l'image du parent (utilisé dans les combos PPID-spoof + hollow). |
| DebugPort | HANDLE | in | Handle de port de debug optionnel. Presque toujours NULL. |
| ExceptionPort | HANDLE | in | Handle de port d'exception optionnel (mécanisme LPC historique). Presque toujours NULL. |
| JobMemberLevel | ULONG | in | Niveau d'imbrication dans une job-object quand le parent fait partie d'une hiérarchie de jobs ; 0 si non pertinent. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x4D | win10-1507 |
| Win10 1607 | 0x4D | win10-1607 |
| Win10 1703 | 0x4D | win10-1703 |
| Win10 1709 | 0x4D | win10-1709 |
| Win10 1803 | 0x4D | win10-1803 |
| Win10 1809 | 0x4D | win10-1809 |
| Win10 1903 | 0x4D | win10-1903 |
| Win10 1909 | 0x4D | win10-1909 |
| Win10 2004 | 0x4D | win10-2004 |
| Win10 20H2 | 0x4D | win10-20h2 |
| Win10 21H1 | 0x4D | win10-21h1 |
| Win10 21H2 | 0x4D | win10-21h2 |
| Win10 22H2 | 0x4D | win10-22h2 |
| Win11 21H2 | 0x4D | win11-21h2 |
| Win11 22H2 | 0x4D | win11-22h2 |
| Win11 23H2 | 0x4D | win11-23h2 |
| Win11 24H2 | 0x4D | win11-24h2 |
| Server 2016 | 0x4D | winserver-2016 |
| Server 2019 | 0x4D | winserver-2019 |
| Server 2022 | 0x4D | winserver-2022 |
| Server 2025 | 0x4D | winserver-2025 |
Module noyau
APIs liées
Stub du syscall
4C 8B D1 mov r10, rcx B8 4D 00 00 00 mov eax, 0x4D 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
NtCreateProcessEx est le point d'entrée bas-niveau de création de processus. Contrairement à NtCreateUserProcess (utilisé désormais par CreateProcessW), NtCreateProcessEx exige que l'appelant ait pré-créé une section SEC_IMAGE depuis l'exécutable cible via NtCreateSection(SEC_IMAGE) puis la rattache. Surtout, le processus résultant n'a pas de thread initial — l'appelant doit ensuite appeler NtCreateThreadEx (ou NtCreateThread). Cette séparation fait de NtCreateProcessEx la brique canonique du *process hollowing* et de la famille *Early Bird APC* : créer un processus, y poster du travail avant que sa première instruction ne s'exécute. Le SSN `0x4D` est stable sur toutes les builds supportées.
Usage courant par les malwares
Utilisée par les chaînes de process hollowing qui veulent éviter la télémétrie de CreateProcessW, par les outils de PPID-spoofing (positionner ParentProcess sur explorer.exe ou un autre PID de confiance), et par les variantes Early Bird combinant NtCreateProcessEx + NtCreateThreadEx(suspendu) + NtQueueApcThreadEx. Les fonctions spawnto/spoof-parent de Cobalt Strike atteignent au final ce chemin de code. Moins courante comme primitive offensive de haut niveau aujourd'hui car NtCreateUserProcess offre un contrôle d'attributs plus riche en un seul syscall.
Opportunités de détection
ETW Microsoft-Windows-Kernel-Process émet des événements ProcessStart depuis PspInsertProcess indépendamment du point d'entrée userland ; le noyau ne distingue pas NtCreateProcessEx de NtCreateUserProcess à ce callback. Sysmon Event ID 1 se déclenche dans les deux cas et expose le vrai PID parent même quand CreateProcessW aurait menti. Les hooks userland sur kernelbase!CreateProcessW ratent les appelants en syscalls directs ; les hooks doivent être placés sur ntdll!NtCreateUserProcess *et* ntdll!NtCreateProcessEx. Indicateur suspect : une section SEC_IMAGE sur un PE non signé suivie en quelques millisecondes par NtCreateProcessEx puis NtCreateThreadEx en état suspendu.
Exemples de syscalls directs
asmx64 direct stub
; Direct syscall stub for NtCreateProcessEx (SSN 0x4D, stable on Win10 1507+ through Win11 24H2)
NtCreateProcessEx PROC
mov r10, rcx ; syscall convention
mov eax, 4Dh ; SSN
syscall
ret
NtCreateProcessEx ENDPcProcess-hollow style: SEC_IMAGE + NtCreateProcessEx
// Open the target PE and wrap it in a SEC_IMAGE section, then spawn a process backed by it.
HANDLE hFile = NULL;
IO_STATUS_BLOCK iosb = { 0 };
OBJECT_ATTRIBUTES oa = { 0 };
UNICODE_STRING path;
RtlInitUnicodeString(&path, L"\\??\\C:\\Windows\\System32\\notepad.exe");
InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE, NULL, NULL);
NtOpenFile(&hFile, FILE_EXECUTE | SYNCHRONIZE, &oa, &iosb,
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
HANDLE hSection = NULL;
NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, NULL,
PAGE_READONLY, SEC_IMAGE, hFile);
HANDLE hProcess = NULL;
NtCreateProcessEx(&hProcess,
PROCESS_ALL_ACCESS,
NULL,
NtCurrentProcess(), // ParentProcess — set elsewhere for PPID spoof
0, // Flags
hSection, // SectionHandle
NULL, // DebugPort
NULL, // ExceptionPort
0); // JobMemberLevel
// New process has NO threads yet — caller must NtCreateThreadEx into it.rustwindows-sys + naked syscall stub
// Cargo: windows-sys = "0.59"
use std::arch::asm;
#[unsafe(naked)]
unsafe extern "system" fn nt_create_process_ex_stub() {
asm!(
"mov r10, rcx",
"mov eax, 0x4D",
"syscall",
"ret",
options(noreturn),
);
}Mappings MITRE ATT&CK
Last verified: 2026-05-20