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

NtLoadKey

Mounts a registry hive file under a target key — the syscall behind offline SAM/SYSTEM loading.

Prototype

NTSTATUS NtLoadKey(
  POBJECT_ATTRIBUTES  KeyObjectAttributes,
  POBJECT_ATTRIBUTES  FileObjectAttributes
);

Arguments

NameTypeDirDescription
KeyObjectAttributesPOBJECT_ATTRIBUTESinTarget registry path the hive is mounted under (e.g. \Registry\Machine\OFFLINE_SAM).
FileObjectAttributesPOBJECT_ATTRIBUTESinNT path to the hive file on disk (e.g. \??\C:\Windows\System32\config\SAM).

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070xF6win10-1507
Win10 16070xFBwin10-1607
Win10 17030xFFwin10-1703
Win10 17090x100win10-1709
Win10 18030x101win10-1803
Win10 18090x101win10-1809
Win10 19030x102win10-1903
Win10 19090x102win10-1909
Win10 20040x107win10-2004
Win10 20H20x107win10-20h2
Win10 21H10x107win10-21h1
Win10 21H20x108win10-21h2
Win10 22H20x108win10-22h2
Win11 21H20x10Dwin11-21h2
Win11 22H20x10Ewin11-22h2
Win11 23H20x10Ewin11-23h2
Win11 24H20x110win11-24h2
Server 20160xFBwinserver-2016
Server 20190x101winserver-2019
Server 20220x10Cwinserver-2022
Server 20250x110winserver-2025

Kernel module

ntoskrnl.exeNtLoadKey

Related APIs

RegLoadKeyWRegUnLoadKeyWNtLoadKey2NtLoadKeyExNtUnloadKeyNtSaveKey

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

Undocumented notes

NtLoadKey mounts a registry hive file (binary on-disk format used by `SAM`, `SECURITY`, `SYSTEM`, `SOFTWARE`, `NTUSER.DAT`) under an arbitrary subkey of `\Registry\Machine\` or `\Registry\User\`. The caller must hold **SeRestorePrivilege** (and typically SeBackupPrivilege). NtLoadKey is the two-argument original; NtLoadKey2 / NtLoadKeyEx add flags such as `REG_HIVE_NO_RM` (no transactional registry / KTM) and explicit trust-class parameters. SSN drifts every feature update.

Common malware usage

The canonical abuse is **offline credential dumping**. An attacker who has already obtained a copy of the `SAM`, `SYSTEM` and `SECURITY` hive files (via VSS, raw NTFS read, or backup theft) re-mounts them on a different machine with NtLoadKey under a chosen subkey (often `HKLM\OFFLINE_SAM`), then reads the encrypted hashes and SYSKEY material with normal registry APIs — exactly the path Impacket's `secretsdump.py` offline mode and Mimikatz's `lsadump::sam /system:SYSTEM /sam:SAM` take. A secondary use is *stealth persistence*: malware drops a small hive into a non-standard location and mounts it at runtime to hide config or Run-key equivalents from registry baseliners that only audit the well-known hives.

Detection opportunities

Sysmon Event ID 12/13 fires on registry object creation / value set under unusual mount points (`HKLM\OFFLINE*`, `HKLM\TEMP_*`). The ETW provider `Microsoft-Windows-Kernel-Registry` exposes hive-load events with both the mount key and the source file path. Adjustment of SeRestorePrivilege right before a load is a strong leading indicator (Sysmon Event 4673 with privilege list). Loads of hive files outside `%SystemRoot%\System32\config\` and `%UserProfile%\NTUSER.DAT` are almost always worth alerting on.

Direct syscall examples

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 ENDP

cOffline 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