NtWriteFile
Schreibt Daten in eine offene Datei, Pipe oder ein Gerät — Kernel-Pendant zu NtCreateFile fürs Ablegen von Payloads.
Prototyp
NTSTATUS NtWriteFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle, mit NtCreateFile/NtOpenFile geöffnet, mit GENERIC_WRITE oder FILE_WRITE_DATA. |
| Event | HANDLE | in | Optionales Event, das nach Abschluss asynchroner Schreibvorgänge signalisiert wird. NULL für synchrone Handles. |
| ApcRoutine | PIO_APC_ROUTINE | in | Optionale APC bei Abschluss. Außerhalb von Overlapped-I/O fast immer NULL. |
| ApcContext | PVOID | in | Kontext für ApcRoutine. NULL, wenn ApcRoutine NULL ist. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Erhält finalen NTSTATUS und Information-Feld mit geschriebenen Bytes. |
| Buffer | PVOID | in | Zeiger auf die zu schreibenden Quelldaten. |
| Length | ULONG | in | Anzahl der aus Buffer zu schreibenden Bytes. |
| ByteOffset | PLARGE_INTEGER | in | Datei-Offset oder FILE_USE_FILE_POINTER_POSITION (-2) für die aktuelle Position. |
| Key | PULONG | in | Optionaler Byte-Range-Lock-Schlüssel. Fast immer NULL. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x8 | win10-1507 |
| Win10 1607 | 0x8 | win10-1607 |
| Win10 1703 | 0x8 | win10-1703 |
| Win10 1709 | 0x8 | win10-1709 |
| Win10 1803 | 0x8 | win10-1803 |
| Win10 1809 | 0x8 | win10-1809 |
| Win10 1903 | 0x8 | win10-1903 |
| Win10 1909 | 0x8 | win10-1909 |
| Win10 2004 | 0x8 | win10-2004 |
| Win10 20H2 | 0x8 | win10-20h2 |
| Win10 21H1 | 0x8 | win10-21h1 |
| Win10 21H2 | 0x8 | win10-21h2 |
| Win10 22H2 | 0x8 | win10-22h2 |
| Win11 21H2 | 0x8 | win11-21h2 |
| Win11 22H2 | 0x8 | win11-22h2 |
| Win11 23H2 | 0x8 | win11-23h2 |
| Win11 24H2 | 0x8 | win11-24h2 |
| Server 2016 | 0x8 | winserver-2016 |
| Server 2019 | 0x8 | winserver-2019 |
| Server 2022 | 0x8 | winserver-2022 |
| Server 2025 | 0x8 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 08 00 00 00 mov eax, 0x8 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 `0x8` — eine der niedrigsten, seit Windows 7 unverändert — macht NtWriteFile ohne dynamische SSN-Auflösung leicht aufrufbar. Hinter der API stehen 9 Parameter, von denen routinemäßig nur FileHandle, IoStatusBlock, Buffer, Length und ByteOffset nicht NULL sind. War das Handle mit `FILE_SYNCHRONOUS_IO_NONALERT` geöffnet (Normalfall), blockiert der Aufruf bis zum Abschluss; der IoStatusBlock wird vor Rückkehr gefüllt. Der Weg führt über IopXxxControlFile → IRP_MJ_WRITE des Treibers; alle Dateisystem- und Storage-Filter-Treiber im Stack sehen das Schreiben.
Häufige Malware-Nutzung
Mit NtCreateFile bildet er die gesamte On-Disk-Staging-Kette: mit FILE_OVERWRITE_IF öffnen, verschlüsselten Nachfolge-Stage per NtWriteFile schreiben, schließen. Ransomware nutzt ihn beim Verschlüsseln (Original öffnen, NtReadFile, verschlüsseln, NtWriteFile zurück über die Datei oder in `.locked`-Pendants). Wiper nullen damit `\Device\PhysicalDriveN` oder überschreiben Registry-Hive-Dateien (HermeticWiper, CaddyWiper). Loader, die `%APPDATA%\Microsoft\Templates\Normal.dotm` oder eine Startup-Verknüpfung beschreiben, schließen die Persistenz-Schleife, ohne kernel32!WriteFile zu berühren.
Erkennungsmöglichkeiten
Anders als NtCreateFile produziert NtWriteFile selten eigene Telemetrie: das *Erzeugen* feuert Sysmon 11 / Kernel-File `Create`, einzelne Schreibvorgänge sind zu laut für Default-Logging. EDRs sitzen im Minifilter-Layer und sehen die tatsächlich geschriebenen Daten — Grundlage von YARA-on-Write-Engines und Ransomware-Canary-Mechaniken. Microsoft-Windows-Kernel-File Event 12 (`Write`) existiert, wird aber meist nur bei Incident-Response aktiviert. Die Asymmetrie ist operativ: Verteidiger erkennen Ransomware daran, *was* geschrieben wird (hochentropischer Inhalt, vertauschte Extensions, Massen-Renames), nicht an NtWriteFile-Zählern. Mit NtSetInformationFile/FileDispositionInformation korrelieren, um das nachfolgende Umbenennen oder Löschen zu greifen.
Direkte Syscall-Beispiele
cWrite payload to a freshly-created file
// After NtCreateFile gave us a synchronous, writable handle:
IO_STATUS_BLOCK iosb = {0};
LARGE_INTEGER offset = { .QuadPart = 0 };
NTSTATUS s = NtWriteFile(
hFile,
NULL, NULL, NULL, // no event, no APC
&iosb,
payload, payloadLen,
&offset, // write at start of file
NULL); // no lock key
if (NT_SUCCESS(s)) {
// iosb.Information == bytes written
}asmDirect stub (SSN 0x8)
NtWriteFile PROC
mov r10, rcx
mov eax, 8
syscall
ret
NtWriteFile ENDPrustAppend to a log device via ntapi
use ntapi::ntioapi::{NtWriteFile, IO_STATUS_BLOCK};
pub unsafe fn write_all(handle: *mut core::ffi::c_void, data: &[u8]) -> i32 {
let mut iosb: IO_STATUS_BLOCK = core::mem::zeroed();
// -2 == FILE_USE_FILE_POINTER_POSITION
let mut offset: i64 = -2;
NtWriteFile(
handle,
core::ptr::null_mut(),
None,
core::ptr::null_mut(),
&mut iosb,
data.as_ptr() as *mut _,
data.len() as u32,
&mut offset as *mut _ as *mut _,
core::ptr::null_mut(),
)
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20