NtCancelSynchronousIoFile
Cancela una llamada de E/S síncrona que está bloqueando actualmente a otro hilo.
Prototipo
NTSTATUS NtCancelSynchronousIoFile( HANDLE ThreadHandle, PIO_STATUS_BLOCK IoRequestToCancel, PIO_STATUS_BLOCK IoStatusBlock );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadHandle | HANDLE | in | Handle al hilo actualmente bloqueado en una llamada de E/S síncrona. Requiere acceso THREAD_TERMINATE. |
| IoRequestToCancel | PIO_STATUS_BLOCK | in | Puntero opcional al IO_STATUS_BLOCK de un IRP síncrono específico. NULL cancela cualquier E/S síncrona en la que el hilo esté bloqueado. |
| 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 | 0x8E | win10-1507 |
| Win10 1607 | 0x8E | win10-1607 |
| Win10 1703 | 0x8F | win10-1703 |
| Win10 1709 | 0x90 | win10-1709 |
| Win10 1803 | 0x91 | win10-1803 |
| Win10 1809 | 0x91 | win10-1809 |
| Win10 1903 | 0x91 | win10-1903 |
| Win10 1909 | 0x91 | win10-1909 |
| Win10 2004 | 0x93 | win10-2004 |
| Win10 20H2 | 0x93 | win10-20h2 |
| Win10 21H1 | 0x93 | win10-21h1 |
| Win10 21H2 | 0x93 | win10-21h2 |
| Win10 22H2 | 0x93 | win10-22h2 |
| Win11 21H2 | 0x93 | win11-21h2 |
| Win11 22H2 | 0x93 | win11-22h2 |
| Win11 23H2 | 0x93 | win11-23h2 |
| Win11 24H2 | 0x95 | win11-24h2 |
| Server 2016 | 0x8E | winserver-2016 |
| Server 2019 | 0x91 | winserver-2019 |
| Server 2022 | 0x93 | winserver-2022 |
| Server 2025 | 0x95 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 95 00 00 00 mov eax, 0x95 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
El primo de NtCancelIoFileEx que apunta a E/S *síncrona* — llamadas como `ReadFile` sobre un handle abierto sin `FILE_FLAG_OVERLAPPED`, donde el hilo llamador está sentado en el kernel esperando que el IRP se complete y no puede atender una cancelación por sí mismo. El wrapper Win32 `CancelSynchronousIo` sólo toma el handle de hilo; la variante que apunta a un IO_STATUS_BLOCK sólo es alcanzable a través de la API nativa. El SSN deriva modestamente (`0x91` 1803–1909, `0x93` 2004–23H2, `0x95` 24H2 / Server 2025). Llamarlo sobre un hilo que no está actualmente en espera de E/S síncrona retorna `STATUS_NOT_FOUND` (0xC0000225) — benigno.
Uso común por malware
Menos comúnmente armado que NtCancelIoFileEx pero con un caso de uso distintivo: **desbloquear un hilo scanner de EDR**. Algunos EDR user-mode ejecutan un worker que lee síncronamente su pipe IPC (`\\.\pipe\<vendor>`) esperando mensajes de comando. Si un atacante puede obtener un handle equivalente a THREAD_TERMINATE a ese worker y la protección del EDR ya está neutralizada, `NtCancelSynchronousIoFile` devuelve el worker a user mode con `STATUS_CANCELLED` — potencialmente dejando al sensor en estado degradado sin matar el proceso (lo que dispararía la autoprotección). También usado por tooling red-team para descongelar un hilo que inyectó en el objetivo y que terminó bloqueado en una E/S síncrona larga (p. ej. un NetUserGetInfo contra un DC lento) para que la inyección se pueda desmontar limpiamente sin orfanar el hilo.
Oportunidades de detección
Genuinamente raro en software benigno. El uso legítimo emblemático es la ruta de desconexión de unidad de red anclada de Explorer (Explorer dispara NtCancelSynchronousIoFile para desatascar el hilo de E/S bloqueado en un share SMB muerto). Fuera de ese contexto estrecho, las llamadas son sospechosas. La señal de alta fidelidad es *cross-process*: el hilo A en el proceso X llama NtCancelSynchronousIoFile con un handle al hilo B en el proceso Y, donde Y es un servicio o componente EDR. ETW `Microsoft-Windows-Kernel-Process` más los eventos de apertura de handle cross-process de `Microsoft-Windows-Kernel-Audit-API-Calls` correlacionan el par open + cancel. Endurecer las ACL en hilos de servicio EDR (para que un no-SYSTEM no pueda adquirir THREAD_TERMINATE) derrota el bypass antes de que el syscall sea alcanzable.
Ejemplos de syscalls directos
asmx64 stub (Win11 24H2 SSN 0x95)
; Direct syscall stub for NtCancelSynchronousIoFile
NtCancelSynchronousIoFile PROC
mov r10, rcx ; syscall convention
mov eax, 95h ; SSN (Win11 24H2 / Server 2025)
syscall
ret
NtCancelSynchronousIoFile ENDPcUnstick an injected worker thread
// Free a thread we injected into a target process that ended up blocked
// in a synchronous I/O wait, so the injection can be cleanly unwound.
#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 *pNtCancelSynchronousIoFile)(
HANDLE ThreadHandle, PIO_STATUS_BLOCK IoRequestToCancel,
PIO_STATUS_BLOCK IoStatusBlock);
NTSTATUS Unstick(HANDLE h_thread) {
pNtCancelSynchronousIoFile NtCancelSynchronousIoFile =
(pNtCancelSynchronousIoFile)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtCancelSynchronousIoFile");
IO_STATUS_BLOCK out = {0};
return NtCancelSynchronousIoFile(h_thread, NULL, &out);
}rustExplorer-style dead-SMB recovery
// Defensive use: cancel a synchronous IO blocking a worker thread we own.
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::IO::CancelSynchronousIo;
pub unsafe fn cancel_blocked(h_thread: HANDLE) -> bool {
// CancelSynchronousIo(h) wraps NtCancelSynchronousIoFile with IRP=NULL.
CancelSynchronousIo(h_thread) != 0
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20