> Windows Syscalls
ntoskrnl.exeT1620T1055.001T1106

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

NameTypeDirDescription
ProcessHandleHANDLEinProzess, dessen I-Cache geleert wird. NtCurrentProcess() für sich selbst.
BaseAddressPVOIDinBeginn der zu invalidierenden Region. NULL leert den gesamten I-Cache.
LengthSIZE_TinLänge der Region in Bytes. Wird ignoriert, wenn BaseAddress NULL ist.

Syscall-IDs pro Windows-Version

Windows-VersionSyscall-IDBuild
Win10 15070xD9win10-1507
Win10 16070xDCwin10-1607
Win10 17030xDFwin10-1703
Win10 17090xE0win10-1709
Win10 18030xE1win10-1803
Win10 18090xE2win10-1809
Win10 19030xE3win10-1903
Win10 19090xE3win10-1909
Win10 20040xE8win10-2004
Win10 20H20xE8win10-20h2
Win10 21H10xE8win10-21h1
Win10 21H20xE9win10-21h2
Win10 22H20xE9win10-22h2
Win11 21H20xEEwin11-21h2
Win11 22H20xEFwin11-22h2
Win11 23H20xEFwin11-23h2
Win11 24H20xF1win11-24h2
Server 20160xDCwinserver-2016
Server 20190xE2winserver-2019
Server 20220xEDwinserver-2022
Server 20250xF1winserver-2025

Kernel-Modul

ntoskrnl.exeNtFlushInstructionCache

Verwandte APIs

FlushInstructionCacheNtProtectVirtualMemoryNtAllocateVirtualMemoryNtWriteVirtualMemoryVirtualProtectEx

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.

Erkennungs­mö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 ENDP

rustSelf-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