> Windows Syscalls
ntoskrnl.exeT1055T1106

NtSetIoCompletion

Poste un paquet de complétion sur un port de complétion d'E/S — le côté noyau de PostQueuedCompletionStatus et le vecteur de livraison des items forgés de PoolParty.

Prototype

NTSTATUS NtSetIoCompletion(
  HANDLE     IoCompletionHandle,
  PVOID      KeyContext,
  PVOID      ApcContext,
  NTSTATUS   IoStatus,
  ULONG_PTR  IoStatusInformation
);

Arguments

NameTypeDirDescription
IoCompletionHandleHANDLEinHandle vers le port de complétion cible. Nécessite IO_COMPLETION_MODIFY_STATE.
KeyContextPVOIDinCompletionKey défini par l'appelant, renvoyé au défileur via lpCompletionKey.
ApcContextPVOIDinPointeur défini par l'appelant renvoyé en lpOverlapped — généralement un OVERLAPPED* en code légitime, contrôlé par l'attaquant dans PoolParty.
IoStatusNTSTATUSinCode de statut écrit dans le champ Status de l'IO_STATUS_BLOCK du paquet.
IoStatusInformationULONG_PTRinValeur définie par l'appelant écrite dans IO_STATUS_BLOCK.Information — typiquement un nombre d'octets transférés.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x185win10-1507
Win10 16070x18Ewin10-1607
Win10 17030x194win10-1703
Win10 17090x197win10-1709
Win10 18030x199win10-1803
Win10 18090x19Awin10-1809
Win10 19030x19Bwin10-1903
Win10 19090x19Bwin10-1909
Win10 20040x1A1win10-2004
Win10 20H20x1A1win10-20h2
Win10 21H10x1A1win10-21h1
Win10 21H20x1A3win10-21h2
Win10 22H20x1A3win10-22h2
Win11 21H20x1ACwin11-21h2
Win11 22H20x1B0win11-22h2
Win11 23H20x1B0win11-23h2
Win11 24H20x1B3win11-24h2
Server 20160x18Ewinserver-2016
Server 20190x19Awinserver-2019
Server 20220x1A9winserver-2022
Server 20250x1B3winserver-2025

Module noyau

ntoskrnl.exeNtSetIoCompletion

APIs liées

PostQueuedCompletionStatusNtCreateIoCompletionNtRemoveIoCompletionNtRemoveIoCompletionExNtWaitForWorkViaWorkerFactorySubmitThreadpoolWork

Stub du syscall

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

Équivalent direct de `PostQueuedCompletionStatus`. Le paquet est ajouté à la KQUEUE du port et un attendeur (dans NtRemoveIoCompletion, NtRemoveIoCompletionEx ou NtWaitForWorkViaWorkerFactory) est réveillé. Le noyau n'interprète pas `KeyContext` ni `ApcContext` — ils sont renvoyés tels quels. Cette opacité rend le syscall très polyvalent pour le dispatch d'E/S asynchrone légitime comme pour l'injection malicieuse de paquets forgés.

Usage courant par les malwares

La variante PoolParty « Worker via Completion Port » : ouvrir le port de complétion du threadpool de la victime (souvent via un handle fuité ou NtDuplicateObject), puis émettre NtSetIoCompletion avec `ApcContext` pointant sur une structure de la forme `TP_DIRECT` — quand ntdll!TppWorkerThread défile le paquet, son dispatcher suit les pointeurs et appelle la fonction choisie par l'attaquant. Crucialement, aucune nouvelle mémoire ne doit être allouée chez la victime si un gadget utilisable existe déjà ; l'injection se résume à un seul syscall une fois le handle obtenu. Les paquets de complétion forgés ont aussi été historiquement utilisés par des chaînes d'exploitation contre des failles de callback Win32k et par diverses évasions de sandbox dans les navigateurs (thread IO de Chromium, broker d'Edge).

Opportunités de détection

Comme NtCreateIoCompletion, le syscall est bien trop courant en code légitime pour alerter dessus. La variante inter-processus est le signal actionnable : NtSetIoCompletion sur un handle dont l'objet sous-jacent appartient à un processus différent de l'appelant n'est essentiellement jamais légitime. ETW Microsoft-Windows-Kernel-IoCompletion peut exposer les événements post à coût élevé ; une approche moins coûteuse est ObRegisterCallbacks sur le type d'objet IoCompletion, conditionnée sur les duplications de handle inter-processus. L'inspection du contenu des paquets forgés est théoriquement possible mais coûteuse — les contextes en forme de TP_DIRECT déréférencés dans des régions VAD RWX sont une heuristique crédible.

Exemples de syscalls directs

cForged delivery — PoolParty 'Worker via Completion Port'

// hRemoteIocp is a handle in OUR address space that maps to the VICTIM's
// threadpool completion port (obtained via NtDuplicateObject or handle leak).
// pVictimGadget is a writable+executable address in the victim already shaped
// like a TP_DIRECT entry whose callback field points at shellcode.
NTSTATUS s = NtSetIoCompletion(
    hRemoteIocp,
    NULL,                  // KeyContext — irrelevant for TP_DIRECT path
    pVictimGadget,         // ApcContext — what TppWorkerThread will dispatch through
    STATUS_SUCCESS,
    0);                    // Information

cBenign use — wake a custom worker

// Plain PostQueuedCompletionStatus equivalent — every server you've written
// uses this under the hood.
NtSetIoCompletion(hIocp,
                  (PVOID)42,           // your CompletionKey
                  (PVOID)pOverlapped,  // your OVERLAPPED*
                  STATUS_SUCCESS,
                  bytesTransferred);

asmx64 direct stub (Win11 24H2 SSN 0x1B3)

NtSetIoCompletion PROC
    mov  r10, rcx
    mov  eax, 1B3h
    syscall
    ret
NtSetIoCompletion ENDP

Mappings MITRE ATT&CK

Last verified: 2026-05-20