NtLoadKey
Mountet eine Registry-Hive-Datei unter einem Zielschlüssel — der Syscall hinter dem Offline-Laden von SAM/SYSTEM.
Prototyp
NTSTATUS NtLoadKey( POBJECT_ATTRIBUTES KeyObjectAttributes, POBJECT_ATTRIBUTES FileObjectAttributes );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| KeyObjectAttributes | POBJECT_ATTRIBUTES | in | Ziel-Registry-Pfad, unter dem die Hive gemountet wird (z. B. \Registry\Machine\OFFLINE_SAM). |
| FileObjectAttributes | POBJECT_ATTRIBUTES | in | NT-Pfad zur Hive-Datei auf der Platte (z. B. \??\C:\Windows\System32\config\SAM). |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0xF6 | win10-1507 |
| Win10 1607 | 0xFB | win10-1607 |
| Win10 1703 | 0xFF | win10-1703 |
| Win10 1709 | 0x100 | win10-1709 |
| Win10 1803 | 0x101 | win10-1803 |
| Win10 1809 | 0x101 | win10-1809 |
| Win10 1903 | 0x102 | win10-1903 |
| Win10 1909 | 0x102 | win10-1909 |
| Win10 2004 | 0x107 | win10-2004 |
| Win10 20H2 | 0x107 | win10-20h2 |
| Win10 21H1 | 0x107 | win10-21h1 |
| Win10 21H2 | 0x108 | win10-21h2 |
| Win10 22H2 | 0x108 | win10-22h2 |
| Win11 21H2 | 0x10D | win11-21h2 |
| Win11 22H2 | 0x10E | win11-22h2 |
| Win11 23H2 | 0x10E | win11-23h2 |
| Win11 24H2 | 0x110 | win11-24h2 |
| Server 2016 | 0xFB | winserver-2016 |
| Server 2019 | 0x101 | winserver-2019 |
| Server 2022 | 0x10C | winserver-2022 |
| Server 2025 | 0x110 | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 10 01 00 00 mov eax, 0x110 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
NtLoadKey mountet eine Registry-Hive-Datei (binäres On-Disk-Format von `SAM`, `SECURITY`, `SYSTEM`, `SOFTWARE`, `NTUSER.DAT`) unter einem beliebigen Unterschlüssel von `\Registry\Machine\` oder `\Registry\User\`. Der Caller benötigt **SeRestorePrivilege** (und üblicherweise SeBackupPrivilege). NtLoadKey ist die Zwei-Argument-Originalversion; NtLoadKey2 / NtLoadKeyEx fügen Flags wie `REG_HIVE_NO_RM` (kein transaktionales Registry / KTM) und explizite Trust-Class-Parameter hinzu. Die SSN driftet bei jedem Feature-Update.
Häufige Malware-Nutzung
Der kanonische Missbrauch ist **Offline-Credential-Dumping**. Ein Angreifer, der bereits eine Kopie der Hive-Dateien `SAM`, `SYSTEM` und `SECURITY` besitzt (via VSS, rohem NTFS-Lesen oder gestohlenem Backup), mountet sie auf einer anderen Maschine mit NtLoadKey unter einem gewählten Unterschlüssel (oft `HKLM\OFFLINE_SAM`) und liest dann die verschlüsselten Hashes und das SYSKEY-Material mit normalen Registry-APIs — exakt der Weg des Offline-Modus von Impackets `secretsdump.py` und von Mimikatz' `lsadump::sam /system:SYSTEM /sam:SAM`. Sekundärnutzung: *stealthy persistence* — Malware legt eine kleine Hive an einem nicht-standardisierten Ort ab und mountet sie zur Laufzeit, um Konfiguration oder Run-Key-Äquivalente vor Registry-Baselinern zu verstecken, die nur die bekannten Hives auditieren.
Erkennungsmöglichkeiten
Sysmon Event ID 12/13 feuert bei Registry-Objekterzeugung / Wert-Set unter ungewöhnlichen Mountpunkten (`HKLM\OFFLINE*`, `HKLM\TEMP_*`). Der ETW-Provider `Microsoft-Windows-Kernel-Registry` liefert Hive-Load-Events mit Mount-Key und Quellpfad. Anpassung von SeRestorePrivilege unmittelbar vor einem Load ist ein starker Frühindikator (Sysmon Event 4673 mit Privilegienliste). Loads von Hive-Dateien außerhalb von `%SystemRoot%\System32\config\` und `%UserProfile%\NTUSER.DAT` sind nahezu immer alarmwürdig.
Direkte Syscall-Beispiele
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtLoadKey (SSN 0x110 on Win11 24H2 — drifts per build)
NtLoadKey PROC
mov r10, rcx ; KeyObjectAttributes
mov eax, 110h ; SSN
syscall
ret
NtLoadKey ENDPcOffline SAM credential extraction sketch
// Mount a stolen SAM hive offline and let LSAlib decrypt the NTLM hashes.
// Requires SeRestorePrivilege + SeBackupPrivilege.
#include <windows.h>
#include <winternl.h>
static void EnablePriv(LPCWSTR p) {
HANDLE tok; TOKEN_PRIVILEGES tp = {0};
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &tok);
LookupPrivilegeValueW(NULL, p, &tp.Privileges[0].Luid);
tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(tok, FALSE, &tp, 0, NULL, NULL);
CloseHandle(tok);
}
int main(void) {
EnablePriv(SE_RESTORE_NAME);
EnablePriv(SE_BACKUP_NAME);
// RegLoadKeyW is the documented wrapper that ends at NtLoadKey under the hood.
LONG s = RegLoadKeyW(HKEY_LOCAL_MACHINE, L"OFFLINE_SAM",
L"C:\\stolen\\SAM");
if (s == ERROR_SUCCESS) {
// Now HKLM\OFFLINE_SAM\SAM\Domains\Account\Users\... is readable.
RegUnLoadKeyW(HKEY_LOCAL_MACHINE, L"OFFLINE_SAM");
}
return s;
}rustDirect NtLoadKey via ntapi
// Cargo: ntapi = "0.4", winapi = { version = "0.3", features = ["ntdef"] }
use ntapi::ntregapi::NtLoadKey;
use ntapi::ntrtl::{RtlInitUnicodeString};
use winapi::shared::ntdef::{OBJECT_ATTRIBUTES, UNICODE_STRING, OBJ_CASE_INSENSITIVE};
unsafe fn load_offline_hive(mount: &str, file: &str) -> i32 {
let mut k_us: UNICODE_STRING = std::mem::zeroed();
let mut f_us: UNICODE_STRING = std::mem::zeroed();
let mw: Vec<u16> = mount.encode_utf16().chain([0]).collect();
let fw: Vec<u16> = file.encode_utf16().chain([0]).collect();
RtlInitUnicodeString(&mut k_us, mw.as_ptr());
RtlInitUnicodeString(&mut f_us, fw.as_ptr());
let mut k_oa: OBJECT_ATTRIBUTES = std::mem::zeroed();
let mut f_oa: OBJECT_ATTRIBUTES = std::mem::zeroed();
k_oa.Length = std::mem::size_of::<OBJECT_ATTRIBUTES>() as u32;
k_oa.ObjectName = &mut k_us; k_oa.Attributes = OBJ_CASE_INSENSITIVE;
f_oa.Length = std::mem::size_of::<OBJECT_ATTRIBUTES>() as u32;
f_oa.ObjectName = &mut f_us; f_oa.Attributes = OBJ_CASE_INSENSITIVE;
NtLoadKey(&mut k_oa, &mut f_oa)
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20