NtGetNextProcess
Durchläuft die Prozessliste des Kernels und liefert ein Handle auf den nächsten Prozess.
Prototyp
NTSTATUS NtGetNextProcess( HANDLE ProcessHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, ULONG Flags, PHANDLE NewProcessHandle );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle auf den vorherigen Prozess in der Aufzählung. NULL beginnt am Listenanfang. |
| DesiredAccess | ACCESS_MASK | in | Auf dem neuen Handle angeforderte Zugriffsrechte, z. B. PROCESS_QUERY_LIMITED_INFORMATION. |
| HandleAttributes | ULONG | in | Handle-Attributflags wie OBJ_INHERIT oder OBJ_CASE_INSENSITIVE. Üblicherweise 0. |
| Flags | ULONG | in | Reserviert. Muss in aktuellen Windows-Builds 0 sein. |
| NewProcessHandle | PHANDLE | out | Empfängt das Handle auf den nächsten Prozess. STATUS_NO_MORE_ENTRIES markiert das Listenende. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0xE8 | win10-1507 |
| Win10 1607 | 0xEB | win10-1607 |
| Win10 1703 | 0xEE | win10-1703 |
| Win10 1709 | 0xEF | win10-1709 |
| Win10 1803 | 0xF0 | win10-1803 |
| Win10 1809 | 0xF1 | win10-1809 |
| Win10 1903 | 0xF2 | win10-1903 |
| Win10 1909 | 0xF2 | win10-1909 |
| Win10 2004 | 0xF7 | win10-2004 |
| Win10 20H2 | 0xF7 | win10-20h2 |
| Win10 21H1 | 0xF7 | win10-21h1 |
| Win10 21H2 | 0xF8 | win10-21h2 |
| Win10 22H2 | 0xF8 | win10-22h2 |
| Win11 21H2 | 0xFD | win11-21h2 |
| Win11 22H2 | 0xFE | win11-22h2 |
| Win11 23H2 | 0xFE | win11-23h2 |
| Win11 24H2 | 0x100 | win11-24h2 |
| Server 2016 | 0xEB | winserver-2016 |
| Server 2019 | 0xF1 | winserver-2019 |
| Server 2022 | 0xFC | winserver-2022 |
| Server 2025 | 0x100 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
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
Undokumentierte Hinweise
NtGetNextProcess durchläuft die in PsActiveProcessHead verankerte EPROCESS-Kette und liefert reihum ein neues Handle für jeden Prozess. Beim ersten Aufruf NULL als ProcessHandle übergeben; bei Folgeaufrufen das zuvor erhaltene Handle (die Funktion schließt es nur unter Windows 8+ selbst, wenn STATUS_NO_MORE_ENTRIES zurückkommt — andernfalls müssen Sie NtClose aufrufen). Die Schwesterroutine NtGetNextThread iteriert ETHREAD-Einträge innerhalb eines gegebenen Prozesses. Beide werden von ntdll.dll exportiert, haben aber keinen Win32-Wrapper — `CreateToolhelp32Snapshot` und `EnumProcesses` nehmen völlig andere Wege (Toolhelp über eine Snapshot-Section, PSAPI über NtQuerySystemInformation(SystemProcessInformation)).
Häufige Malware-Nutzung
Modernes OPSEC-bewusstes Tradecraft meidet Toolhelp32 und PSAPI zunehmend, weil sie von EDR-User-Mode-Agenten stark gehookt werden und laute ETW-Events erzeugen. PsActiveProcessHead über NtGetNextProcess zu iterieren gibt dem Implant eine Prozessliste, ohne je die offensichtlichen Enumerations-APIs aufzurufen. Typische Anwendungen: lsass.exe vor einem Credential-Dump lokalisieren, EDR/AV-Prozesse zum Unhooken oder Identitätsdiebstahl finden, einen Opferhost für Process Injection wählen sowie Analyse-Tools (Process Hacker, x64dbg) vor dem Entpacken meiden. PoolParty und mehrere öffentliche PoCs nutzen das Paar NtGetNextProcess + NtGetNextThread, um Thread-Pool-Worker-Threads zu enumerieren, ohne Toolhelp anzufassen.
Erkennungsmöglichkeiten
NtGetNextProcess hat im User-Mode kaum legitimen Fußabdruck — Windows selbst ruft sie außerhalb einer Handvoll Systemkomponenten kaum auf. Ein User-Mode-Prozess, der sie wiederholt aufruft, sollte als anomal gelten. Microsoft-Windows-Threat-Intelligence-ETW exponiert diesen Syscall nicht direkt, aber die *Folge*-Operationen (NtOpenProcess gegen lsass, ProcessAccess mit PROCESS_VM_READ) sind gut instrumentiert (Sysmon Event ID 10). Kernel-Callback-basierte EDRs, die PspInsertProcess hooken, sehen diese Enumeration nicht — nur den nachfolgenden Zugriff. Achten Sie auf unsignierte Binaries, die `NtGetNextProcess` aus der Export-Tabelle von ntdll auflösen.
Direkte Syscall-Beispiele
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 ENDPrustEDR 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>();
}
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20