NtWriteFile
Escribe datos en un archivo, canal o dispositivo abierto — pareja kernel de NtCreateFile para soltar payloads.
Prototipo
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 );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle abierto por NtCreateFile/NtOpenFile con GENERIC_WRITE o FILE_WRITE_DATA. |
| Event | HANDLE | in | Evento opcional señalado al completar una escritura asíncrona. NULL para handles síncronos. |
| ApcRoutine | PIO_APC_ROUTINE | in | APC opcional entregado al completar. Casi siempre NULL fuera de E/S superpuesta. |
| ApcContext | PVOID | in | Contexto pasado a ApcRoutine. NULL si ApcRoutine es NULL. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Recibe el NTSTATUS final y el campo Information con los bytes escritos. |
| Buffer | PVOID | in | Puntero a los datos a escribir. |
| Length | ULONG | in | Número de bytes a escribir desde Buffer. |
| ByteOffset | PLARGE_INTEGER | in | Desplazamiento en el archivo, o FILE_USE_FILE_POINTER_POSITION (-2) para la posición actual. |
| Key | PULONG | in | Clave opcional de bloqueo por rango de bytes. Casi siempre NULL. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | 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 |
Módulo del kernel
APIs relacionadas
Stub del syscall
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
Notas no documentadas
SSN `0x8` — uno de los más bajos, sin cambios desde Windows 7 — facilita llamar a NtWriteFile sin resolución dinámica. Tras la API hay 9 parámetros, de los cuales solo FileHandle, IoStatusBlock, Buffer, Length y ByteOffset suelen ser no nulos. Si el handle se abrió con `FILE_SYNCHRONOUS_IO_NONALERT` (caso típico), la llamada bloquea hasta completar y se rellena IoStatusBlock antes de retornar. Pasa por IopXxxControlFile → IRP_MJ_WRITE del driver, así que cada filtro de sistema de archivos y de almacenamiento ve la escritura.
Uso común por malware
Combinado con NtCreateFile constituye la cadena entera de staging en disco: abrir con FILE_OVERWRITE_IF, NtWriteFile del siguiente stage cifrado, cerrar. El ransomware lo emplea durante el cifrado (abrir original, NtReadFile, cifrar, NtWriteFile sobre el mismo archivo o sobre un compañero `.locked`). Los wipers lo usan para cerar `\Device\PhysicalDriveN` o sobrescribir hives del registro (HermeticWiper, CaddyWiper). Los loaders que escriben en `%APPDATA%\Microsoft\Templates\Normal.dotm` o en un acceso directo de la carpeta Startup cierran el bucle de persistencia sin tocar kernel32!WriteFile.
Oportunidades de detección
A diferencia de NtCreateFile, NtWriteFile rara vez produce telemetría dedicada por sí solo: la *creación* dispara Sysmon 11 / Kernel-File `Create`, pero los eventos por escritura son demasiado ruidosos para registrarlos por defecto. Los EDR residen en la capa minifilter y ven los datos efectivamente escritos, que es como funcionan los motores YARA-on-write y los honeypots anti-ransomware. Microsoft-Windows-Kernel-File event 12 (`Write`) existe pero suele activarse solo durante respuesta a incidentes por su volumen. La asimetría es operativa: los defensores detectan ransomware por *lo que* se escribe (contenido de alta entropía, extensiones cambiadas, renombrados masivos), no por contar invocaciones a NtWriteFile. Correlacionar con NtSetInformationFile/FileDispositionInformation para capturar el renombrado o borrado posterior.
Ejemplos de syscalls directos
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(),
)
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20