> Windows Syscalls
ntoskrnl.exeT1620T1055.001T1106

NtFlushInstructionCache

Invalidates the instruction cache for a region in a target process so freshly written code can be executed.

Prototype

NTSTATUS NtFlushInstructionCache(
  HANDLE  ProcessHandle,
  PVOID   BaseAddress,
  SIZE_T  Length
);

Arguments

NameTypeDirDescription
ProcessHandleHANDLEinProcess whose I-cache to flush. NtCurrentProcess() for self.
BaseAddressPVOIDinStart of the region whose instructions should be invalidated. NULL flushes the entire I-cache.
LengthSIZE_TinLength of the region in bytes. Ignored when BaseAddress is NULL.

Syscall IDs by 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 module

ntoskrnl.exeNtFlushInstructionCache

Related 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

Undocumented notes

On x86/x64, the architectural rules already serialise instruction fetches against data writes after a `cpuid`/`iret`/branch on the same logical processor, so the syscall is mostly a no-op for self-flushes on the current core — its real job is to broadcast an IPI to every other processor running the target process so they invalidate their L1 I-cache lines. On ARM64 (where the rules are weaker), it issues the `ic ivau` / `dsb ish` / `isb` sequence the architecture requires after any code modification. The Win32 thin wrapper is `FlushInstructionCache`, which is unconditionally called by `JIT_GenericResolverCommon` in CLR, by V8's `CodeRange::CommitPages`, by Chakra, and by every reflective loader worth its salt.

Common malware usage

Mandatory step in **every** reflective shellcode or PE loader that writes executable bytes at runtime. The classic sequence is `NtAllocateVirtualMemory(MEM_COMMIT, PAGE_READWRITE)` → memcpy payload → `NtProtectVirtualMemory(PAGE_EXECUTE_READ)` → `NtFlushInstructionCache`. Skipping the flush is the #1 reason brand-new loaders crash on ARM64 Windows but work on x64 — and even on x64, omitting it produces hard-to-reproduce intermittent crashes when the target page happens to be touched by a sibling SMT thread. ScareCrow, Donut-generated stagers, Stephen Fewer's classic reflective DLL injection, MemoryModule, and virtually every Cobalt Strike user-defined reflective loader call it. Direct-syscall loaders that resolve the SSN dynamically (Hell's Gate / Halo's Gate / Tartarus' Gate) usually hardcode this one too because it's so cheap.

Detection opportunities

Per call, almost no signal in legitimate software because JITs call it constantly. The interesting telemetry is the *combination*: a process that has neither a managed runtime (CLR, V8, JVM, Chakra) nor a browser/Electron content host calling `NtFlushInstructionCache` against a region that was recently `PAGE_EXECUTE_READWRITE` or has no `MEMORY_BASIC_INFORMATION::AllocationBase` mapping to an on-disk image is a textbook reflective-loader IOC. ETW `Microsoft-Windows-Threat-Intelligence` (Defender ETW-TI) emits a `VirtualProtect`-like event for the protection transition that *immediately precedes* the flush in nearly every loader — pair the two.

Direct syscall examples

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