> Windows Syscalls
ntoskrnl.exeT1552.002T1555T1012

NtQueryValueKey

Reads a value from a registry key — the targeted credential and config harvest primitive.

Prototype

NTSTATUS NtQueryValueKey(
  HANDLE                      KeyHandle,
  PUNICODE_STRING             ValueName,
  KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
  PVOID                       KeyValueInformation,
  ULONG                       Length,
  PULONG                      ResultLength
);

Arguments

NameTypeDirDescription
KeyHandleHANDLEinHandle opened with KEY_QUERY_VALUE (subset of KEY_READ).
ValueNamePUNICODE_STRINGinName of the value to read. Empty string targets the default value.
KeyValueInformationClassKEY_VALUE_INFORMATION_CLASSinLayout: KeyValueBasicInformation=0, KeyValueFullInformation=1, KeyValuePartialInformation=2, KeyValuePartialInformationAlign64=4.
KeyValueInformationPVOIDoutOutput buffer that receives the requested KEY_VALUE_*_INFORMATION struct.
LengthULONGinSize of KeyValueInformation in bytes.
ResultLengthPULONGoutReceives the required size; allows the classic query-with-zero then re-query pattern.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x17win10-1507
Win10 16070x17win10-1607
Win10 17030x17win10-1703
Win10 17090x17win10-1709
Win10 18030x17win10-1803
Win10 18090x17win10-1809
Win10 19030x17win10-1903
Win10 19090x17win10-1909
Win10 20040x17win10-2004
Win10 20H20x17win10-20h2
Win10 21H10x17win10-21h1
Win10 21H20x17win10-21h2
Win10 22H20x17win10-22h2
Win11 21H20x17win11-21h2
Win11 22H20x17win11-22h2
Win11 23H20x17win11-23h2
Win11 24H20x17win11-24h2
Server 20160x17winserver-2016
Server 20190x17winserver-2019
Server 20220x17winserver-2022
Server 20250x17winserver-2025

Kernel module

ntoskrnl.exeNtQueryValueKey

Related APIs

RegQueryValueExWRegGetValueWNtEnumerateValueKeyNtQueryMultipleValueKeyNtOpenKeyNtClose

Syscall stub

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

SSN `0x17` has been frozen since Windows 7. The two information classes that actually matter to offensive tooling are `KeyValuePartialInformation` (returns Type + DataLength + raw Data, no name) and `KeyValueFullInformation` (returns name plus all the above) — they cover ~99% of credential-scraping paths. The classic caller idiom is to issue with `Length=0` to learn `ResultLength`, allocate, then re-issue; this two-call dance is itself a hunt signal when paired with a sensitive value name.

Common malware usage

**Targeted credential / config harvesting (T1552.002, T1555)**. Stealers do not enumerate the entire registry — they query specific known-good value names under known-good keys. High-value targets: (a) **Putty saved sessions** at `\Registry\User\<SID>\Software\SimonTatham\PuTTY\Sessions\*` (HostName, UserName, PortNumber, PublicKeyFile, ProxyHost/User) — and where `PublicKeyFile` points, the private key file becomes the next NtReadFile target; (b) **WinSCP saved sessions** at `\...\Software\Martin Prikryl\WinSCP 2\Sessions\*` with `Password` weakly-encrypted by host+user+session-name; (c) **RDP cached credentials** in `\...\Software\Microsoft\Terminal Server Client\Servers\*\UsernameHint`; (d) **Outlook profile mail credentials** under `\...\Software\Microsoft\Office\<ver>\Outlook\Profiles\*\<profileGUID>\...`; (e) **VNC** (TightVNC, UltraVNC) password values; (f) **Stored mail clients** (Thunderbird, eM Client) account/server entries pointing to disk-resident credential blobs. RedLine, LummaC2, Vidar, AgentTesla, Atomic, Raccoon all rely on this exact primitive in their config-driven harvesting loops.

Detection opportunities

Microsoft-Windows-Kernel-Registry ETW emits a query event per call — extremely chatty, hundreds per second per process, so volume alone is useless. The signal lives in the **value-name path correlation**: any process other than the owning application (mstsc.exe for RDP, putty.exe for PuTTY, outlook.exe for Outlook profiles) reading from those specific keys is a high-fidelity indicator. Sysmon's `RegistryEvent` does not directly cover reads, so EDR kernel-callback telemetry (`CmRegisterCallbackEx` → `RegNtPreQueryValueKey` / `RegNtPostQueryValueKey`) is the practical detection surface. Defender's stealer-aware behavior monitor (`Behavior:Win32/StealerCredAccess.*`) flags processes that query many of these paths in rapid sequence. A useful synthetic rule: alert when the same PID issues `NtQueryValueKey` against ≥ 3 distinct credential-bearing subtrees within a 5-second window.

Direct syscall examples

cRead PuTTY HostName for a saved session

// hSess = handle to \Registry\User\<SID>\Software\SimonTatham\PuTTY\Sessions\<name>
UNICODE_STRING vn;
RtlInitUnicodeString(&vn, L"HostName");

BYTE  buf[512];
ULONG got = 0;
NTSTATUS s = NtQueryValueKey(
    hSess, &vn,
    KeyValuePartialInformation,
    buf, sizeof(buf), &got);

if (NT_SUCCESS(s)) {
    PKEY_VALUE_PARTIAL_INFORMATION pv = (PKEY_VALUE_PARTIAL_INFORMATION)buf;
    // pv->Type == REG_SZ
    // pv->Data == L"prod-jumpbox.contoso.com"
}

asmDirect stub (SSN 0x17)

NtQueryValueKey PROC
    mov  r10, rcx
    mov  eax, 17h
    syscall
    ret
NtQueryValueKey ENDP

rustTwo-call resize idiom

use ntapi::ntregapi::{NtQueryValueKey, KEY_VALUE_PARTIAL_INFORMATION};
use ntapi::ntrtl::RtlInitUnicodeString;
use winapi::shared::ntdef::{HANDLE, UNICODE_STRING};

pub unsafe fn read_value(h: HANDLE, name: *const u16) -> Option<Vec<u8>> {
    let mut vn: UNICODE_STRING = core::mem::zeroed();
    RtlInitUnicodeString(&mut vn, name);
    let mut need = 0u32;
    // probe length
    let _ = NtQueryValueKey(h, &mut vn, 2, core::ptr::null_mut(), 0, &mut need);
    let mut buf = vec![0u8; need as usize];
    let s = NtQueryValueKey(
        h, &mut vn, 2,
        buf.as_mut_ptr() as *mut _, buf.len() as u32, &mut need);
    if s == 0 {
        let pv = &*(buf.as_ptr() as *const KEY_VALUE_PARTIAL_INFORMATION);
        let n = pv.DataLength as usize;
        Some(buf[std::mem::offset_of!(KEY_VALUE_PARTIAL_INFORMATION, Data)..
               std::mem::offset_of!(KEY_VALUE_PARTIAL_INFORMATION, Data) + n].to_vec())
    } else { None }
}

MITRE ATT&CK mappings

Last verified: 2026-05-20