NtWaitForSingleObject
Espera hasta que un objeto dispatcher se señale o expire el timeout opcional.
Prototipo
NTSTATUS NtWaitForSingleObject( HANDLE Handle, BOOLEAN Alertable, PLARGE_INTEGER Timeout );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| Handle | HANDLE | in | Handle a un objeto dispatcher esperable (event, mutex, thread, process, timer, semaphore, ...) con acceso SYNCHRONIZE. |
| Alertable | BOOLEAN | in | TRUE coloca el hilo en estado alertable para que los APC de usuario en cola se ejecuten y aborten la espera con STATUS_USER_APC. |
| Timeout | PLARGE_INTEGER | in | Timeout opcional en unidades de 100 ns; negativo = relativo, positivo = absoluto, NULL = INFINITE. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x4 | win10-1507 |
| Win10 1607 | 0x4 | win10-1607 |
| Win10 1703 | 0x4 | win10-1703 |
| Win10 1709 | 0x4 | win10-1709 |
| Win10 1803 | 0x4 | win10-1803 |
| Win10 1809 | 0x4 | win10-1809 |
| Win10 1903 | 0x4 | win10-1903 |
| Win10 1909 | 0x4 | win10-1909 |
| Win10 2004 | 0x4 | win10-2004 |
| Win10 20H2 | 0x4 | win10-20h2 |
| Win10 21H1 | 0x4 | win10-21h1 |
| Win10 21H2 | 0x4 | win10-21h2 |
| Win10 22H2 | 0x4 | win10-22h2 |
| Win11 21H2 | 0x4 | win11-21h2 |
| Win11 22H2 | 0x4 | win11-22h2 |
| Win11 23H2 | 0x4 | win11-23h2 |
| Win11 24H2 | 0x4 | win11-24h2 |
| Server 2016 | 0x4 | winserver-2016 |
| Server 2019 | 0x4 | winserver-2019 |
| Server 2022 | 0x4 | winserver-2022 |
| Server 2025 | 0x4 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 04 00 00 00 mov eax, 0x4 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
Despacha a través de `KeWaitForSingleObject` dentro de ntoskrnl.exe. La SSN `0x04` es estable en todas las builds Win10/11 — una de las más estables de la tabla. El flag `Alertable` es la clave: una espera no alertable bloquea solo sobre el objeto, mientras que una espera alertable además drena los APCs de usuario pendientes y devuelve `STATUS_USER_APC` (0x000000C0) cuando uno se ejecuta. Por eso exactamente las cadenas de inyección APC (T1055.004) requieren que el hilo objetivo realice una espera alertable. El timeout va en unidades NT de 100 ns; los valores `LARGE_INTEGER` negativos son relativos (p. ej. `-10000000LL` == 1 segundo).
Uso común por malware
Dos abusos completamente distintos comparten esta syscall. (1) Las esperas alertables son el *vehículo de entrega* de los APCs de usuario encolados — las sleep masks tipo Ekko esperan alertablemente sobre el evento que su APC de timer señala; la inyección EarlyBird APC depende de que `ntdll!LdrpInitializeProcess` ejecute una espera alertable. (2) El polling de timeout corto sobre un handle sintético es una **sonda anti-sandbox / aceleración de tiempo** barata: el malware compara el delta wall-clock con la duración esperada; las sandboxes que aceleran sleeps fallan el chequeo. Algunos loaders también esperan sobre un handle de hilo para serializar descifrado multi-etapa.
Oportunidades de detección
Las llamadas wait son ubicuas; los conteos crudos son inútiles. La señal interesante es la *combinación* `NtWaitForSingleObject(..., Alertable=TRUE, ...)` sobre un handle cuyo objeto es un evento nombrado privado, ejecutada por un hilo cuyo código está en memoria no respaldada / RWX — esa es la huella Ekko / Foliage. ETW Threat Intelligence no emite un evento wait pero sí el evento QueueApc que se empareja con él; correlacionar QueueApc con un retorno posterior de espera alertable en el mismo hilo captura las cadenas APC. El stack-walk al retorno del wait es el patrón oro.
Ejemplos de syscalls directos
asmx64 direct stub
; Direct syscall stub for NtWaitForSingleObject (SSN 0x04, stable across Win10/11)
NtWaitForSingleObject PROC
mov r10, rcx ; syscall convention
mov eax, 04h ; SSN
syscall
ret
NtWaitForSingleObject ENDPcAlertable INFINITE wait (APC delivery point)
// Block alertably on an event until either it signals or a queued APC fires.
NTSTATUS st;
do {
st = NtWaitForSingleObject(hEvent, TRUE, NULL); // NULL == INFINITE
} while (st == STATUS_USER_APC); // re-enter wait after the APC ran
if (!NT_SUCCESS(st)) return st;rustTime-acceleration sandbox probe
// Cargo: windows-sys = "0.59"
use windows_sys::Win32::System::Threading::{WaitForSingleObjectEx, INFINITE};
use std::time::Instant;
unsafe fn sandbox_speeds_up_sleeps(h_event: *mut core::ffi::c_void) -> bool {
let start = Instant::now();
// Wait 3s alertably on a never-signalled event.
let _ = WaitForSingleObjectEx(h_event, 3000, 1);
start.elapsed().as_millis() < 2500
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20