> Windows Syscalls
ntoskrnl.exeT1055T1106

NtSetIoCompletion

Postea un paquete de finalización a un puerto de finalización de E/S — el lado del kernel de PostQueuedCompletionStatus y el vector de entrega de los items falsificados de PoolParty.

Prototipo

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

Argumentos

NameTypeDirDescription
IoCompletionHandleHANDLEinHandle al puerto de finalización objetivo. Requiere IO_COMPLETION_MODIFY_STATE.
KeyContextPVOIDinCompletionKey definido por el llamador, devuelto al desencolador vía lpCompletionKey.
ApcContextPVOIDinPuntero definido por el llamador devuelto como lpOverlapped — normalmente un OVERLAPPED* en código legítimo, controlado por el atacante en PoolParty.
IoStatusNTSTATUSinCódigo de estado escrito en el campo Status del IO_STATUS_BLOCK del paquete.
IoStatusInformationULONG_PTRinValor definido por el llamador escrito en IO_STATUS_BLOCK.Information — típicamente un conteo de bytes transferidos.

IDs de syscalls por versión de Windows

Versión 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

Módulo del kernel

ntoskrnl.exeNtSetIoCompletion

APIs relacionadas

PostQueuedCompletionStatusNtCreateIoCompletionNtRemoveIoCompletionNtRemoveIoCompletionExNtWaitForWorkViaWorkerFactorySubmitThreadpoolWork

Stub del 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

Notas no documentadas

Equivalente directo de `PostQueuedCompletionStatus`. El paquete se añade a la KQUEUE del puerto y se despierta un esperante (en NtRemoveIoCompletion, NtRemoveIoCompletionEx o NtWaitForWorkViaWorkerFactory). El kernel no interpreta `KeyContext` ni `ApcContext` — se devuelven literales. Esa opacidad hace al syscall muy versátil tanto para el despacho legítimo de E/S asíncrona como para la inyección maliciosa de paquetes forjados.

Uso común por malware

La variante PoolParty 'Worker via Completion Port': abrir el puerto de finalización del threadpool de la víctima (a menudo vía un handle filtrado o NtDuplicateObject), luego emitir NtSetIoCompletion con `ApcContext` apuntando a una estructura con forma `TP_DIRECT` — cuando ntdll!TppWorkerThread desencola el paquete, su dispatcher seguirá los punteros y llamará la función elegida por el atacante. Crucialmente, no necesita asignarse nueva memoria en la víctima si ya existe un gadget utilizable; la inyección se reduce a un solo syscall una vez obtenido el handle. Paquetes de finalización falsificados también han sido usados históricamente por cadenas de exploit contra fallos de callback de Win32k y por varias evasiones de sandbox en navegadores (hilo IO de Chromium, broker de Edge).

Oportunidades de detección

Como NtCreateIoCompletion, el syscall es demasiado común en código legítimo para alertar directamente. La variante inter-proceso es la señal accionable: NtSetIoCompletion sobre un handle cuyo objeto subyacente pertenece a un proceso distinto del llamador es esencialmente nunca legítimo. El ETW Microsoft-Windows-Kernel-IoCompletion puede exponer eventos post a alto coste; un enfoque más liviano es ObRegisterCallbacks sobre el tipo de objeto IoCompletion, condicionado a duplicaciones de handle inter-proceso. La inspección del contenido de paquetes forjados es teóricamente posible pero costosa — contextos con forma de TP_DIRECT desreferenciados en regiones VAD RWX son una heurística creíble.

Ejemplos de syscalls directos

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

Mapeos MITRE ATT&CK

Last verified: 2026-05-20