NtAlertThreadByThreadId
Despierta un único hilo (identificado por su TID) parado en NtWaitForAlertByThreadId — la cara kernel de WakeByAddressSingle.
Prototipo
NTSTATUS NtAlertThreadByThreadId( HANDLE ThreadId );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadId | HANDLE | in | TID del hilo a despertar, como valor con forma de HANDLE (campo ClientId.UniqueThread del TEB). |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x6E | win10-1507 |
| Win10 1607 | 0x6E | win10-1607 |
| Win10 1703 | 0x6F | win10-1703 |
| Win10 1709 | 0x6F | win10-1709 |
| Win10 1803 | 0x6F | win10-1803 |
| Win10 1809 | 0x6F | win10-1809 |
| Win10 1903 | 0x6F | win10-1903 |
| Win10 1909 | 0x6F | win10-1909 |
| Win10 2004 | 0x70 | win10-2004 |
| Win10 20H2 | 0x70 | win10-20h2 |
| Win10 21H1 | 0x70 | win10-21h1 |
| Win10 21H2 | 0x70 | win10-21h2 |
| Win10 22H2 | 0x70 | win10-22h2 |
| Win11 21H2 | 0x70 | win11-21h2 |
| Win11 22H2 | 0x70 | win11-22h2 |
| Win11 23H2 | 0x70 | win11-23h2 |
| Win11 24H2 | 0x71 | win11-24h2 |
| Server 2016 | 0x6E | winserver-2016 |
| Server 2019 | 0x6F | winserver-2019 |
| Server 2022 | 0x70 | winserver-2022 |
| Server 2025 | 0x71 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 71 00 00 00 mov eax, 0x71 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
Introducido en Windows 8 y muy usado desde Windows 10 como el sustrato kernel de `WakeByAddressSingle` / `WakeByAddressAll`. Junto con `NtWaitForAlertByThreadId`, este par implementa alertas por hilo que *no* requieren objeto kernel nombrado (ni Event, ni Mutex, ni Semaphore). El estado de sincronización es simplemente una dirección en modo usuario que el kernel hashea en una tabla de espera por proceso; el kernel solo necesita el TID destino y no hace lookup en el object manager, lo que abarata muchísimo la ruta de despacho. Las SRWLOCK, el nuevo `WaitOnAddress` y la implementación moderna de variables de condición de ntdll se apoyan en esta primitiva.
Uso común por malware
Sincronización de baja señal para sleep-masks, planificación de beacons y bombas de comandos. Como no se crea ni se nombra ningún objeto kernel, no hay artefactos `\BaseNamedObjects\…` que herramientas como ProcessHacker / SystemInformer puedan mostrar — ese es el principal atractivo frente a `NtSetEvent` / `NtWaitForSingleObject`. Algunas variantes de sleep-mask derivadas de Cobalt Strike y los backends de planificación más recientes de Sliver usan `WaitOnAddress` (y por tanto este syscall) para aparcar hilos workers entre ciclos de beacon. A mediados de 2026 sigue siendo un syscall de *baja señal* maliciosa: la misma ruta la usa cualquier programa Win32 moderno con SRWLOCK o std::mutex en MSVC, el volumen legítimo entierra el uso malicioso.
Oportunidades de detección
Prácticamente indetectable por sí solo — `NtAlertThreadByThreadId` lo invocan la liberación de SRWLOCK, el wake de variable de condición y todo unlock de `std::mutex` C++17/20 que encuentra un waiter. Los EDR no lo hookean porque el suelo de ruido es enorme. El único ángulo útil es la *combinación* con telemetría de sleep-mask: retornos cortos y periódicos de NtWaitForAlertByThreadId combinados con NtAlertThreadByThreadId desde un único hilo no-sistema, en un proceso que también hace beaconing, *más* una región de código RX-only — ahí el syscall es señal corroborativa, nunca primaria.
Ejemplos de syscalls directos
cStealth wake of a parked worker
// Replace SetEvent on a globally named event with WaitOnAddress/WakeByAddressSingle
// to avoid creating an object visible to handle-listers.
typedef NTSTATUS(NTAPI* fnNtAlertThreadByThreadId)(HANDLE);
void PokeWorker(DWORD workerTid) {
HMODULE n = GetModuleHandleA("ntdll.dll");
fnNtAlertThreadByThreadId p = (fnNtAlertThreadByThreadId)
GetProcAddress(n, "NtAlertThreadByThreadId");
p((HANDLE)(ULONG_PTR)workerTid);
}asmx64 direct stub (Win11 24H2 SSN)
; SSN 0x71 on win11-24h2 / winserver-2025. 0x70 on most prior 20H2+ builds.
NtAlertThreadByThreadId PROC
mov r10, rcx
mov eax, 71h
syscall
ret
NtAlertThreadByThreadId ENDPrustPair with WaitOnAddress equivalent
// Wake a worker parked in NtWaitForAlertByThreadId without ever instantiating
// a named kernel object. Useful in implants that want to look like a normal
// Win32 program using SRWLOCK.
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
type NtAlertThreadByThreadId = unsafe extern "system" fn(tid: isize) -> i32;
pub unsafe fn wake_worker(tid: u32) -> i32 {
let n = GetModuleHandleA(b"ntdll.dll\0".as_ptr());
let addr = GetProcAddress(n, b"NtAlertThreadByThreadId\0".as_ptr()).unwrap();
let f: NtAlertThreadByThreadId = std::mem::transmute(addr);
f(tid as isize)
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20