> Windows Syscalls
ntoskrnl.exeT1027T1106

NtWaitForKeyedEvent

Bloquea el hilo llamante en un keyed event hasta que otro hilo libere el mismo par (event, clave).

Prototipo

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

Argumentos

NameTypeDirDescription
KeyedEventHandleHANDLEinHandle a un keyed event. NULL usa el predeterminado por proceso en \KernelObjects\CritSecOutOfMemoryEvent.
KeyPVOIDinValor de rendez-vous del tamaño de un puntero. Debe estar alineado naturalmente (bit bajo a cero). Convencionalmente la dirección de la critical section / palabra de cerrojo.
AlertableBOOLEANinTRUE hace que la espera sea interrumpible por APC de user-mode; casi siempre FALSE para primitivas de cerrojo.
TimeoutPLARGE_INTEGERinTiempo de espera opcional (unidades de 100 ns, negativo = relativo). NULL = espera infinita. Devuelve STATUS_TIMEOUT al expirar.

IDs de syscalls por versión de Windows

Versión 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

Módulo del kernel

ntoskrnl.exeNtWaitForKeyedEvent

APIs relacionadas

NtCreateKeyedEventNtOpenKeyedEventNtReleaseKeyedEventWaitOnAddressWakeByAddressSingleRtlEnterCriticalSection

Stub del 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

Notas no documentadas

La mitad wait del par keyed-event. Usada internamente por `ntdll!RtlEnterCriticalSection` como **fallback en baja memoria** cuando la contención de una critical section requeriría normalmente asignar un objeto event y el sistema está sin pool. En esa ruta, `ntdll.dll` pasa `peb->KeyedEventHandle` (el global `CritSecOutOfMemoryEvent`) y usa la dirección del campo `LockSemaphore` de la critical section como clave, de modo que todo hilo en contención del sistema comparte un único keyed event global. `NtWaitForKeyedEvent` también es una de las dos primitivas detrás de `WaitOnAddress` (la otra es `KeWaitForSingleObject` sobre un event thread-local); en builds modernos, `WaitOnAddress` es el punto de entrada user-mode recomendado.

Uso común por malware

Señal modesta. Diseños de obfuscación de sleep que quieren una primitiva de sincronía sin nombrar ningún objeto de kernel emparejan `NtWaitForKeyedEvent` (en el hilo dormido) con `NtReleaseKeyedEvent` (en un gadget ROP de timer de wakeup o un APC). Como el handle global ya está abierto en cada proceso, el esquema no requiere crear ningún handle nuevo — esquivando sandboxes rudimentarias que escanean tablas de handles buscando events / semáforos recién creados. La familia Ekko y varias demos públicas de sleep-mask dependen de esto. Uso directo y deliberado de `NtWaitForKeyedEvent` desde un binario no-sistema fuera de ese patrón es más bien indicio de implementación de cerrojo casera que señal de malware.

Oportunidades de detección

Misma disposición que `NtCreateKeyedEvent`: sin superficie ETW o Sysmon útil. El único ángulo blue-team significativo es *comportamental* — un hilo bloqueado en `NtWaitForKeyedEvent` sobre el handle global durante un tiempo anormalmente largo, mientras su módulo contenedor vive en memoria no respaldada / solo RX (típico de beacons con sleep-mask), es sospechoso. ETW Threat Intelligence + introspección de regiones de memoria (p. ej. `MemoryWorkingSetExInformation` mostrando páginas `MEMORY_REGION_PRIVATE`) es más informativo que trazar el syscall mismo.

Ejemplos de syscalls directos

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);
}

Mapeos MITRE ATT&CK

Last verified: 2026-05-20