> Windows Syscalls
ntoskrnl.exeT1003.002T1112T1070

NtUnloadKey

Detaches a previously-loaded registry hive from the configuration manager.

Prototype

NTSTATUS NtUnloadKey(
  POBJECT_ATTRIBUTES TargetKey
);

Arguments

NameTypeDirDescription
TargetKeyPOBJECT_ATTRIBUTESinObject attributes naming the hive root key to unload, e.g. \Registry\User\TempHive.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x1AAwin10-1507
Win10 16070x1B3win10-1607
Win10 17030x1B9win10-1703
Win10 17090x1BDwin10-1709
Win10 18030x1BFwin10-1803
Win10 18090x1C0win10-1809
Win10 19030x1C1win10-1903
Win10 19090x1C1win10-1909
Win10 20040x1C7win10-2004
Win10 20H20x1C7win10-20h2
Win10 21H10x1C7win10-21h1
Win10 21H20x1C9win10-21h2
Win10 22H20x1C9win10-22h2
Win11 21H20x1D3win11-21h2
Win11 22H20x1D7win11-22h2
Win11 23H20x1D7win11-23h2
Win11 24H20x1DAwin11-24h2
Server 20160x1B3winserver-2016
Server 20190x1C0winserver-2019
Server 20220x1CFwinserver-2022
Server 20250x1DAwinserver-2025

Kernel module

ntoskrnl.exeNtUnloadKey

Related APIs

RegUnLoadKeyWRegLoadKeyWNtLoadKeyNtLoadKey2NtUnloadKey2NtUnloadKeyEx

Syscall stub

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

NtUnloadKey forcibly detaches a registry hive that was previously attached via NtLoadKey / `reg load`. The caller must hold SeRestorePrivilege; the request fails with STATUS_PRIVILEGE_NOT_HELD otherwise. Internally CmUnloadKey walks the CM_KEY_CONTROL_BLOCK chain and writes back any pending changes before tearing down the hive — so a sloppy unload of a still-in-use hive can race against another thread's open key handles and return STATUS_CANNOT_DELETE. A safer variant NtUnloadKey2 takes an `Flags` argument to force the detach.

Common malware usage

Used as the cleanup half of an offline-hive abuse: an operator stages `reg load HKU\Tmp C:\Users\victim\NTUSER.DAT`, extracts credentials / harvests Run keys from the mounted hive, then issues NtUnloadKey to detach the hive and remove the obvious indicator. The same primitive is used as light anti-forensics — by unloading a hive that a triage tool has just mapped, the implant can starve the analyst's view. Some loaders also unload temporarily-mounted hives used for AppCompat / TypedURL tampering.

Detection opportunities

Sysmon doesn't have a dedicated event for hive unload, but ETW provider Microsoft-Windows-Kernel-Registry emits Event ID 1 (HiveLoad) and Event ID 2 (HiveUnload) with the hive path. Audit subcategory "Registry" (4657 / 4660) does not cover hive load/unload; you need "Other System Events" (4660-class) or the Kernel-Registry ETW provider directly. Defender ATP raises a high-fidelity alert on `reg unload` of hives outside %SystemRoot%\System32\config — anomalous when paired with a preceding hive load against another user's profile.

Direct syscall examples

asmx64 direct stub (Win11 24H2 SSN 0x1DA)

NtUnloadKey PROC
    mov  r10, rcx
    mov  eax, 1DAh
    syscall
    ret
NtUnloadKey ENDP

cOffline hive cleanup after credential harvest

// After mounting another user's NTUSER.DAT via NtLoadKey and harvesting,
// detach it so `reg query HKU` no longer reveals the staged hive.
UNICODE_STRING name;
RtlInitUnicodeString(&name, L"\\Registry\\User\\TempHive");

OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &name, OBJ_CASE_INSENSITIVE, NULL, NULL);

NTSTATUS st = NtUnloadKey(&oa);
if (st == STATUS_CANNOT_DELETE) {
    // Some handle is still open; force via NtUnloadKey2 with REG_FORCE_UNLOAD.
}

rustwindows-sys + SeRestorePrivilege enablement

// Cargo: windows-sys = "0.59" (Win32_Security, Win32_System_Registry)
// Caller must first enable SeRestorePrivilege via NtAdjustPrivilegesToken.
unsafe {
    let mut name = make_unicode_string("\\Registry\\User\\TempHive");
    let mut oa = OBJECT_ATTRIBUTES {
        Length: size_of::<OBJECT_ATTRIBUTES>() as u32,
        ObjectName: &mut name,
        Attributes: OBJ_CASE_INSENSITIVE,
        ..zeroed()
    };
    let st = NtUnloadKey(&mut oa);
    assert!(st == 0, "NtUnloadKey failed: {st:#x}");
}

MITRE ATT&CK mappings

Last verified: 2026-05-20