NtOpenKeyEx
Extended variant of NtOpenKey accepting OpenOptions — required for symlink-following and backup-semantics opens.
Prototype
NTSTATUS NtOpenKeyEx( PHANDLE KeyHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, ULONG OpenOptions );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| KeyHandle | PHANDLE | out | Receives the handle to the opened key on success. |
| DesiredAccess | ACCESS_MASK | in | Access mask, e.g. KEY_READ, KEY_QUERY_VALUE, KEY_WRITE, KEY_ALL_ACCESS. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | OBJECT_ATTRIBUTES with the NT registry path. |
| OpenOptions | ULONG | in | Flags: REG_OPTION_BACKUP_RESTORE (use SeBackupPrivilege), REG_OPTION_OPEN_LINK (don't follow symlink). |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x10C | win10-1507 |
| Win10 1607 | 0x111 | win10-1607 |
| Win10 1703 | 0x115 | win10-1703 |
| Win10 1709 | 0x117 | win10-1709 |
| Win10 1803 | 0x119 | win10-1803 |
| Win10 1809 | 0x11A | win10-1809 |
| Win10 1903 | 0x11B | win10-1903 |
| Win10 1909 | 0x11B | win10-1909 |
| Win10 2004 | 0x120 | win10-2004 |
| Win10 20H2 | 0x120 | win10-20h2 |
| Win10 21H1 | 0x120 | win10-21h1 |
| Win10 21H2 | 0x121 | win10-21h2 |
| Win10 22H2 | 0x121 | win10-22h2 |
| Win11 21H2 | 0x127 | win11-21h2 |
| Win11 22H2 | 0x129 | win11-22h2 |
| Win11 23H2 | 0x129 | win11-23h2 |
| Win11 24H2 | 0x12B | win11-24h2 |
| Server 2016 | 0x111 | winserver-2016 |
| Server 2019 | 0x11A | winserver-2019 |
| Server 2022 | 0x126 | winserver-2022 |
| Server 2025 | 0x12B | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 2B 01 00 00 mov eax, 0x12B 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
NtOpenKeyEx is `NtOpenKey + OpenOptions`. The only two options that matter offensively are `REG_OPTION_BACKUP_RESTORE` (`0x4`) — which causes the configuration manager to skip the DACL check and use the caller's `SeBackupPrivilege` / `SeRestorePrivilege` instead — and `REG_OPTION_OPEN_LINK` (`0x8`) — which opens a symbolic-link key without following the target, allowing audit of redirections. The SSN drifts significantly across builds because adjacent syscalls were added/removed: Win10 1909 = 0x11B, Win10 22H2 = 0x121, Win11 24H2 = 0x12B. Hardcoding SSNs is brittle here — Hell's Gate / Halo's Gate dynamic lookup is mandatory if you want cross-build support.
Common malware usage
Two distinct offensive patterns. (1) **Privilege-mediated hive access**: with `SeBackupPrivilege` enabled (granted to Administrators, Backup Operators), opening with `REG_OPTION_BACKUP_RESTORE` bypasses the DACL on `\Registry\Machine\SAM` and `\Registry\Machine\SECURITY` without needing SYSTEM impersonation — this is what `reg.exe save HKLM\SAM` does under the hood, and what most modern SAM dumpers (CrackMapExec/NetExec local SAM module, secretsdump.py with `-local-auth`) rely on. (2) **Symlink-aware persistence**: APT-style implants chain `NtOpenKeyEx` with `REG_OPTION_OPEN_LINK` to introspect registry symbolic links planted by themselves or earlier intrusions for hijack persistence (Image File Execution Options redirected to attacker keys, COM CLSID symlinked into HKCU). Forensics tools doing the same audit benefit from the same flag.
Detection opportunities
Same providers as NtOpenKey — Microsoft-Windows-Kernel-Registry ETW logs each open with PID and access. The fingerprint of `REG_OPTION_BACKUP_RESTORE` is visible: the requesting process must hold `SeBackupPrivilege` *enabled*, which Sysmon Event ID 4673 (Sensitive Privilege Use) records when actually exercised — pair `SeBackupPrivilege` use with a subsequent open of `\Registry\Machine\SAM` or `\Registry\Machine\SECURITY` for a very high-fidelity SAM-dump signal. EDRs that register `CmRegisterCallbackEx` see the OpenOptions value in `RegNtPreOpenKeyEx`. Sysmon's `RegistryEvent` `Sensitive Registry Object` rule from SwiftOnSecurity's template catches this. Audit Subcategory "Sensitive Privilege Use" (4673/4674) with privilege filter `SeBackupPrivilege` is the cheapest baseline.
Direct syscall examples
cOpen SAM hive with backup privilege
// Caller must first enable SeBackupPrivilege in its token.
#define REG_OPTION_BACKUP_RESTORE 0x00000004
UNICODE_STRING name;
RtlInitUnicodeString(&name, L"\\Registry\\Machine\\SAM");
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &name, OBJ_CASE_INSENSITIVE, NULL, NULL);
HANDLE hKey = NULL;
NTSTATUS s = NtOpenKeyEx(&hKey, KEY_READ, &oa,
REG_OPTION_BACKUP_RESTORE);asmDirect stub (SSN 0x12B, Win11 24H2)
; SSN drifts across builds — resolve dynamically when targeting multiple.
NtOpenKeyEx PROC
mov r10, rcx
mov eax, 12Bh ; Win11 24H2
syscall
ret
NtOpenKeyEx ENDPrustOpen key but don't follow registry symlink
use ntapi::ntregapi::NtOpenKeyEx;
use ntapi::ntrtl::RtlInitUnicodeString;
use winapi::shared::ntdef::{HANDLE, OBJECT_ATTRIBUTES, OBJ_CASE_INSENSITIVE, UNICODE_STRING};
const REG_OPTION_OPEN_LINK: u32 = 0x0000_0008;
pub unsafe fn open_link(path: *const u16) -> Option<HANDLE> {
let mut us: UNICODE_STRING = core::mem::zeroed();
RtlInitUnicodeString(&mut us, path);
let mut oa = OBJECT_ATTRIBUTES {
Length: core::mem::size_of::<OBJECT_ATTRIBUTES>() as u32,
RootDirectory: core::ptr::null_mut(),
ObjectName: &mut us,
Attributes: OBJ_CASE_INSENSITIVE,
SecurityDescriptor: core::ptr::null_mut(),
SecurityQualityOfService: core::ptr::null_mut(),
};
let mut h: HANDLE = core::ptr::null_mut();
if NtOpenKeyEx(&mut h, 0x20019 /* KEY_READ */, &mut oa, REG_OPTION_OPEN_LINK) == 0 {
Some(h)
} else { None }
}MITRE ATT&CK mappings
Last verified: 2026-05-20