NtWaitForMultipleObjects
Wartet auf bis zu MAXIMUM_WAIT_OBJECTS Dispatcher-Objekte mit WaitAny- oder WaitAll-Semantik.
Prototyp
NTSTATUS NtWaitForMultipleObjects( ULONG Count, PHANDLE Handles, WAIT_TYPE WaitType, BOOLEAN Alertable, PLARGE_INTEGER Timeout );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| Count | ULONG | in | Anzahl Handles im Handles-Array; muss 1..MAXIMUM_WAIT_OBJECTS (64) sein. |
| Handles | PHANDLE | in | Zeiger auf ein Array wartebarer Dispatcher-Objekt-Handles, jedes mit SYNCHRONIZE-Zugriff. |
| WaitType | WAIT_TYPE | in | WaitAll erfordert, dass alle Objekte signalisiert sind; WaitAny kehrt beim ersten zurück. |
| Alertable | BOOLEAN | in | TRUE erlaubt eingereihten User-APCs zu feuern und das Warten mit STATUS_USER_APC abzubrechen. |
| Timeout | PLARGE_INTEGER | in | Optionales Timeout in 100-ns-Einheiten; negativ = relativ, positiv = absolut, NULL = INFINITE. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x5B | win10-1507 |
| Win10 1607 | 0x5B | win10-1607 |
| Win10 1703 | 0x5B | win10-1703 |
| Win10 1709 | 0x5B | win10-1709 |
| Win10 1803 | 0x5B | win10-1803 |
| Win10 1809 | 0x5B | win10-1809 |
| Win10 1903 | 0x5B | win10-1903 |
| Win10 1909 | 0x5B | win10-1909 |
| Win10 2004 | 0x5B | win10-2004 |
| Win10 20H2 | 0x5B | win10-20h2 |
| Win10 21H1 | 0x5B | win10-21h1 |
| Win10 21H2 | 0x5B | win10-21h2 |
| Win10 22H2 | 0x5B | win10-22h2 |
| Win11 21H2 | 0x5B | win11-21h2 |
| Win11 22H2 | 0x5B | win11-22h2 |
| Win11 23H2 | 0x5B | win11-23h2 |
| Win11 24H2 | 0x5B | win11-24h2 |
| Server 2016 | 0x5B | winserver-2016 |
| Server 2019 | 0x5B | winserver-2019 |
| Server 2022 | 0x5B | winserver-2022 |
| Server 2025 | 0x5B | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 5B 00 00 00 mov eax, 0x5B 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
Dispatcht über `KeWaitForMultipleObjects` in ntoskrnl.exe. SSN `0x5B` ist über jeden Win10/11-Build stabil. Bei `WaitAny` zeigt der Returnwert `STATUS_WAIT_0..STATUS_WAIT_63` an, welches Handle signalisiert hat; bei `WaitAll` muss der Kernel den Zustand jedes Objekts atomar prüfen, weshalb `WaitAll` vergleichsweise teuer ist und `MAXIMUM_WAIT_OBJECTS` auf 64 begrenzt ist — der Dispatcher führt Waiter über einen Block fester Größe. Wie bei `NtWaitForSingleObject` öffnet das `Alertable`-Flag den Thread für die User-APC-Zustellung (mit Rückgabe `STATUS_USER_APC`).
Häufige Malware-Nutzung
Verwendet von Implants, die mehrere gleichzeitige Aufwachquellen unterhalten — typischerweise *(Timer-Event)* + *(Kill-Switch-Event)* + *(Netzwerk-Ready-Event)* —, damit der Beacon entweder auf abgelaufene Zeit oder auf einen Push des Control-Channels reagieren kann. Sleep-Mask-Varianten (Cronos, Zilean) warten auf das Verschlüsselungs-Stufen-Event plus ein Cancellation-Event, sodass der C2 einen laufenden Sleep-Zyklus unterbrechen kann. Manche Dropper warten auf mehrere Thread-Handles, um auf das Ende paralleler Entschlüsselungsstufen zu warten (WaitAll). Seltener dient ein alertable WaitAny auf handgebauten Handle-Arrays als flexiblerer APC-Empfangspunkt als Single-Object-Waits.
Erkennungsmöglichkeiten
Gleiche Einschränkungen wie bei `NtWaitForSingleObject`: zu häufig, um direkt zu alerten. Brauchbare Signale: alertable `WaitAny` über eine Handle-Menge, die ein privates benanntes Event enthält, gehalten von einem Prozess, dessen Code in nicht-gemapptem Speicher liegt; oder ein Thread, der routinemäßig auf >1 Handle wartet, eines davon ein Kernel-Transport-Layer-Objekt (\Device\Afd, Named Pipe, benanntes Event) plus ein synthetisiertes Timer-Event. Kernel-Mode-Handle-Tabelle gegen modulgebundene Thread-RIPs beim Sleep-Eintritt korrelieren.
Direkte Syscall-Beispiele
asmx64 direct stub
; Direct syscall stub for NtWaitForMultipleObjects (SSN 0x5B, stable across Win10/11)
NtWaitForMultipleObjects PROC
mov r10, rcx ; syscall convention
mov eax, 5Bh ; SSN
syscall
ret
NtWaitForMultipleObjects ENDPcAlertable WaitAny on event + kill-switch
// Beacon wakes on either the sleep-timer event or an out-of-band cancel.
HANDLE waits[2] = { hSleepEvent, hCancelEvent };
NTSTATUS st = NtWaitForMultipleObjects(2, waits, WaitAny, TRUE, NULL);
if (st == STATUS_WAIT_0) { /* normal beacon tick */ }
else if (st == STATUS_WAIT_1) { /* operator told us to shut down */ }
else if (st == STATUS_USER_APC){ /* APC fired mid-wait */ }cWaitAll on parallel decryption threads
// Spin up N threads to decrypt stage payload, then synchronize before execution.
HANDLE workers[4];
for (int i = 0; i < 4; i++)
workers[i] = create_decrypt_worker(stage_slice(i));
NtWaitForMultipleObjects(4, workers, WaitAll, FALSE, NULL);
// All slices decrypted; safe to jump into stage.MITRE ATT&CK-Mappings
Last verified: 2026-05-20