NtSignalAndWaitForSingleObject
Señaliza atómicamente un objeto dispatcher y espera sobre otro en una única transición libre de carrera.
Prototipo
NTSTATUS NtSignalAndWaitForSingleObject( HANDLE SignalHandle, HANDLE WaitHandle, BOOLEAN Alertable, PLARGE_INTEGER Timeout );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| SignalHandle | HANDLE | in | Handle al objeto a señalizar (event con acceso SET, mutant propiedad del llamador o semaphore con MODIFY_STATE). Debe ser uno de los tipos dispatcher señalizables. |
| WaitHandle | HANDLE | in | Handle al objeto a esperar (cualquier objeto dispatcher esperable — event, mutant, semaphore, process, thread, timer, ...). Debe otorgar SYNCHRONIZE. |
| Alertable | BOOLEAN | in | Si TRUE, la espera puede ser interrumpida por APC user-mode (NtQueueApcThread), retornando STATUS_USER_APC. |
| Timeout | PLARGE_INTEGER | in | Timeout opcional: NULL = infinito, negativo = unidades relativas 100ns, positivo = tiempo absoluto. STATUS_TIMEOUT (0x102) señaliza expiración. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x19A | win10-1507 |
| Win10 1607 | 0x1A3 | win10-1607 |
| Win10 1703 | 0x1A9 | win10-1703 |
| Win10 1709 | 0x1AC | win10-1709 |
| Win10 1803 | 0x1AE | win10-1803 |
| Win10 1809 | 0x1AF | win10-1809 |
| Win10 1903 | 0x1B0 | win10-1903 |
| Win10 1909 | 0x1B0 | win10-1909 |
| Win10 2004 | 0x1B6 | win10-2004 |
| Win10 20H2 | 0x1B6 | win10-20h2 |
| Win10 21H1 | 0x1B6 | win10-21h1 |
| Win10 21H2 | 0x1B8 | win10-21h2 |
| Win10 22H2 | 0x1B8 | win10-22h2 |
| Win11 21H2 | 0x1C1 | win11-21h2 |
| Win11 22H2 | 0x1C5 | win11-22h2 |
| Win11 23H2 | 0x1C5 | win11-23h2 |
| Win11 24H2 | 0x1C8 | win11-24h2 |
| Server 2016 | 0x1A3 | winserver-2016 |
| Server 2019 | 0x1AF | winserver-2019 |
| Server 2022 | 0x1BE | winserver-2022 |
| Server 2025 | 0x1C8 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 C8 01 00 00 mov eax, 0x1C8 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 implementación kernel detrás de `SignalObjectAndWait`. La propiedad definitoria es la *atomicidad*: la señal y la espera ocurren como una sola transición de hilo, sin ventana de instrucciones intermedia donde otro hilo pueda observar la señal y hacer rendez-vous antes de que este hilo se haya estacionado en su objeto de espera. Comparar con el par ingenuo `SetEvent(a); WaitForSingleObject(b, ...)`, que son dos syscalls distintos con un hueco visible en userland. El SSN deriva en cada release mayor (`0x1B0` 1903, `0x1B6` 2004, `0x1C8` 24H2 / Server 2025). El flag Alertable es lo que hace viables los sleep-masks tipo Ekko: la espera puede ser terminada por un APC, pero la secuencia señalar-luego-esperar permanece atómica.
Uso común por malware
La piedra angular de los diseños de **sleep-mask** libres de carrera. El patrón Ekko / Foliage usa tres etapas de timer-queue cuyo handoff está coreografiado por `NtSignalAndWaitForSingleObject` — en cada límite de fase, el hilo del beacon señaliza atómicamente «fase N terminada, estoy estacionado» y espera sobre el siguiente event que la cadena de gadgets ROP disparará. Sin atomicidad, un escáner de memoria que observe el flag «estoy cifrado» y carrere a leer memoria antes de que la espera se registre puede ganar una ventana de carrera sub-microsegundo — la atomicidad colapsa esa ventana a cero. La misma primitiva se usa en la sincronización clásica de Windows (el `_endthreadex` del runtime C la usa para señalizar el event de fin de hilo y auto-terminar, liberando atómicamente el estado por hilo del runtime). Algunos canales C2 red-team también la usan para handoff atómico de dequeue en pools de workers lock-free.
Oportunidades de detección
Por sí solo, NtSignalAndWaitForSingleObject es mainstream — los thread pools de Win32 (`PTP_WORK`), el runtime C y muchas primitivas internas de .NET pasan por él. *No* es una señal primaria útil. El patrón interesante es la correlación con la cadena telemétrica de sleep-mask Ekko: creación de timer-queue (`NtCreateTimer`), tres o más events creados consecutivamente, una región RWX bloqueada vía `NtLockVirtualMemory`, y luego un patrón `NtSignalAndWaitForSingleObject` con timeouts en el rango de segundos a minutos. Los escáneres de memoria que snapshotean bytes del working set a intervalos aleatorios — en lugar de reaccionar a syscalls — esquivan toda esta clase de evasión porque no dependen de observar el handoff atómico.
Ejemplos de syscalls directos
asmx64 stub (Win11 24H2 SSN 0x1C8)
; Direct syscall stub for NtSignalAndWaitForSingleObject
NtSignalAndWaitForSingleObject PROC
mov r10, rcx ; syscall convention
mov eax, 1C8h ; SSN (Win11 24H2 / Server 2025)
syscall
ret
NtSignalAndWaitForSingleObject ENDPcEkko-style atomic sleep handoff
// Phase handoff in an Ekko sleep mask: atomically signal "encrypted,
// parked" and wait for the wake event. No userland gap for a scanner
// to race into.
#include <windows.h>
typedef NTSTATUS (NTAPI *pNtSignalAndWaitForSingleObject)(
HANDLE Signal, HANDLE Wait, BOOLEAN Alertable, PLARGE_INTEGER Timeout);
NTSTATUS SleepHandoff(HANDLE h_encrypted_done, HANDLE h_wake) {
pNtSignalAndWaitForSingleObject NtSignalAndWaitForSingleObject =
(pNtSignalAndWaitForSingleObject)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtSignalAndWaitForSingleObject");
// Wait alertable so a timer APC can wake us; infinite timeout.
return NtSignalAndWaitForSingleObject(
h_encrypted_done, h_wake, TRUE, NULL);
}rustRendezvous primitive (race-free)
// SignalObjectAndWait wraps NtSignalAndWaitForSingleObject.
// Use it to atomically release a producer event and park on the consumer
// event in a lock-free queue worker.
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::Threading::SignalObjectAndWait;
pub unsafe fn handoff(signal: HANDLE, wait: HANDLE, ms: u32) -> u32 {
SignalObjectAndWait(signal, wait, ms, /* alertable */ 0)
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20