NtRemoveIoCompletionEx
Holt in einem einzigen Syscall bis zu einer vom Aufrufer angegebenen Anzahl von Completion-Paketen aus einem I/O-Completion-Port.
Prototyp
NTSTATUS NtRemoveIoCompletionEx( HANDLE IoCompletionHandle, PFILE_IO_COMPLETION_INFORMATION IoCompletionInformation, ULONG Count, PULONG NumEntriesRemoved, PLARGE_INTEGER Timeout, BOOLEAN Alertable );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| IoCompletionHandle | HANDLE | in | Handle auf das IoCompletion-Objekt, geöffnet mit IO_COMPLETION_MODIFY_STATE. |
| IoCompletionInformation | PFILE_IO_COMPLETION_INFORMATION | out | Vom Aufrufer alloziertes Array von Count Einträgen, das (KeyContext, ApcContext, IoStatusBlock)-Tupel empfängt. |
| Count | ULONG | in | Maximale Anzahl Pakete, die in diesem Aufruf entnommen werden. |
| NumEntriesRemoved | PULONG | out | Empfängt die tatsächliche Anzahl zurückgegebener Pakete (1..Count). |
| Timeout | PLARGE_INTEGER | in | Optionales Timeout in 100ns-Einheiten, relativ (negativ) oder absolut (positiv). NULL wartet ewig. |
| Alertable | BOOLEAN | in | TRUE erlaubt das Unterbrechen des Waits durch eine User-Mode-APC (liefert STATUS_USER_APC). |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x156 | win10-1507 |
| Win10 1607 | 0x15D | win10-1607 |
| Win10 1703 | 0x163 | win10-1703 |
| Win10 1709 | 0x166 | win10-1709 |
| Win10 1803 | 0x168 | win10-1803 |
| Win10 1809 | 0x169 | win10-1809 |
| Win10 1903 | 0x16A | win10-1903 |
| Win10 1909 | 0x16A | win10-1909 |
| Win10 2004 | 0x170 | win10-2004 |
| Win10 20H2 | 0x170 | win10-20h2 |
| Win10 21H1 | 0x170 | win10-21h1 |
| Win10 21H2 | 0x172 | win10-21h2 |
| Win10 22H2 | 0x172 | win10-22h2 |
| Win11 21H2 | 0x17A | win11-21h2 |
| Win11 22H2 | 0x17D | win11-22h2 |
| Win11 23H2 | 0x17D | win11-23h2 |
| Win11 24H2 | 0x17F | win11-24h2 |
| Server 2016 | 0x15D | winserver-2016 |
| Server 2019 | 0x169 | winserver-2019 |
| Server 2022 | 0x178 | winserver-2022 |
| Server 2025 | 0x17F | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 7F 01 00 00 mov eax, 0x17F 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
Batch-Cousin von `NtRemoveIoCompletion`. Wo der Legacy-Syscall maximal ein Completion-Paket pro Aufruf liefert, gibt `NtRemoveIoCompletionEx` bis zu `Count` Pakete in einem Roundtrip zurück — ein deutlicher Effizienzgewinn für High-Throughput-Server (IIS, SQL Server, der I/O-Completion-Thread des .NET-ThreadPools, jeder Win32-ThreadPool mit `WT_EXECUTEINIOTHREAD`). Der Win32-Wrapper ist `GetQueuedCompletionStatusEx`. Liefert STATUS_TIMEOUT, wenn Timeout ohne Paket abläuft, STATUS_USER_APC bei Alertable und ausgelöster APC, STATUS_ABANDONED_WAIT_0, wenn das IoCompletion-Objekt während des Waits geschlossen wird. Verfügbar seit Windows Vista — die API ist seitdem voll stabil, nur die SSN hat sich bewegt.
Häufige Malware-Nutzung
**Schwaches Malware-Signal**. Dieser Syscall ist überwältigend eine High-Perf-Server-/Runtime-Primitive — der .NET-ThreadPool, der I/O-Worker des Win32-ThreadPools und jedes Windows-Server-Produkt rufen ihn ständig. Die wenigen Malware-Erwähnungen in der Literatur betreffen hochnebenläufigen C2-*Server-Code* (Panels und Bots, die selbst als Server agieren), nicht Implants. Eine kleine Zahl netzwerk-intensiver Implants (manche Cobalt-Strike-External-C2-Bridges, bestimmte Sliver-Custom-Transports, die auf IO-Completion-Ports aufbauen) nutzen ihn, weil sie ein Server-artiges Threading-Modell wiederverwenden — und dort sieht es in der Telemetrie schlicht identisch zu legitimem WCF-/ASP.NET-Core-Traffic aus. Als offensive Primitive bringt es null Stealth gegenüber dem darunterliegenden ReadFile / WSARecv, das es batcht.
Erkennungsmöglichkeiten
Praktisch kein nutzbares Detection-Signal — `NtRemoveIoCompletionEx` ist einer der volumenstärksten Syscalls auf jedem Windows-Server und ein konstantes Hintergrundrauschen auf Workstations (jeder Browser-Tab, jeder Cloud-Storage-Agent, jeder IDE-Language-Server). Es gibt keinen Per-Call-Telemetrie-Pivot, der bösartig von gutartig sinnvoll trennt. Verteidiger sollten sich auf die *Nachbarn* des IO-Completion-Ports konzentrieren: welche Handles dorthin queued werden (Sockets zu verdächtigen Endpunkten, File-Handles in sensible Pfade) und das Threading-Muster des Konsumenten (ein Worker, der Completion-Port-Reads gefolgt unmittelbar von NtCreateThreadEx in einen Remote-Prozess macht, ist die einzige schwache Red-Flag, die sich modellieren lässt). EDRs hooken diesen Syscall wegen des Volumens typischerweise nicht — Direct-Syscall-Varianten gewinnen nichts.
Direkte Syscall-Beispiele
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtRemoveIoCompletionEx (SSN 0x17F on Win11 24H2)
NtRemoveIoCompletionEx PROC
mov r10, rcx ; syscall convention
mov eax, 17Fh ; SSN — varies per build
syscall
ret
NtRemoveIoCompletionEx ENDPcBatched dequeue via GetQueuedCompletionStatusEx
// Standard server-thread loop: pull up to 16 packets per syscall.
// This is what nearly every IOCP-based service does.
#include <windows.h>
VOID IocpWorker(HANDLE hPort) {
OVERLAPPED_ENTRY entries[16];
ULONG got = 0;
while (GetQueuedCompletionStatusEx(hPort, entries, 16, &got, INFINITE, FALSE)) {
for (ULONG i = 0; i < got; ++i) {
DispatchCompletion(&entries[i]);
}
}
}rustDirect-syscall wrapper
// Cargo: windows-sys = "0.59"
// Minimal wrapper around the Ex syscall — used by a custom server runtime.
use std::ptr::null_mut;
#[repr(C)]
pub struct IoCompletionInfo {
pub key_context: usize,
pub apc_context: usize,
pub io_status: [usize; 2], // status + information
}
extern "system" {
fn NtRemoveIoCompletionEx(h: isize, info: *mut IoCompletionInfo,
count: u32, removed: *mut u32, timeout: *mut i64, alertable: u8) -> i32;
}
pub unsafe fn drain(h: isize, batch: &mut [IoCompletionInfo]) -> u32 {
let mut n: u32 = 0;
NtRemoveIoCompletionEx(h, batch.as_mut_ptr(), batch.len() as u32,
&mut n, null_mut(), 0);
n
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20