> Windows Syscalls
ntoskrnl.exeT1027T1106

NtWaitForKeyedEvent

Bloque le thread appelant sur un keyed event jusqu'à ce qu'un autre thread libère la même paire (event, clé).

Prototype

NTSTATUS NtWaitForKeyedEvent(
  HANDLE         KeyedEventHandle,
  PVOID          Key,
  BOOLEAN        Alertable,
  PLARGE_INTEGER Timeout
);

Arguments

NameTypeDirDescription
KeyedEventHandleHANDLEinHandle vers un keyed event. NULL utilise le défaut par processus à \KernelObjects\CritSecOutOfMemoryEvent.
KeyPVOIDinValeur de rendez-vous de la taille d'un pointeur. Doit être alignée naturellement (bit faible à zéro). Conventionnellement l'adresse de la critical section / mot de verrou.
AlertableBOOLEANinTRUE rend l'attente interruptible par APC user-mode ; presque toujours FALSE pour les primitives de verrou.
TimeoutPLARGE_INTEGERinDélai optionnel (unités de 100 ns, négatif = relatif). NULL = attente infinie. Renvoie STATUS_TIMEOUT à expiration.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x1B5win10-1507
Win10 16070x1BEwin10-1607
Win10 17030x1C4win10-1703
Win10 17090x1C8win10-1709
Win10 18030x1CAwin10-1803
Win10 18090x1CBwin10-1809
Win10 19030x1CCwin10-1903
Win10 19090x1CCwin10-1909
Win10 20040x1D2win10-2004
Win10 20H20x1D2win10-20h2
Win10 21H10x1D2win10-21h1
Win10 21H20x1D4win10-21h2
Win10 22H20x1D4win10-22h2
Win11 21H20x1DEwin11-21h2
Win11 22H20x1E2win11-22h2
Win11 23H20x1E2win11-23h2
Win11 24H20x1E5win11-24h2
Server 20160x1BEwinserver-2016
Server 20190x1CBwinserver-2019
Server 20220x1DAwinserver-2022
Server 20250x1E5winserver-2025

Module noyau

ntoskrnl.exeNtWaitForKeyedEvent

APIs liées

NtCreateKeyedEventNtOpenKeyedEventNtReleaseKeyedEventWaitOnAddressWakeByAddressSingleRtlEnterCriticalSection

Stub du syscall

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

La moitié wait de la paire keyed-event. Utilisée en interne par `ntdll!RtlEnterCriticalSection` comme **fallback bas mémoire** lorsque la contention d'une critical section exigerait normalement d'allouer un objet event et que le système est à court de pool. Dans ce chemin, `ntdll.dll` passe `peb->KeyedEventHandle` (le global `CritSecOutOfMemoryEvent`) et utilise l'adresse du champ `LockSemaphore` de la critical section comme clé, si bien que chaque thread en contention dans le système partage un unique keyed event global. `NtWaitForKeyedEvent` est aussi l'une des deux primitives derrière `WaitOnAddress` (l'autre étant `KeWaitForSingleObject` sur un event thread-local) ; sur les builds modernes, `WaitOnAddress` est le point d'entrée user-mode recommandé.

Usage courant par les malwares

Signal modeste. Les designs d'obfuscation de sommeil voulant une primitive de synchro sans nommer aucun objet noyau apparient `NtWaitForKeyedEvent` (dans le thread endormi) à `NtReleaseKeyedEvent` (dans un gadget ROP de timer de réveil ou un APC). Le handle global étant déjà ouvert dans chaque processus, l'ensemble n'exige aucun nouveau handle — esquivant les sandboxes rudimentaires qui scannent les tables de handles à la recherche d'events / sémaphores fraîchement créés. La famille Ekko et plusieurs démos publiques de sleep-mask s'appuient là-dessus. Un usage direct et délibéré de `NtWaitForKeyedEvent` depuis un binaire non système hors ce pattern est plutôt l'indice d'une implémentation de verrou maison qu'un signal de malware.

Opportunités de détection

Même disposition que `NtCreateKeyedEvent` : pas de surface ETW ou Sysmon utile. Le seul angle blue-team significatif est *comportemental* — un thread bloqué dans `NtWaitForKeyedEvent` sur le handle global anormalement longtemps, alors que son module hôte vit en mémoire non backed / RX-only (typique des beacons sleep-maskés), est suspect. ETW Threat Intelligence + introspection des régions mémoire (ex. `MemoryWorkingSetExInformation` montrant des pages `MEMORY_REGION_PRIVATE`) est plus informatif que tracer le syscall lui-même.

Exemples de syscalls directs

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtWaitForKeyedEvent (SSN 0x1E5 on Win11 24H2)
NtWaitForKeyedEvent PROC
    mov  r10, rcx          ; KeyedEventHandle
    mov  eax, 1E5h         ; SSN — drifts per build
    syscall
    ret
NtWaitForKeyedEvent ENDP

cCritical-section low-memory fallback (illustrative)

// Mirrors what ntdll!RtlpWaitOnCriticalSection does in the out-of-pool path:
// every contending thread parks on the global keyed event using the address
// of the critical section's LockSemaphore slot as the rendezvous key.
#include <windows.h>
#include <winternl.h>

typedef NTSTATUS (NTAPI *pNtWaitForKeyedEvent)(
    HANDLE, PVOID, BOOLEAN, PLARGE_INTEGER);

static pNtWaitForKeyedEvent g_wait;

void ParkOnLockWord(volatile void **lockSlot) {
    if (!g_wait) {
        g_wait = (pNtWaitForKeyedEvent)GetProcAddress(
            GetModuleHandleA("ntdll.dll"), "NtWaitForKeyedEvent");
    }
    // NULL handle ⇒ kernel substitutes peb->KeyedEventHandle = global event.
    g_wait(NULL, (PVOID)lockSlot, FALSE, NULL);
}

cModern equivalent — WaitOnAddress (recommended)

// Microsoft's blessed user-mode primitive for the same pattern.
#include <windows.h>
#include <synchapi.h>
#pragma comment(lib, "Synchronization.lib")

void WaitForLockChange(volatile LONG *lock, LONG expected) {
    WaitOnAddress((volatile VOID*)lock, &expected, sizeof(expected), INFINITE);
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20