NtRemoveIoCompletionEx
Retire jusqu'à un nombre spécifié par l'appelant de paquets de complétion d'un port I/O completion en un seul syscall.
Prototype
NTSTATUS NtRemoveIoCompletionEx( HANDLE IoCompletionHandle, PFILE_IO_COMPLETION_INFORMATION IoCompletionInformation, ULONG Count, PULONG NumEntriesRemoved, PLARGE_INTEGER Timeout, BOOLEAN Alertable );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| IoCompletionHandle | HANDLE | in | Handle vers l'objet IoCompletion ouvert avec IO_COMPLETION_MODIFY_STATE. |
| IoCompletionInformation | PFILE_IO_COMPLETION_INFORMATION | out | Tableau alloué par l'appelant de Count entrées qui reçoit des tuples (KeyContext, ApcContext, IoStatusBlock). |
| Count | ULONG | in | Nombre maximum de paquets à retirer dans cet appel. |
| NumEntriesRemoved | PULONG | out | Reçoit le nombre effectif de paquets retournés (1..Count). |
| Timeout | PLARGE_INTEGER | in | Timeout optionnel en unités 100ns relatif (négatif) ou absolu (positif). NULL attend indéfiniment. |
| Alertable | BOOLEAN | in | TRUE autorise l'attente à être interrompue par un APC usermode (renvoie STATUS_USER_APC). |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | 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 |
Module noyau
APIs liées
Stub du syscall
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
Notes non documentées
Cousin batché de `NtRemoveIoCompletion`. Là où le syscall legacy renvoie au plus un paquet de complétion par appel, `NtRemoveIoCompletionEx` retourne jusqu'à `Count` paquets en un seul aller-retour — un gain d'efficacité significatif pour les serveurs à haut débit (IIS, SQL Server, le thread I/O completion du ThreadPool .NET, tout thread-pool Win32 avec `WT_EXECUTEINIOTHREAD`). Le wrapper Win32 est `GetQueuedCompletionStatusEx`. Renvoie STATUS_TIMEOUT si Timeout s'écoule sans paquet, STATUS_USER_APC si Alertable et qu'un APC s'est déclenché, STATUS_ABANDONED_WAIT_0 si l'objet IoCompletion est fermé pendant l'attente. Disponible depuis Windows Vista — API totalement stable depuis, seul le SSN a bougé.
Usage courant par les malwares
**Signal malware faible**. Ce syscall est massivement une primitive serveur haute-perf / runtime — le ThreadPool .NET, le worker I/O du thread-pool Win32, et tout produit serveur Windows l'appellent constamment. Les rares mentions de malware dans la littérature concernent du code C2 *côté serveur* à haute concurrence (panneaux et bots qui agissent eux-mêmes comme des serveurs), pas des implants. Un petit nombre d'implants réseau-lourds (certains ponts external-C2 de Cobalt Strike, certains transports custom Sliver bâtis sur IO completion ports) l'utilisent parce qu'ils réutilisent un modèle de threading style serveur — et là, c'est juste identique en télémétrie au trafic WCF / ASP.NET Core légitime. En tant que primitive offensive, il n'ajoute aucune furtivité par-dessus le ReadFile / WSARecv qu'il batche.
Opportunités de détection
Pas de signal de détection utile en pratique — `NtRemoveIoCompletionEx` est l'un des syscalls les plus volumétriques sur tout serveur Windows et un bruit de fond constant sur poste (chaque onglet navigateur, chaque agent de stockage cloud, chaque language server d'IDE). Aucun pivot télémétrique par-appel ne sépare malveillant de bénin de façon significative. Les défenseurs doivent se concentrer sur les *voisins* du port IO completion : quels handles y sont queuées (sockets vers des endpoints suspects, handles de fichiers vers des chemins sensibles), et le pattern de threading du consommateur (un worker qui fait des reads sur completion-port suivis immédiatement d'un NtCreateThreadEx dans un processus distant est le seul red flag faible modélisable). Les EDR ne hookent typiquement pas ce syscall à cause du volume — les variantes en syscall direct ne gagnent rien.
Exemples de syscalls directs
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
}Mappings MITRE ATT&CK
Last verified: 2026-05-20