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
| Name | Type | Dir | Description |
|---|---|---|---|
| KeyedEventHandle | HANDLE | in | Handle a un keyed event. NULL usa el predeterminado por proceso en \KernelObjects\CritSecOutOfMemoryEvent. |
| Key | PVOID | in | Valor 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. |
| Alertable | BOOLEAN | in | TRUE hace que la espera sea interrumpible por APC de user-mode; casi siempre FALSE para primitivas de cerrojo. |
| Timeout | PLARGE_INTEGER | in | Tiempo 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 Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x1B5 | win10-1507 |
| Win10 1607 | 0x1BE | win10-1607 |
| Win10 1703 | 0x1C4 | win10-1703 |
| Win10 1709 | 0x1C8 | win10-1709 |
| Win10 1803 | 0x1CA | win10-1803 |
| Win10 1809 | 0x1CB | win10-1809 |
| Win10 1903 | 0x1CC | win10-1903 |
| Win10 1909 | 0x1CC | win10-1909 |
| Win10 2004 | 0x1D2 | win10-2004 |
| Win10 20H2 | 0x1D2 | win10-20h2 |
| Win10 21H1 | 0x1D2 | win10-21h1 |
| Win10 21H2 | 0x1D4 | win10-21h2 |
| Win10 22H2 | 0x1D4 | win10-22h2 |
| Win11 21H2 | 0x1DE | win11-21h2 |
| Win11 22H2 | 0x1E2 | win11-22h2 |
| Win11 23H2 | 0x1E2 | win11-23h2 |
| Win11 24H2 | 0x1E5 | win11-24h2 |
| Server 2016 | 0x1BE | winserver-2016 |
| Server 2019 | 0x1CB | winserver-2019 |
| Server 2022 | 0x1DA | winserver-2022 |
| Server 2025 | 0x1E5 | winserver-2025 |
Módulo del kernel
APIs relacionadas
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 ENDPcCritical-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