NtFlushVirtualMemory
Vacía a disco las páginas modificadas de una vista respaldada por fichero, equivalente a FlushViewOfFile.
Prototipo
NTSTATUS NtFlushVirtualMemory( HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, PIO_STATUS_BLOCK IoStatus );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle al proceso cuyo mapping debe vaciarse. NtCurrentProcess() para sí mismo. |
| BaseAddress | PVOID* | in/out | Puntero a la base de la región a vaciar. Al retornar, ajustado a la base realmente vaciada. |
| RegionSize | PSIZE_T | in/out | Tamaño de la región en bytes. Redondeado al límite de página; tamaño real devuelto en salida. |
| IoStatus | PIO_STATUS_BLOCK | out | Recibe el estado de I/O (Status + Information : bytes vaciados). |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0xDC | win10-1507 |
| Win10 1607 | 0xDF | win10-1607 |
| Win10 1703 | 0xE2 | win10-1703 |
| Win10 1709 | 0xE3 | win10-1709 |
| Win10 1803 | 0xE4 | win10-1803 |
| Win10 1809 | 0xE5 | win10-1809 |
| Win10 1903 | 0xE6 | win10-1903 |
| Win10 1909 | 0xE6 | win10-1909 |
| Win10 2004 | 0xEB | win10-2004 |
| Win10 20H2 | 0xEB | win10-20h2 |
| Win10 21H1 | 0xEB | win10-21h1 |
| Win10 21H2 | 0xEC | win10-21h2 |
| Win10 22H2 | 0xEC | win10-22h2 |
| Win11 21H2 | 0xF1 | win11-21h2 |
| Win11 22H2 | 0xF2 | win11-22h2 |
| Win11 23H2 | 0xF2 | win11-23h2 |
| Win11 24H2 | 0xF4 | win11-24h2 |
| Server 2016 | 0xDF | winserver-2016 |
| Server 2019 | 0xE5 | winserver-2019 |
| Server 2022 | 0xF0 | winserver-2022 |
| Server 2025 | 0xF4 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 F4 00 00 00 mov eax, 0xF4 ; Win11 24H2 SSN 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
Contraparte de `FlushViewOfFile` para secciones respaldadas por un fichero real en disco. El kernel recorre las PTE del rango solicitado, recolecta las páginas dirty y emite I/O de paging hacia el filesystem subyacente — para un fichero memory-mapped es equivalente a un `fsync` solo del subconjunto modificado. Para regiones respaldadas por pagefile o PAGE_NOACCESS la llamada es efectivamente un no-op. El campo `IoStatus.Information` reporta los bytes realmente escritos.
Uso común por malware
Usado en cadenas de persistencia y ransomware que modifican artefactos en disco vía una vista memory-mapped en lugar de `WriteFile`: el atacante mapea un ejecutable, INI, XML de tarea programada o la lista de LSA Notification Packages como `PAGE_READWRITE`, parchea los bytes y llama a `NtFlushVirtualMemory` para forzar la escritura *ahora* en lugar de esperar al lazy writer — importante cuando el siguiente paso (reinicio, reinicio de servicio o exec inmediato) debe ver el cambio en disco. También visto en cadenas de robo de credenciales estilo `MiniDumpWriteDump` que mapean y editan in-place el fichero de dump. Algunas familias de ransomware tocan esta llamada para asegurar que los bytes cifrados llegan al plato antes de borrar shadow copies y forzar un reinicio.
Oportunidades de detección
Sysmon Event 11 (FileCreate) no se dispara en un flush — el fichero ya existe. La telemetría más fiable es el evento ETW `Microsoft-Windows-Kernel-FileIO` `FileIo/OperationEnd` para operaciones `WRITE` cuyos `IrpFlags` indican paging-IO, correlacionado a un proceso que recientemente obtuvo una sección con escritura sobre un fichero sensible (ejecutable en `\System32`, XML de tarea programada, binario referenciado por `ImagePath` en `\Registry\Machine\System\CurrentControlSet\Services\*`). Los EDR que hookean `NtCreateSection` contra rutas de alto valor y luego siguen los `NtFlushVirtualMemory` subsecuentes contra esas vistas atrapan el patrón de patch in-place.
Ejemplos de syscalls directos
cPatch an on-disk binary via mapped view and force flush
// Persistence helper: patch C:\Windows\System32\target.exe in place.
HANDLE hFile = CreateFileW(L"C:\\Windows\\System32\\target.exe",
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
HANDLE hMap = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
PVOID view = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 0);
PatchEntryPoint(view); // overwrite a few bytes
SIZE_T regionSize = /* SizeOfImage rounded up */ 0;
IO_STATUS_BLOCK iosb;
NTSTATUS s = NtFlushVirtualMemory(
NtCurrentProcess(),
&view,
®ionSize,
&iosb);
// iosb.Information now holds the number of bytes written back.asmx64 direct stub (Win11 24H2)
; NtFlushVirtualMemory direct stub — SSN 0xF4 on Win11 24H2
NtFlushVirtualMemory PROC
mov r10, rcx
mov eax, 0F4h
syscall
ret
NtFlushVirtualMemory ENDPrustFlush a mapped region
use ntapi::ntmmapi::NtFlushVirtualMemory;
use winapi::shared::ntdef::HANDLE;
use winapi::shared::ntdef::PVOID;
use winapi::um::winnt::IO_STATUS_BLOCK;
use std::{mem, ptr::null_mut};
unsafe fn flush(view: PVOID, mut size: usize) -> usize {
let mut base = view;
let mut iosb: IO_STATUS_BLOCK = mem::zeroed();
let s = NtFlushVirtualMemory(
-1isize as HANDLE,
&mut base,
&mut size,
&mut iosb,
);
assert!(s >= 0);
*iosb.Information() as usize
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20