> Windows Syscalls
ntoskrnl.exeT1106T1027

NtWaitForAlertByThreadId

Parque le thread appelant jusqu'à ce que NtAlertThreadByThreadId le réveille — la face noyau de WaitOnAddress.

Prototype

NTSTATUS NtWaitForAlertByThreadId(
  PVOID          Address,
  PLARGE_INTEGER Timeout
);

Arguments

NameTypeDirDescription
AddressPVOIDinPointeur opaque utilisé comme clé de hachage dans la table d'attente par processus — typiquement l'adresse du mot de synchro user-mode.
TimeoutPLARGE_INTEGERinIntervalle 100-ns optionnel. Négatif = timeout relatif. NULL = attente infinie. Renvoie STATUS_TIMEOUT à l'expiration.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x1B3win10-1507
Win10 16070x1BCwin10-1607
Win10 17030x1C2win10-1703
Win10 17090x1C6win10-1709
Win10 18030x1C8win10-1803
Win10 18090x1C9win10-1809
Win10 19030x1CAwin10-1903
Win10 19090x1CAwin10-1909
Win10 20040x1D0win10-2004
Win10 20H20x1D0win10-20h2
Win10 21H10x1D0win10-21h1
Win10 21H20x1D2win10-21h2
Win10 22H20x1D2win10-22h2
Win11 21H20x1DCwin11-21h2
Win11 22H20x1E0win11-22h2
Win11 23H20x1E0win11-23h2
Win11 24H20x1E3win11-24h2
Server 20160x1BCwinserver-2016
Server 20190x1C9winserver-2019
Server 20220x1D8winserver-2022
Server 20250x1E3winserver-2025

Module noyau

ntoskrnl.exeNtWaitForAlertByThreadId

APIs liées

WaitOnAddressWakeByAddressSingleWakeByAddressAllNtAlertThreadByThreadIdNtDelayExecutionRtlWaitOnAddress

Stub du syscall

4C 8B D1            mov r10, rcx
B8 E3 01 00 00      mov eax, 0x1E3
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

Pendant de `NtAlertThreadByThreadId`. L'argument `Address` n'est *pas* déréférencé par le noyau — c'est simplement une clé de hachage routée dans la table d'attente de l'EPROCESS (KiWaitChainList / RtlpHashAddress). Quand `NtAlertThreadByThreadId` est appelé avec le TID correspondant, le noyau parcourt le bucket, trouve le thread correspondant et le signale. Le hachage par adresse permet la sémantique économique sans objet de `WaitOnAddress`. La plage de SSN (0x1B3 → 0x1E3) est l'une des plus volatiles de ntoskrnl — elle bouge presque à chaque mise à jour parce qu'elle se trouve en queue de la SST et que la table grandit derrière.

Usage courant par les malwares

Utilisé par les implémentations modernes de sleep-mask (style Ekko et successeurs) qui veulent une primitive d'attente *non* adossée à un objet Event/Mutex. Garez le thread beacon dormant dans ce syscall avec un long timeout ; signalez-le avec `NtAlertThreadByThreadId` quand du travail arrive. Aucune entrée `\BaseNamedObjects\…`, aucun handle visible dans la vue handles de System Informer, aucun événement `Microsoft-Windows-Kernel-Object` de création. Combiné à un polling style `WaitOnAddress` sur un mot d'état, l'implant peut se faire passer pour n'importe quel processus Win32 moderne utilisant SRWLOCK ou les nouveaux internals de std::mutex. Le volume légitime en fait une cible primaire de détection médiocre, ce qui en fait justement l'attrait pour les auteurs.

Opportunités de détection

Microsoft-Windows-Threat-Intelligence n'émet pas d'événement par appel. La seule anomalie réaliste est *fréquence × contexte* : un thread passant l'essentiel de son temps dans `NtWaitForAlertByThreadId` avec un timeout relatif non NULL, dans un processus avec egress réseau, mapping RX-only, créé par un enfant d'explorer.exe ou d'une appli Office. Les scanners mémoire (Moneta, Hunt-Sleeping-Beacons) attrapent la région RX environnante ; le syscall n'est qu'une couleur. Les EDR signalent parfois le pattern d'appel inhabituel (alert/wait depuis une DLL non système) mais le taux de faux positifs est élevé car les runtimes C++ modernes empruntent le même chemin.

Exemples de syscalls directs

cWaitOnAddress-style park

// Park a worker thread until a partner calls NtAlertThreadByThreadId
// with our TID. No event handle ever exists.
typedef NTSTATUS(NTAPI* fnWait)(PVOID, PLARGE_INTEGER);

void ParkWorker(volatile LONG* statusWord, ULONGLONG ns100) {
    HMODULE n = GetModuleHandleA("ntdll.dll");
    fnWait p = (fnWait)GetProcAddress(n, "NtWaitForAlertByThreadId");
    LARGE_INTEGER t;
    t.QuadPart = -(LONGLONG)ns100; // relative timeout
    p((PVOID)statusWord, &t);
}

asmx64 direct stub (Win11 24H2 SSN)

; SSN 0x1E3 on win11-24h2 / winserver-2025. Moves on nearly every feature update.
NtWaitForAlertByThreadId PROC
    mov  r10, rcx
    mov  eax, 1E3h
    syscall
    ret
NtWaitForAlertByThreadId ENDP

rustSleep-mask park with timeout

// Park between beacon cycles using the address of a status word as hash key.
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};

type NtWaitForAlertByThreadId = unsafe extern "system" fn(addr: *mut u8, to: *mut i64) -> i32;

pub unsafe fn park(status: *mut u32, hundred_ns: i64) -> i32 {
    let n = GetModuleHandleA(b"ntdll.dll\0".as_ptr());
    let addr = GetProcAddress(n, b"NtWaitForAlertByThreadId\0".as_ptr()).unwrap();
    let f: NtWaitForAlertByThreadId = std::mem::transmute(addr);
    let mut t: i64 = -hundred_ns;
    f(status as *mut u8, &mut t)
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20