> Windows Syscalls
ntoskrnl.exeT1055.012T1055T1106

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

NameTypeDirDescription
ProcessHandlePHANDLEoutReçoit le handle vers le processus nouvellement créé.
DesiredAccessACCESS_MASKinMasque d'accès processus demandé (PROCESS_ALL_ACCESS pour contrôle total).
ObjectAttributesPOBJECT_ATTRIBUTESinNom Object Manager et descripteur de sécurité optionnels.
ParentProcessHANDLEinHandle vers le processus parent. PEB et handles héritables en sont clonés.
InheritObjectTableBOOLEANinTRUE pour hériter la table de handles du parent ; FALSE pour une table vide.
SectionHandleHANDLEinHandle vers une section SEC_IMAGE d'image qui deviendra l'image principale du processus.
DebugPortHANDLEinPort de debug optionnel (LPC/ALPC) qui reçoit les évènements DBG ; typiquement NULL.
ExceptionPortHANDLEinPort d'exception optionnel pour les exceptions second-chance ; typiquement NULL sur Windows moderne.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070xADwin10-1507
Win10 16070xAFwin10-1607
Win10 17030xB2win10-1703
Win10 17090xB3win10-1709
Win10 18030xB4win10-1803
Win10 18090xB4win10-1809
Win10 19030xB5win10-1903
Win10 19090xB5win10-1909
Win10 20040xB9win10-2004
Win10 20H20xB9win10-20h2
Win10 21H10xB9win10-21h1
Win10 21H20xBAwin10-21h2
Win10 22H20xBAwin10-22h2
Win11 21H20xBDwin11-21h2
Win11 22H20xBEwin11-22h2
Win11 23H20xBEwin11-23h2
Win11 24H20xC0win11-24h2
Server 20160xAFwinserver-2016
Server 20190xB4winserver-2019
Server 20220xBCwinserver-2022
Server 20250xC0winserver-2025

Module noyau

ntoskrnl.exeNtCreateProcess

APIs liées

CreateProcessWCreateProcessAsUserWNtCreateProcessExNtCreateUserProcessNtCreateSectionNtCreateThreadCsrClientCallServer

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 ENDP

rustResolve 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