NtFlushVirtualMemory
Schreibt veränderte Seiten einer dateigestützten View auf die Platte zurück, analog zu FlushViewOfFile.
Prototyp
NTSTATUS NtFlushVirtualMemory( HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, PIO_STATUS_BLOCK IoStatus );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle auf den Prozess, dessen Mapping geflusht werden soll. NtCurrentProcess() für sich selbst. |
| BaseAddress | PVOID* | in/out | Zeiger auf die Basis der zu flushenden Region. Beim Rückkehren auf die tatsächlich geflushte Basis gesetzt. |
| RegionSize | PSIZE_T | in/out | Größe der Region in Bytes. Auf Seitengrenze aufgerundet; tatsächliche Größe wird zurückgegeben. |
| IoStatus | PIO_STATUS_BLOCK | out | Nimmt den I/O-Status auf (Status + Information mit der Anzahl geflushter Bytes). |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | 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 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
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
Undokumentierte Hinweise
Gegenstück zu `FlushViewOfFile` für Sections, die durch eine echte On-Disk-Datei gestützt sind. Der Kernel läuft die PTEs des angeforderten Bereichs ab, sammelt Dirty Pages und löst Paging-IO zum darunterliegenden Dateisystem aus — bei einer Memory-Mapped File entspricht das einem `fsync` nur des dirty-Teilsatzes. Für Pagefile-gestützte oder PAGE_NOACCESS-Regionen ist der Aufruf effektiv ein No-op. Das Feld `IoStatus.Information` enthält die tatsächlich geschriebene Bytezahl.
Häufige Malware-Nutzung
Eingesetzt in Persistenz- und Ransomware-Ketten, die On-Disk-Artefakte über eine Memory-Mapped View statt über `WriteFile` ändern: der Angreifer mappt eine ausführbare Datei, eine INI, ein Scheduled-Task-XML oder die LSA-Notification-Package-Liste mit `PAGE_READWRITE`, patcht die Bytes und ruft `NtFlushVirtualMemory` auf, um den Schreibvorgang *jetzt* zu erzwingen, statt auf den Lazy Writer zu warten — wichtig, wenn die nächste Stufe (Reboot, Service-Neustart oder unmittelbares Exec) die On-Disk-Änderung sehen muss. Auch zu sehen in `MiniDumpWriteDump`-artigen Credential-Theft-Ketten, die die Dump-Datei in-place mappen und editieren. Einige Ransomware-Familien rufen den Syscall an, um sicherzustellen, dass die verschlüsselten Bytes die Platte erreichen, bevor sie Shadow Copies löschen und einen erzwungenen Reboot auslösen.
Erkennungsmöglichkeiten
Sysmon Event 11 (FileCreate) feuert beim Flush nicht — die Datei existiert bereits. Die zuverlässigste Telemetrie ist das ETW-Event `Microsoft-Windows-Kernel-FileIO` `FileIo/OperationEnd` für `WRITE`-Operationen, deren `IrpFlags` Paging-IO anzeigen, korreliert zu einem Prozess, der kürzlich eine schreibbare Section über eine sensible Datei (ausführbare Datei in `\System32`, Scheduled-Task-XML, durch `ImagePath` in `\Registry\Machine\System\CurrentControlSet\Services\*` referenzierte Binärdatei) erhalten hat. EDRs, die `NtCreateSection` gegen hochwertige Pfade hooken und nachfolgende `NtFlushVirtualMemory`-Aufrufe gegen diese Views verfolgen, erfassen das In-place-Patch-Muster.
Direkte Syscall-Beispiele
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
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20