> Windows Syscalls
ntoskrnl.exeT1057T1106

NtGetNextProcess

Parcourt la liste des processus du noyau et retourne un handle vers le processus suivant.

Prototype

NTSTATUS NtGetNextProcess(
  HANDLE      ProcessHandle,
  ACCESS_MASK DesiredAccess,
  ULONG       HandleAttributes,
  ULONG       Flags,
  PHANDLE     NewProcessHandle
);

Arguments

NameTypeDirDescription
ProcessHandleHANDLEinHandle vers le processus précédent dans l'énumération. NULL pour partir du début de la liste.
DesiredAccessACCESS_MASKinDroits d'accès demandés sur le nouveau handle, ex. PROCESS_QUERY_LIMITED_INFORMATION.
HandleAttributesULONGinDrapeaux d'attributs de handle tels que OBJ_INHERIT ou OBJ_CASE_INSENSITIVE. Généralement 0.
FlagsULONGinRéservé. Doit valoir 0 sur les builds Windows actuels.
NewProcessHandlePHANDLEoutReçoit le handle du processus suivant. STATUS_NO_MORE_ENTRIES indique la fin de la liste.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070xE8win10-1507
Win10 16070xEBwin10-1607
Win10 17030xEEwin10-1703
Win10 17090xEFwin10-1709
Win10 18030xF0win10-1803
Win10 18090xF1win10-1809
Win10 19030xF2win10-1903
Win10 19090xF2win10-1909
Win10 20040xF7win10-2004
Win10 20H20xF7win10-20h2
Win10 21H10xF7win10-21h1
Win10 21H20xF8win10-21h2
Win10 22H20xF8win10-22h2
Win11 21H20xFDwin11-21h2
Win11 22H20xFEwin11-22h2
Win11 23H20xFEwin11-23h2
Win11 24H20x100win11-24h2
Server 20160xEBwinserver-2016
Server 20190xF1winserver-2019
Server 20220xFCwinserver-2022
Server 20250x100winserver-2025

Module noyau

ntoskrnl.exeNtGetNextProcess

APIs liées

NtGetNextThreadNtQueryInformationProcessNtQuerySystemInformationCreateToolhelp32SnapshotEnumProcessesOpenProcess

Stub du syscall

4C 8B D1                  mov r10, rcx
B8 00 01 00 00            mov eax, 0x100
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

NtGetNextProcess parcourt la chaîne EPROCESS ancrée à PsActiveProcessHead et retourne un nouveau handle vers chaque processus. Passez NULL en ProcessHandle au premier appel ; aux appels suivants passez le handle précédemment retourné (la fonction le ferme pour vous uniquement sous Windows 8+ quand STATUS_NO_MORE_ENTRIES est renvoyé — sinon vous devez NtClose). La routine soeur NtGetNextThread itère les ETHREAD d'un processus donné. Toutes deux sont exportées par ntdll.dll mais n'ont pas d'enveloppe Win32 — `CreateToolhelp32Snapshot` et `EnumProcesses` empruntent des chemins entièrement différents (Toolhelp via une section snapshot, PSAPI via NtQuerySystemInformation(SystemProcessInformation)).

Usage courant par les malwares

Le tradecraft moderne soucieux d'OPSEC évite de plus en plus Toolhelp32 et PSAPI car ils sont fortement hookés par les agents user-mode des EDR et génèrent des événements ETW bruyants. Itérer PsActiveProcessHead via NtGetNextProcess donne à l'implant la liste des processus sans jamais appeler les API d'énumération évidentes. Usages typiques : localiser lsass.exe avant un dump d'identifiants, trouver les processus EDR/AV pour les unhook ou usurper leur identité, choisir un hôte sacrificiel pour l'injection, éviter les processus d'outillage d'analyse (Process Hacker, x64dbg) avant le dépaquetage. PoolParty et plusieurs PoC publics utilisent le couple NtGetNextProcess + NtGetNextThread pour énumérer les threads du pool sans toucher à Toolhelp.

Opportunités de détection

NtGetNextProcess a une empreinte légitime en user-mode très faible — Windows lui-même l'appelle rarement hors de quelques composants système. Un processus user-mode qui l'invoque de manière répétée doit être considéré comme anormal. L'ETW Microsoft-Windows-Threat-Intelligence n'expose pas directement ce syscall, mais les opérations *suivantes* (NtOpenProcess sur lsass, ProcessAccess avec PROCESS_VM_READ) sont bien instrumentées (Sysmon Event ID 10). Les EDR à callbacks noyau qui hookent PspInsertProcess ne verront pas cette énumération — seulement l'accès qui suit. Cherchez des binaires non signés qui résolvent `NtGetNextProcess` depuis la table d'exports de ntdll.

Exemples de syscalls directs

cLSASS PID discovery without Toolhelp

// Locate lsass.exe by walking PsActiveProcessHead via NtGetNextProcess.
// Avoids CreateToolhelp32Snapshot / EnumProcesses entirely.
#include <windows.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI *PNT_GET_NEXT_PROCESS)(HANDLE, ACCESS_MASK, ULONG, ULONG, PHANDLE);
typedef NTSTATUS (NTAPI *PNT_QUERY_INFO_PROCESS)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);

DWORD FindLsassPid(void) {
    HMODULE nt = GetModuleHandleA("ntdll.dll");
    PNT_GET_NEXT_PROCESS pNtGetNextProcess = (PNT_GET_NEXT_PROCESS)GetProcAddress(nt, "NtGetNextProcess");
    PNT_QUERY_INFO_PROCESS pNtQueryInformationProcess = (PNT_QUERY_INFO_PROCESS)GetProcAddress(nt, "NtQueryInformationProcess");

    HANDLE hProcess = NULL;
    HANDLE hNext = NULL;
    while (pNtGetNextProcess(hProcess, PROCESS_QUERY_LIMITED_INFORMATION, 0, 0, &hNext) == 0) {
        if (hProcess) CloseHandle(hProcess);
        hProcess = hNext;

        BYTE buf[1024];
        ULONG ret = 0;
        if (pNtQueryInformationProcess(hProcess, ProcessImageFileName, buf, sizeof(buf), &ret) == 0) {
            PUNICODE_STRING us = (PUNICODE_STRING)buf;
            if (us->Buffer && wcsstr(us->Buffer, L"lsass.exe")) {
                PROCESS_BASIC_INFORMATION pbi = {0};
                pNtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &ret);
                CloseHandle(hProcess);
                return (DWORD)(ULONG_PTR)pbi.UniqueProcessId;
            }
        }
    }
    if (hProcess) CloseHandle(hProcess);
    return 0;
}

asmx64 direct stub (Win11 24H2, SSN 0x100)

; Direct syscall stub for NtGetNextProcess on Win11 24H2.
NtGetNextProcess PROC
    mov  r10, rcx
    mov  eax, 100h
    syscall
    ret
NtGetNextProcess ENDP

rustEDR process scan via windows-sys + dynamic SSN

// Cargo: windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_System_LibraryLoader"] }
use std::ffi::CString;
use std::ptr::null_mut;
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};

type NtGetNextProcessFn = unsafe extern "system" fn(HANDLE, u32, u32, u32, *mut HANDLE) -> i32;

const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;

fn enumerate_processes(mut callback: impl FnMut(HANDLE)) {
    unsafe {
        let ntdll = GetModuleHandleA(CString::new("ntdll.dll").unwrap().as_ptr() as *const u8);
        let name = CString::new("NtGetNextProcess").unwrap();
        let f: NtGetNextProcessFn = std::mem::transmute(GetProcAddress(ntdll, name.as_ptr() as *const u8));

        let mut prev: HANDLE = 0;
        let mut next: HANDLE = 0;
        while f(prev, PROCESS_QUERY_LIMITED_INFORMATION, 0, 0, &mut next) == 0 {
            if prev != 0 { CloseHandle(prev); }
            prev = next;
            callback(prev);
        }
        if prev != 0 { CloseHandle(prev); }
        let _ = null_mut::<u8>();
    }
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20