> Windows Syscalls
ntoskrnl.exeT1547.001T1112T1106

NtFlushKey

Forces all pending changes to a registry key to be written to its backing hive on disk.

Prototype

NTSTATUS NtFlushKey(
  HANDLE  KeyHandle
);

Arguments

NameTypeDirDescription
KeyHandleHANDLEinOpen handle to any key in the hive to be flushed; the entire hive containing the key is committed.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070xDAwin10-1507
Win10 16070xDDwin10-1607
Win10 17030xE0win10-1703
Win10 17090xE1win10-1709
Win10 18030xE2win10-1803
Win10 18090xE3win10-1809
Win10 19030xE4win10-1903
Win10 19090xE4win10-1909
Win10 20040xE9win10-2004
Win10 20H20xE9win10-20h2
Win10 21H10xE9win10-21h1
Win10 21H20xEAwin10-21h2
Win10 22H20xEAwin10-22h2
Win11 21H20xEFwin11-21h2
Win11 22H20xF0win11-22h2
Win11 23H20xF0win11-23h2
Win11 24H20xF2win11-24h2
Server 20160xDDwinserver-2016
Server 20190xE3winserver-2019
Server 20220xEEwinserver-2022
Server 20250xF2winserver-2025

Kernel module

ntoskrnl.exeNtFlushKey

Related APIs

RegFlushKeyNtFlushBuffersFileNtSaveKeyNtSetValueKey

Syscall stub

4C 8B D1            mov r10, rcx
B8 F2 00 00 00      mov eax, 0xF2
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

NtFlushKey commits all in-memory changes for the *hive* containing the supplied key to its backing disk file. Despite the per-key signature, the kernel flushes the entire hive — the configuration manager does not maintain per-key dirty bits at flush granularity. Note the difference from `NtFlushBuffersFile`: that targets a file via FILE_OBJECT, NtFlushKey targets a hive via a key handle. The Win32 wrapper is `RegFlushKey`. The lazy flusher normally writes dirty hives every five seconds (`CmpLazyFlushIntervalInSeconds`); NtFlushKey forces immediate write-back.

Common malware usage

The principal abuse pattern is **anti-forensics around persistence**: after writing a Run key, scheduled-task config, service entry, or COM-hijack value, malware calls NtFlushKey to guarantee the change is persisted to disk *before* the next unclean reboot — which is meaningful when the lazy-flush window is wider than the expected window of opportunity (BSOD-triggering exploit, planned hard-reset, host-killing ransomware). A secondary pattern: some droppers flush a freshly written autorun entry, then immediately delete the on-disk binary they referenced, leaving Run-key metadata pointing at content that may or may not still exist at boot — a tiny anti-analysis nuisance.

Detection opportunities

On its own, NtFlushKey is uninteresting — explorer.exe and many drivers call it routinely. The useful signal is the *sequence*: Sysmon Event 13 (registry value set) on a known persistence key, immediately followed by a flush from the same PID, particularly when that PID is unsigned or recently created. ETW `Microsoft-Windows-Kernel-Registry` exposes flush events with key path and PID. Correlate against Sysmon Event 11 (file create / delete) for the binary the persistence entry references.

Direct syscall examples

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtFlushKey (SSN 0xF2 on Win11 24H2 — drifts per build)
NtFlushKey PROC
    mov  r10, rcx          ; KeyHandle
    mov  eax, 0F2h         ; SSN
    syscall
    ret
NtFlushKey ENDP

cPersistence write-then-flush

// Write a Run key and immediately flush — defeats lazy-flush race on hard reboot.
#include <windows.h>

LONG PersistAndFlush(LPCWSTR name, LPCWSTR cmd) {
    HKEY hRun = NULL;
    LONG s = RegCreateKeyExW(HKEY_CURRENT_USER,
        L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
        0, NULL, 0, KEY_SET_VALUE, NULL, &hRun, NULL);
    if (s != ERROR_SUCCESS) return s;
    s = RegSetValueExW(hRun, name, 0, REG_SZ,
                       (const BYTE*)cmd,
                       (DWORD)((wcslen(cmd) + 1) * sizeof(WCHAR)));
    if (s == ERROR_SUCCESS) RegFlushKey(hRun);   // ← NtFlushKey under the hood
    RegCloseKey(hRun);
    return s;
}

rustDirect NtFlushKey

// Cargo: ntapi = "0.4"
use ntapi::ntregapi::NtFlushKey;
use winapi::shared::ntdef::{HANDLE, NTSTATUS};

unsafe fn flush(key: HANDLE) -> NTSTATUS {
    NtFlushKey(key)
}

MITRE ATT&CK mappings

Last verified: 2026-05-20