NtSetIoCompletion
Sendet ein Completion-Paket an einen I/O-Completion-Port — die Kernel-Seite von PostQueuedCompletionStatus und der Lieferpfad für PoolPartys gefälschte Work-Items.
Prototyp
NTSTATUS NtSetIoCompletion( HANDLE IoCompletionHandle, PVOID KeyContext, PVOID ApcContext, NTSTATUS IoStatus, ULONG_PTR IoStatusInformation );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| IoCompletionHandle | HANDLE | in | Handle auf den Ziel-Completion-Port. Erfordert IO_COMPLETION_MODIFY_STATE. |
| KeyContext | PVOID | in | Vom Aufrufer definierter CompletionKey, der dem Dequeuer via lpCompletionKey zurückgegeben wird. |
| ApcContext | PVOID | in | Vom Aufrufer definierter Zeiger, der als lpOverlapped zurückgegeben wird — in legitimem Code üblicherweise ein OVERLAPPED*, bei PoolParty vom Angreifer kontrolliert. |
| IoStatus | NTSTATUS | in | Statuscode, der in das Status-Feld des IO_STATUS_BLOCK des Pakets geschrieben wird. |
| IoStatusInformation | ULONG_PTR | in | Vom Aufrufer definierter Wert, der in IO_STATUS_BLOCK.Information geschrieben wird — üblicherweise eine übertragene Byte-Anzahl. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x185 | win10-1507 |
| Win10 1607 | 0x18E | win10-1607 |
| Win10 1703 | 0x194 | win10-1703 |
| Win10 1709 | 0x197 | win10-1709 |
| Win10 1803 | 0x199 | win10-1803 |
| Win10 1809 | 0x19A | win10-1809 |
| Win10 1903 | 0x19B | win10-1903 |
| Win10 1909 | 0x19B | win10-1909 |
| Win10 2004 | 0x1A1 | win10-2004 |
| Win10 20H2 | 0x1A1 | win10-20h2 |
| Win10 21H1 | 0x1A1 | win10-21h1 |
| Win10 21H2 | 0x1A3 | win10-21h2 |
| Win10 22H2 | 0x1A3 | win10-22h2 |
| Win11 21H2 | 0x1AC | win11-21h2 |
| Win11 22H2 | 0x1B0 | win11-22h2 |
| Win11 23H2 | 0x1B0 | win11-23h2 |
| Win11 24H2 | 0x1B3 | win11-24h2 |
| Server 2016 | 0x18E | winserver-2016 |
| Server 2019 | 0x19A | winserver-2019 |
| Server 2022 | 0x1A9 | winserver-2022 |
| Server 2025 | 0x1B3 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
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
Undokumentierte Hinweise
Direktes Pendant zu `PostQueuedCompletionStatus`. Das Paket wird an die KQUEUE des Ports angehängt und ein Wartender (in NtRemoveIoCompletion, NtRemoveIoCompletionEx oder NtWaitForWorkViaWorkerFactory) wird geweckt. Der Kernel interpretiert weder `KeyContext` noch `ApcContext` — sie werden wortgetreu zurückgereicht. Diese Undurchsichtigkeit macht den Syscall so vielseitig: für legitimes asynchrones I/O-Dispatch wie für bösartige Forged-Packet-Injection.
Häufige Malware-Nutzung
Die PoolParty-'Worker via Completion Port'-Variante: den Threadpool-Completion-Port des Opfers öffnen (oft über einen geleakten Handle oder NtDuplicateObject), dann NtSetIoCompletion mit `ApcContext` aussenden, der auf eine `TP_DIRECT`-förmige Struktur zeigt — wenn ntdll!TppWorkerThread das Paket dequeue't, folgt sein Dispatcher den Pointern und ruft die vom Angreifer gewählte Funktion auf. Entscheidend: es muss kein neuer Speicher im Opfer alloziert werden, wenn ein brauchbares Gadget existiert; die Injection schrumpft auf einen einzigen Syscall, sobald der Handle vorliegt. Gefälschte Completion-Pakete wurden auch historisch in Exploit-Ketten gegen Win32k-Callback-Schwachstellen und in diversen Sandbox-Escapes in Browsern (Chromium-IO-Thread, Edge-Broker) verwendet.
Erkennungsmöglichkeiten
Wie NtCreateIoCompletion ist der Syscall in legitimem Code viel zu häufig, um direkt zu alarmieren. Die cross-process-Variante ist das handlungsleitende Signal: NtSetIoCompletion auf einem Handle, dessen zugrundeliegendes Objekt einem anderen Prozess gehört als dem Aufrufer, ist im Wesentlichen nie legitim. ETW Microsoft-Windows-Kernel-IoCompletion kann Post-Events teuer offenlegen; ein günstigerer Ansatz ist ObRegisterCallbacks am IoCompletion-Objekttyp, gegated über cross-process Handle-Duplikationen. Inhaltsinspektion gefälschter Pakete ist theoretisch möglich, aber teuer — TP_DIRECT-förmige Kontexte, die in RWX-VAD-Regionen dereferenziert werden, sind eine plausible Heuristik.
Direkte Syscall-Beispiele
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); // InformationcBenign 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 ENDPMITRE ATT&CK-Mappings
Last verified: 2026-05-20