> Windows Syscalls
ntoskrnl.exeT1106

NtRemoveIoCompletionEx

Retira hasta un número especificado por el llamador de paquetes de completion de un puerto de I/O completion en una sola syscall.

Prototipo

NTSTATUS NtRemoveIoCompletionEx(
  HANDLE                IoCompletionHandle,
  PFILE_IO_COMPLETION_INFORMATION IoCompletionInformation,
  ULONG                 Count,
  PULONG                NumEntriesRemoved,
  PLARGE_INTEGER        Timeout,
  BOOLEAN               Alertable
);

Argumentos

NameTypeDirDescription
IoCompletionHandleHANDLEinHandle al objeto IoCompletion abierto con IO_COMPLETION_MODIFY_STATE.
IoCompletionInformationPFILE_IO_COMPLETION_INFORMATIONoutArray asignado por el llamador de Count entradas que recibe tuplas (KeyContext, ApcContext, IoStatusBlock).
CountULONGinNúmero máximo de paquetes a retirar en esta llamada.
NumEntriesRemovedPULONGoutRecibe el número real de paquetes devueltos (1..Count).
TimeoutPLARGE_INTEGERinTimeout opcional en unidades de 100ns relativo (negativo) o absoluto (positivo). NULL espera indefinidamente.
AlertableBOOLEANinTRUE permite que la espera sea interrumpida por una APC en modo usuario (devuelve STATUS_USER_APC).

IDs de syscalls por versión de Windows

Versión de WindowsID de syscallBuild
Win10 15070x156win10-1507
Win10 16070x15Dwin10-1607
Win10 17030x163win10-1703
Win10 17090x166win10-1709
Win10 18030x168win10-1803
Win10 18090x169win10-1809
Win10 19030x16Awin10-1903
Win10 19090x16Awin10-1909
Win10 20040x170win10-2004
Win10 20H20x170win10-20h2
Win10 21H10x170win10-21h1
Win10 21H20x172win10-21h2
Win10 22H20x172win10-22h2
Win11 21H20x17Awin11-21h2
Win11 22H20x17Dwin11-22h2
Win11 23H20x17Dwin11-23h2
Win11 24H20x17Fwin11-24h2
Server 20160x15Dwinserver-2016
Server 20190x169winserver-2019
Server 20220x178winserver-2022
Server 20250x17Fwinserver-2025

Módulo del kernel

ntoskrnl.exeNtRemoveIoCompletionEx

APIs relacionadas

GetQueuedCompletionStatusExGetQueuedCompletionStatusNtRemoveIoCompletionNtSetIoCompletionNtCreateIoCompletionCreateIoCompletionPort

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

Notas no documentadas

Primo en batch de `NtRemoveIoCompletion`. Donde la syscall legacy devuelve a lo sumo un paquete de completion por llamada, `NtRemoveIoCompletionEx` retorna hasta `Count` paquetes en un solo round-trip — una ganancia significativa para servidores de alto throughput (IIS, SQL Server, el hilo I/O completion del ThreadPool .NET, todo thread-pool Win32 con `WT_EXECUTEINIOTHREAD`). El wrapper Win32 es `GetQueuedCompletionStatusEx`. Devuelve STATUS_TIMEOUT si Timeout expira sin paquete, STATUS_USER_APC si Alertable y se disparó una APC, STATUS_ABANDONED_WAIT_0 si el objeto IoCompletion se cierra durante la espera. Disponible desde Windows Vista — API totalmente estable desde entonces, solo el SSN ha cambiado.

Uso común por malware

**Señal de malware débil**. Este syscall es abrumadoramente una primitiva de servidor/runtime de alto rendimiento — el ThreadPool .NET, el worker I/O del thread-pool Win32, y todo producto servidor Windows lo invocan constantemente. Las pocas menciones de malware en la literatura involucran código C2 *del lado servidor* de alta concurrencia (paneles y bots que actúan como servidores), no implantes. Un pequeño número de implantes pesados en red (algunos puentes external-C2 de Cobalt Strike, ciertos transportes custom de Sliver construidos sobre IO completion ports) lo usan porque reutilizan un modelo de threading estilo servidor — y ahí en telemetría se ve idéntico al tráfico WCF / ASP.NET Core legítimo. Como primitiva ofensiva no añade sigilo sobre el ReadFile / WSARecv subyacente.

Oportunidades de detección

Sin señal de detección útil en la práctica — `NtRemoveIoCompletionEx` es uno de los syscalls de mayor volumen en cualquier servidor Windows y un zumbido de fondo constante en workstations (cada pestaña de navegador, cada agente de almacenamiento cloud, cada language server de IDE). No hay pivot telemétrico por llamada que separe malicioso de benigno de forma significativa. Los defensores deben enfocarse en los *vecinos* del puerto IO completion: qué handles se encolan (sockets a endpoints sospechosos, handles a rutas sensibles), y el patrón de threading del consumidor (un worker que hace lecturas en completion port seguidas inmediatamente de NtCreateThreadEx en un proceso remoto es la única bandera débil modelable). Los EDR no suelen hookear este syscall por volumen — las variantes con syscall directo no ganan nada.

Ejemplos de syscalls directos

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 ENDP

cBatched 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
}

Mapeos MITRE ATT&CK

Last verified: 2026-05-20