NtCancelIoFile
Bricht jede ausstehende I/O-Anforderung ab, die der aufrufende Thread auf einem Datei-Handle gestellt hat.
Prototyp
NTSTATUS NtCancelIoFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle auf die Datei / das Gerät / die Pipe, deren vom *aufrufenden Thread* gestellte I/O abgebrochen werden soll. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Empfängt das Ergebnis der Abbruch-Operation selbst (Status, Information). Nicht den Status eines abgebrochenen IRP. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x5D | win10-1507 |
| Win10 1607 | 0x5D | win10-1607 |
| Win10 1703 | 0x5D | win10-1703 |
| Win10 1709 | 0x5D | win10-1709 |
| Win10 1803 | 0x5D | win10-1803 |
| Win10 1809 | 0x5D | win10-1809 |
| Win10 1903 | 0x5D | win10-1903 |
| Win10 1909 | 0x5D | win10-1909 |
| Win10 2004 | 0x5D | win10-2004 |
| Win10 20H2 | 0x5D | win10-20h2 |
| Win10 21H1 | 0x5D | win10-21h1 |
| Win10 21H2 | 0x5D | win10-21h2 |
| Win10 22H2 | 0x5D | win10-22h2 |
| Win11 21H2 | 0x5D | win11-21h2 |
| Win11 22H2 | 0x5D | win11-22h2 |
| Win11 23H2 | 0x5D | win11-23h2 |
| Win11 24H2 | 0x5D | win11-24h2 |
| Server 2016 | 0x5D | winserver-2016 |
| Server 2019 | 0x5D | winserver-2019 |
| Server 2022 | 0x5D | winserver-2022 |
| Server 2025 | 0x5D | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 5D 00 00 00 mov eax, 0x5D 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
SSN `0x5D` über alle Windows-10-/11-/Server-Builds — außergewöhnliche Stabilität für einen Datei-I/O-Syscall. Der entscheidende Scope-Vorbehalt: NtCancelIoFile bricht nur I/O ab, die der *aufrufende Thread* auf dem gegebenen Handle gestellt hat. I/O anderer Threads auf demselben Handle ist *nicht* betroffen (dafür wurde NtCancelIoFileEx in Vista eingeführt). `CancelIo` (Win32) wrappt diesen Aufruf direkt. Der Kernel läuft die an den Thread-TCB und den FsContext der Datei angehängte IRP-Liste durch, markiert jeden `IRP->Cancel = TRUE` und ruft die Cancel-Routine des Treibers auf, die die IRPs mit `STATUS_CANCELLED` (0xC0000120) abschließen soll.
Häufige Malware-Nutzung
Wird in defensiven **EDR-Hook-Evasion**-Mustern eingesetzt: viele User-Mode-EDRs hooken `ReadFile` / `WriteFile` / `NtReadFile` zur Inhaltsinspektion; ist die Inspektionsroutine asynchron (queuet einen Callback auf I/O-Completion), kann ein Angreifer den IRP race-canceln, bevor die Completion-Routine feuert, und den EDR die Bytes nie sehen lassen. Häufiger im offensiven Einsatz ist Reliability-Code in Implants, die über Named Pipes sprechen — stirbt der Peer, ruft das Implant `NtCancelIoFile`, um blockierte Overlapped-Reads zu flushen und den Worker-Thread zurückzugewinnen, statt bis zum Kernel-TCP-/Pipe-Timeout zu hängen. Etwas Red-Team-Tooling ruft es auch beim Graceful-Shutdown, um keine Waisen-IRPs zu hinterlassen, die bei forensischer Timeline-Prüfung in `!irp`-Debugger-Output auftauchen.
Erkennungsmöglichkeiten
NtCancelIoFile ist in legitimer I/O-intensiver Software extrem häufig (Browser brechen In-Flight-HTTPS-Reads bei Navigation ab, IDEs brechen File-Watcher ab, OneDrive bricht Sync beim Offline-Toggle ab) und ist daher ein schlechtes Primärsignal. Interessante Telemetrie ist das *Triplett*: ein ausstehender Async-Read auf einem sensitiven Handle (Named Pipe \\.\pipe\protected_storage_pipe, EDR-injizierter IPC-Handle, HTTPS-Socket mit C2), der innerhalb von Mikrosekunden nach Issue vom selben Thread abgebrochen wird, der ihn ausgegeben hat. ETW `Microsoft-Windows-Kernel-File` plus Completion-IRP-Korrelation via WPP können den Pfad des abgebrochenen IRP zeigen; mit PsSetCreateProcessNotifyRoutineEx kombinieren, um Thread-zu-Prozess zuzuordnen. Die meisten EDRs exponieren kein erstklassiges Cancel-Event.
Direkte Syscall-Beispiele
asmx64 direct stub
; Direct syscall stub for NtCancelIoFile (SSN 0x5D, all builds)
NtCancelIoFile PROC
mov r10, rcx ; syscall convention
mov eax, 5Dh ; SSN
syscall
ret
NtCancelIoFile ENDPcImplant pipe shutdown
// Implant teardown: when the C2 peer dies, free our blocked overlapped
// read on the named pipe so the worker thread can exit instead of hanging.
#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 *pNtCancelIoFile)(HANDLE, PIO_STATUS_BLOCK);
void ShutdownPipe(HANDLE h_pipe) {
pNtCancelIoFile NtCancelIoFile = (pNtCancelIoFile)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtCancelIoFile");
IO_STATUS_BLOCK iosb = {0};
NtCancelIoFile(h_pipe, &iosb); // only kills this thread's pending IRPs
CloseHandle(h_pipe);
}rustrace-cancel a hooked async read
// PoC: dispatch an async read, then cancel before the EDR's completion
// inspection runs. Same thread issues and cancels.
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Storage::FileSystem::{ReadFile, OVERLAPPED};
use windows_sys::Win32::System::IO::CancelIo;
pub unsafe fn race_cancel(h: HANDLE, buf: &mut [u8], ov: *mut OVERLAPPED) {
let mut dummy = 0u32;
let _ = ReadFile(h, buf.as_mut_ptr() as _, buf.len() as u32, &mut dummy, ov);
// Immediately yank the IRP before any completion routine fires.
let _ = CancelIo(h); // CancelIo == NtCancelIoFile under the hood
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20