NtFlushInstructionCache
Invalidiert den Instruction-Cache für eine Region in einem Zielprozess, damit frisch geschriebener Code ausgeführt werden kann.
Prototyp
NTSTATUS NtFlushInstructionCache( HANDLE ProcessHandle, PVOID BaseAddress, SIZE_T Length );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Prozess, dessen I-Cache geleert wird. NtCurrentProcess() für sich selbst. |
| BaseAddress | PVOID | in | Beginn der zu invalidierenden Region. NULL leert den gesamten I-Cache. |
| Length | SIZE_T | in | Länge der Region in Bytes. Wird ignoriert, wenn BaseAddress NULL ist. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0xD9 | win10-1507 |
| Win10 1607 | 0xDC | win10-1607 |
| Win10 1703 | 0xDF | win10-1703 |
| Win10 1709 | 0xE0 | win10-1709 |
| Win10 1803 | 0xE1 | win10-1803 |
| Win10 1809 | 0xE2 | win10-1809 |
| Win10 1903 | 0xE3 | win10-1903 |
| Win10 1909 | 0xE3 | win10-1909 |
| Win10 2004 | 0xE8 | win10-2004 |
| Win10 20H2 | 0xE8 | win10-20h2 |
| Win10 21H1 | 0xE8 | win10-21h1 |
| Win10 21H2 | 0xE9 | win10-21h2 |
| Win10 22H2 | 0xE9 | win10-22h2 |
| Win11 21H2 | 0xEE | win11-21h2 |
| Win11 22H2 | 0xEF | win11-22h2 |
| Win11 23H2 | 0xEF | win11-23h2 |
| Win11 24H2 | 0xF1 | win11-24h2 |
| Server 2016 | 0xDC | winserver-2016 |
| Server 2019 | 0xE2 | winserver-2019 |
| Server 2022 | 0xED | winserver-2022 |
| Server 2025 | 0xF1 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 F1 00 00 00 mov eax, 0xF1 ; 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
Auf x86/x64 serialisieren die Architekturregeln Instruction-Fetches gegenüber Datenschreibvorgängen bereits nach einem `cpuid`/`iret`/Branch auf demselben logischen Prozessor, sodass der Syscall für Self-Flushes auf dem aktuellen Kern weitgehend ein No-op ist — seine eigentliche Aufgabe ist es, ein IPI an jeden weiteren Prozessor zu senden, der den Zielprozess ausführt, damit dieser seine L1-I-Cache-Lines invalidiert. Auf ARM64 (mit schwächeren Regeln) gibt er die von der Architektur nach jeder Codeänderung geforderte Sequenz `ic ivau` / `dsb ish` / `isb` aus. Der dünne Win32-Wrapper `FlushInstructionCache` wird bedingungslos von `JIT_GenericResolverCommon` in der CLR, von V8s `CodeRange::CommitPages`, von Chakra und von jedem ernstzunehmenden Reflective-Loader aufgerufen.
Häufige Malware-Nutzung
Pflichtschritt in **jedem** Reflective-Shellcode- oder PE-Loader, der zur Laufzeit ausführbare Bytes schreibt. Die klassische Sequenz: `NtAllocateVirtualMemory(MEM_COMMIT, PAGE_READWRITE)` → memcpy Payload → `NtProtectVirtualMemory(PAGE_EXECUTE_READ)` → `NtFlushInstructionCache`. Den Flush auszulassen ist Grund Nr. 1, warum brandneue Loader unter ARM64-Windows abstürzen, unter x64 aber laufen — und selbst unter x64 produziert das Weglassen schwer reproduzierbare, sporadische Crashes, sobald ein Sibling-SMT-Thread die Zielseite berührt. ScareCrow, Donut-Stager, Stephen Fewers klassisches Reflective DLL Injection, MemoryModule und praktisch jeder User-Defined Reflective Loader für Cobalt Strike rufen es auf. Direct-Syscall-Loader, die die SSN dynamisch (Hell's Gate / Halo's Gate / Tartarus' Gate) auflösen, kodieren diese typischerweise hart, weil sie so günstig ist.
Erkennungsmöglichkeiten
Pro Aufruf in legitimer Software nahezu kein Signal, da JITs den Aufruf permanent absetzen. Interessant ist die *Kombination*: ein Prozess ohne Managed Runtime (CLR, V8, JVM, Chakra) und ohne Browser/Electron-Content-Host, der `NtFlushInstructionCache` auf eine Region anwendet, die kürzlich `PAGE_EXECUTE_READWRITE` war oder deren `MEMORY_BASIC_INFORMATION::AllocationBase` keiner On-Disk-Image-Datei zugeordnet ist, ist ein Lehrbuch-IOC für Reflective Loader. ETW `Microsoft-Windows-Threat-Intelligence` (Defender ETW-TI) emittiert für den Schutzwechsel, der dem Flush in nahezu jedem Loader *unmittelbar* vorausgeht, ein `VirtualProtect`-artiges Event — beide korrelieren.
Direkte Syscall-Beispiele
cReflective loader fix-up
// Final step of a reflective PE loader.
// pImageBase points at freshly relocated bytes; size is the SizeOfImage.
// 1) Flip the .text section to PAGE_EXECUTE_READ.
ULONG oldProt;
NtProtectVirtualMemory(NtCurrentProcess(),
(PVOID*)&pTextBase, &textSize,
PAGE_EXECUTE_READ, &oldProt);
// 2) Invalidate the I-cache across every core that runs us.
NtFlushInstructionCache(NtCurrentProcess(), pTextBase, textSize);
// 3) Safe to call the DLL's entry point.
DllMain_t entry = (DllMain_t)((BYTE*)pImageBase + nt->OptionalHeader.AddressOfEntryPoint);
entry((HMODULE)pImageBase, DLL_PROCESS_ATTACH, NULL);asmx64 direct stub (Win11 24H2)
; NtFlushInstructionCache direct stub — SSN 0xF1 on Win11 24H2
NtFlushInstructionCache PROC
mov r10, rcx
mov eax, 0F1h
syscall
ret
NtFlushInstructionCache ENDPrustSelf-flush after writing shellcode
use ntapi::ntmmapi::NtFlushInstructionCache;
use winapi::shared::ntdef::HANDLE;
unsafe fn flush_self(addr: *mut u8, len: usize) {
let s = NtFlushInstructionCache(
-1isize as HANDLE, // NtCurrentProcess()
addr as _,
len,
);
assert!(s >= 0);
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20