> Windows Syscalls
ntoskrnl.exeT1027.011T1027T1106

NtSignalAndWaitForSingleObject

Signale atomiquement un objet dispatcher et attend sur un autre en une transition unique sans course.

Prototype

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

Arguments

NameTypeDirDescription
SignalHandleHANDLEinHandle vers l'objet à signaler (event avec accès SET, mutant détenu par l'appelant, ou semaphore avec MODIFY_STATE). Doit être un des types dispatcher signalables.
WaitHandleHANDLEinHandle vers l'objet à attendre (tout objet dispatcher attendable — event, mutant, semaphore, processus, thread, timer, ...). Doit accorder SYNCHRONIZE.
AlertableBOOLEANinSi TRUE, l'attente peut être interrompue par des APC user-mode (NtQueueApcThread), retournant STATUS_USER_APC.
TimeoutPLARGE_INTEGERinTimeout optionnel : NULL = infini, négatif = unités relatives 100ns, positif = temps absolu. STATUS_TIMEOUT (0x102) signale expiration.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
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

Module noyau

ntoskrnl.exeNtSignalAndWaitForSingleObject

APIs liées

SignalObjectAndWaitNtWaitForSingleObjectNtWaitForMultipleObjectsNtSetEventNtCreateEvent

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

Notes non documentées

L'implémentation noyau derrière `SignalObjectAndWait`. La propriété définissante est l'*atomicité* : le signal et l'attente se produisent comme une transition unique du thread, sans fenêtre d'instruction entre les deux durant laquelle un autre thread pourrait observer le signal et rendez-vous avant que ce thread se soit garé sur son objet d'attente. À comparer avec la paire naïve `SetEvent(a); WaitForSingleObject(b, ...)`, qui est deux syscalls distincts avec un écart visible en userland. Le SSN dérive à chaque release majeure (`0x1B0` 1903, `0x1B6` 2004, `0x1C8` 24H2 / Server 2025). Le flag Alertable est ce qui rend les sleep-masks Ekko-family viables : l'attente peut être terminée par un APC, mais la séquence signal-puis-attente reste atomique.

Usage courant par les malwares

La pierre angulaire des conceptions **sleep-mask** sans course. Le motif Ekko / Foliage utilise trois étages de timer-queue dont le handoff est chorégraphié par `NtSignalAndWaitForSingleObject` — à chaque frontière de phase, le thread beacon signale atomiquement « phase N terminée, je suis garé » et attend sur le prochain event que la chaîne de gadgets ROP déclenchera. Sans atomicité, un scanner mémoire qui observe le flag « je suis chiffré » et fait la course pour lire la mémoire avant que l'attente ne s'enregistre peut gagner une fenêtre de course sous-microseconde — l'atomicité écrase cette fenêtre à zéro. La même primitive est utilisée dans la synchronisation Windows classique (le `_endthreadex` du runtime C l'utilise pour signaler l'event de fin de thread et s'auto-terminer, libérant atomiquement l'état par-thread du runtime). Certains canaux C2 red-team l'utilisent aussi pour un handoff de dequeue atomique dans des pools de workers lock-free.

Opportunités de détection

Pris isolément, NtSignalAndWaitForSingleObject est mainstream — les thread pools Win32 (`PTP_WORK`), le runtime C, et de nombreuses primitives internes .NET enchaînent à travers lui. Ce n'est *pas* un signal primaire utile. Le motif intéressant est la corrélation avec la chaîne télémétrique sleep-mask Ekko : création de timer-queue (`NtCreateTimer`), trois events ou plus créés dos-à-dos, une région RWX verrouillée via `NtLockVirtualMemory`, puis un motif `NtSignalAndWaitForSingleObject` avec timeouts dans la fourchette secondes-à-minutes. Les scanners mémoire qui snapshottent les octets de working set à intervalles aléatoires — plutôt que de réagir aux syscalls — contournent toute cette classe d'évasion car ils ne dépendent pas de l'observation du handoff atomique.

Exemples de syscalls directs

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

Mappings MITRE ATT&CK

Last verified: 2026-05-20