> Windows Syscalls
ntoskrnl.exeT1027.011T1027T1106

NtSignalAndWaitForSingleObject

Atomically signals one dispatcher object and waits on another in a single, race-free transition.

Prototype

NTSTATUS NtSignalAndWaitForSingleObject(
  HANDLE         SignalHandle,
  HANDLE         WaitHandle,
  BOOLEAN        Alertable,
  PLARGE_INTEGER Timeout
);

Arguments

NameTypeDirDescription
SignalHandleHANDLEinHandle to the object to signal (event with SET access, mutant the caller owns, or semaphore with MODIFY_STATE). Must be one of the signalable dispatcher types.
WaitHandleHANDLEinHandle to the object to wait on (any waitable dispatcher object — event, mutant, semaphore, process, thread, timer, ...). Must grant SYNCHRONIZE.
AlertableBOOLEANinIf TRUE, the wait can be interrupted by user-mode APCs (NtQueueApcThread), returning STATUS_USER_APC.
TimeoutPLARGE_INTEGERinOptional timeout: NULL = infinite, negative = relative 100ns units, positive = absolute time. STATUS_TIMEOUT (0x102) signals expiry.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x19Awin10-1507
Win10 16070x1A3win10-1607
Win10 17030x1A9win10-1703
Win10 17090x1ACwin10-1709
Win10 18030x1AEwin10-1803
Win10 18090x1AFwin10-1809
Win10 19030x1B0win10-1903
Win10 19090x1B0win10-1909
Win10 20040x1B6win10-2004
Win10 20H20x1B6win10-20h2
Win10 21H10x1B6win10-21h1
Win10 21H20x1B8win10-21h2
Win10 22H20x1B8win10-22h2
Win11 21H20x1C1win11-21h2
Win11 22H20x1C5win11-22h2
Win11 23H20x1C5win11-23h2
Win11 24H20x1C8win11-24h2
Server 20160x1A3winserver-2016
Server 20190x1AFwinserver-2019
Server 20220x1BEwinserver-2022
Server 20250x1C8winserver-2025

Kernel module

ntoskrnl.exeNtSignalAndWaitForSingleObject

Related APIs

SignalObjectAndWaitNtWaitForSingleObjectNtWaitForMultipleObjectsNtSetEventNtCreateEvent

Syscall stub

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

Undocumented notes

The kernel implementation behind `SignalObjectAndWait`. The defining property is *atomicity*: the signal and the wait happen as a single thread transition, with no instruction window in between in which another thread can observe the signal and rendezvous before this thread has parked on its wait object. Compare with the naive `SetEvent(a); WaitForSingleObject(b, ...)` pair, which is two distinct syscalls with a userland-visible gap. The SSN drifts each major release (`0x1B0` 1903, `0x1B6` 2004, `0x1C8` 24H2 / Server 2025). The Alertable flag is what makes Ekko-family sleep masks workable: the wait can be terminated by an APC, but the signal-then-wait sequence remains atomic.

Common malware usage

The cornerstone of race-free **sleep-mask** designs. The Ekko / Foliage pattern uses three timer-queue stages whose handoff is choreographed by `NtSignalAndWaitForSingleObject` — at each phase boundary, the beacon thread atomically signals "phase N done, I'm parked" and waits on the next event the ROP-gadget chain will fire. Without atomicity, a memory scanner that observes the "I'm encrypted" flag and then races to read memory before the wait registers can win a sub-microsecond race window — atomicity collapses that window to zero. The same primitive is used in classic Windows synchronization (the C runtime's `_endthreadex` uses it to signal the thread-end event and self-terminate, atomically releasing the runtime's per-thread state). Some red-team C2 channels also use it for atomic dequeue handoff in lock-free worker pools.

Detection opportunities

By itself, NtSignalAndWaitForSingleObject is mainstream — Win32 thread pools (`PTP_WORK`), C runtime, and many .NET internal primitives all chain through it. It is *not* a useful primary signal. The interesting pattern is correlation with the Ekko sleep-mask telemetry chain: timer-queue creation (`NtCreateTimer`), three or more events created back-to-back, an RWX region locked via `NtLockVirtualMemory`, and then a `NtSignalAndWaitForSingleObject` pattern with timeouts in the seconds-to-minutes range. Memory scanners that snapshot working-set bytes at random intervals — rather than reacting to syscalls — sidestep this whole class of evasion because they don't depend on observing the atomic handoff.

Direct syscall examples

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 ENDP

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

MITRE ATT&CK mappings

Last verified: 2026-05-20