NtCreateProcess
Création de processus NT historique à partir d'un objet section préconstruit — prédécesseur de NtCreateProcessEx et NtCreateUserProcess.
Prototype
NTSTATUS NtCreateProcess( PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, BOOLEAN InheritObjectTable, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | PHANDLE | out | Reçoit le handle vers le processus nouvellement créé. |
| DesiredAccess | ACCESS_MASK | in | Masque d'accès processus demandé (PROCESS_ALL_ACCESS pour contrôle total). |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Nom Object Manager et descripteur de sécurité optionnels. |
| ParentProcess | HANDLE | in | Handle vers le processus parent. PEB et handles héritables en sont clonés. |
| InheritObjectTable | BOOLEAN | in | TRUE pour hériter la table de handles du parent ; FALSE pour une table vide. |
| SectionHandle | HANDLE | in | Handle vers une section SEC_IMAGE d'image qui deviendra l'image principale du processus. |
| DebugPort | HANDLE | in | Port de debug optionnel (LPC/ALPC) qui reçoit les évènements DBG ; typiquement NULL. |
| ExceptionPort | HANDLE | in | Port d'exception optionnel pour les exceptions second-chance ; typiquement NULL sur Windows moderne. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0xAD | win10-1507 |
| Win10 1607 | 0xAF | win10-1607 |
| Win10 1703 | 0xB2 | win10-1703 |
| Win10 1709 | 0xB3 | win10-1709 |
| Win10 1803 | 0xB4 | win10-1803 |
| Win10 1809 | 0xB4 | win10-1809 |
| Win10 1903 | 0xB5 | win10-1903 |
| Win10 1909 | 0xB5 | win10-1909 |
| Win10 2004 | 0xB9 | win10-2004 |
| Win10 20H2 | 0xB9 | win10-20h2 |
| Win10 21H1 | 0xB9 | win10-21h1 |
| Win10 21H2 | 0xBA | win10-21h2 |
| Win10 22H2 | 0xBA | win10-22h2 |
| Win11 21H2 | 0xBD | win11-21h2 |
| Win11 22H2 | 0xBE | win11-22h2 |
| Win11 23H2 | 0xBE | win11-23h2 |
| Win11 24H2 | 0xC0 | win11-24h2 |
| Server 2016 | 0xAF | winserver-2016 |
| Server 2019 | 0xB4 | winserver-2019 |
| Server 2022 | 0xBC | winserver-2022 |
| Server 2025 | 0xC0 | winserver-2025 |
Module noyau
APIs liées
Stub du syscall
4C 8B D1 mov r10, rcx B8 C0 00 00 00 mov eax, 0xC0 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
Le syscall de création de processus NT *original* — présent depuis Windows NT 3.1. C'est la primitive de plus bas niveau : l'appelant doit avoir déjà créé la section image (`NtCreateSection(SEC_IMAGE, …)`), et le noyau ne fait que construire l'EPROCESS/KPROCESS, copier le squelette PEB du parent, et cloner l'espace d'adressage. Il ne crée **pas** le thread initial (à enchaîner avec `NtCreateThread`), ne charge **aucune** DLL au-delà de l'image principale, et n'invoque **pas** CSRSS pour enregistrer le nouveau processus — ce qui signifie que le processus résultant **n'est pas encore un processus Win32**. Pour obtenir un processus console/Win32-visible, il faut appeler `CsrClientCallServer(BasepCreateProcess)` manuellement. `NtCreateProcessEx` (Windows XP+) a ajouté `JobMemberLevel` et la création sans section ; `NtCreateUserProcess` (Vista+) a remplacé les deux comme implémentation one-shot de CreateProcess qui gère section, thread, PEB, CSRSS, AppContainer et policies de mitigation en un seul syscall. Le `kernel32!CreateProcessW` moderne appelle exclusivement `NtCreateUserProcess` — `NtCreateProcess` n'est joignable que par invocation directe.
Usage courant par les malwares
Utilisé historiquement (pré-2010s) dans les chaînes de **process hollowing manuel** qui voulaient un contrôle maximum sur l'initialisation du PEB, le bypass de policy de mitigation, ou l'évasion vis-à-vis d'outils qui hookaient `CreateProcessW` mais pas la couche NT inférieure. Le pattern canonique : `NtCreateSection(SEC_IMAGE)` sur un binaire cible bénin → `NtCreateProcess` avec cette section comme image → `NtCreateThread` au point d'entrée de la nouvelle image. Parce que le processus résultant ne parle jamais à CSRSS, il manque une entrée dans le sous-système console, n'apparaît pas dans `EnumProcesses` jusqu'à notification CSRSS, et sur les systèmes legacy était effectivement invisible au Task Manager pendant une brève fenêtre. Les callbacks modernes `Process Notify Routine` (`PsSetCreateProcessNotifyRoutineEx`) se déclenchent depuis `PspInsertProcess` indépendamment du wrapper user-mode appelant, donc le bénéfice d'évasion de visibilité est révolu, et `NtCreateUserProcess` est universellement préféré. Aujourd'hui, les appels à `NtCreateProcess` venant d'autre chose qu'une poignée de binaires de test MS et certaines émulations Cygwin/MSYS2 de `fork()` sont essentiellement une empreinte de malware.
Opportunités de détection
Le signal le plus fort est *toute* invocation hors d'une allowlist connue — `cygwin1.dll` de Cygwin, `msys-2.0.dll` de MSYS2, la couche LXSS WSL1 et les outils de test du sous-système NT de Microsoft. Les callbacks noyau `PsSetCreateProcessNotifyRoutineEx` reçoivent un `PS_CREATE_NOTIFY_INFO` avec `FileObject != NULL` seulement quand l'image a été ouverte par le noyau — les appelants de `NtCreateProcess` fournissent eux-mêmes la section, donc l'`ImageFileName` est le fichier source de la section et le contexte *appelant* dit qui l'a construite. L'évènement ETW `Microsoft-Windows-Kernel-Process` ID 1 se déclenche uniformément ; corrélez le champ `CreateRoutine` (visible dans la pile noyau de la vue threadée) pour distinguer `NtCreateUserProcess` de `NtCreateProcessEx` / `NtCreateProcess`. Tout évènement Sysmon 1 dont le ParentImage est inhabituel et dont l'`OriginalFileName` ne correspond pas à `ImageFileName` est un signal fort.
Exemples de syscalls directs
cMinimal legacy hollow-skeleton (NtCreateSection -> NtCreateProcess -> NtCreateThread)
// Minimal legacy-style hollow. Modern EDR sees the PspInsertProcess callback
// regardless, so this is documented for reverse-engineers, not as a working
// evasion in 2026.
#include <windows.h>
#include <winternl.h>
typedef NTSTATUS (NTAPI* pNtCreateProcess)(
PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, HANDLE, BOOLEAN,
HANDLE, HANDLE, HANDLE);
void legacy_hollow(HANDLE hImageSection) {
pNtCreateProcess NtCreateProcess = (pNtCreateProcess)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtCreateProcess");
HANDLE hProc = NULL;
NTSTATUS st = NtCreateProcess(
&hProc,
PROCESS_ALL_ACCESS,
NULL,
NtCurrentProcess(), // parent = self → inherits address-space template
FALSE, // do not inherit handles
hImageSection, // SEC_IMAGE backing
NULL, // no debug port
NULL); // no exception port
// At this point the process exists but has zero threads and has not been
// announced to CSRSS. Caller now creates the initial thread at the image
// entry point with NtCreateThread.
}asmx64 direct stub (Win11 24H2 / Server 2025, SSN 0xC0)
NtCreateProcess PROC
mov r10, rcx
mov eax, 0C0h
syscall
ret
NtCreateProcess ENDPrustResolve and call via Halo's Gate
// Hardcoded SSNs drift; resolve at runtime by walking ntdll exports.
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
unsafe fn nt_create_process_ssn() -> u32 {
let h = GetModuleHandleA(b"ntdll.dll\0".as_ptr());
let p = GetProcAddress(h, b"NtCreateProcess\0".as_ptr()) as *const u8;
// mov r10, rcx ; mov eax, IMM32 ; ...
if p.read() == 0x4C && p.add(1).read() == 0x8B && p.add(2).read() == 0xD1 && p.add(3).read() == 0xB8 {
u32::from_le_bytes([p.add(4).read(), p.add(5).read(), p.add(6).read(), p.add(7).read()])
} else { 0 }
}Mappings MITRE ATT&CK
Last verified: 2026-05-20