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
| Name | Type | Dir | Description |
|---|---|---|---|
| KeyHandle | HANDLE | in | Handle opened with KEY_QUERY_VALUE (subset of KEY_READ). |
| ValueName | PUNICODE_STRING | in | Name of the value to read. Empty string targets the default value. |
| KeyValueInformationClass | KEY_VALUE_INFORMATION_CLASS | in | Layout: KeyValueBasicInformation=0, KeyValueFullInformation=1, KeyValuePartialInformation=2, KeyValuePartialInformationAlign64=4. |
| KeyValueInformation | PVOID | out | Output buffer that receives the requested KEY_VALUE_*_INFORMATION struct. |
| Length | ULONG | in | Size of KeyValueInformation in bytes. |
| ResultLength | PULONG | out | Receives the required size; allows the classic query-with-zero then re-query pattern. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x17 | win10-1507 |
| Win10 1607 | 0x17 | win10-1607 |
| Win10 1703 | 0x17 | win10-1703 |
| Win10 1709 | 0x17 | win10-1709 |
| Win10 1803 | 0x17 | win10-1803 |
| Win10 1809 | 0x17 | win10-1809 |
| Win10 1903 | 0x17 | win10-1903 |
| Win10 1909 | 0x17 | win10-1909 |
| Win10 2004 | 0x17 | win10-2004 |
| Win10 20H2 | 0x17 | win10-20h2 |
| Win10 21H1 | 0x17 | win10-21h1 |
| Win10 21H2 | 0x17 | win10-21h2 |
| Win10 22H2 | 0x17 | win10-22h2 |
| Win11 21H2 | 0x17 | win11-21h2 |
| Win11 22H2 | 0x17 | win11-22h2 |
| Win11 23H2 | 0x17 | win11-23h2 |
| Win11 24H2 | 0x17 | win11-24h2 |
| Server 2016 | 0x17 | winserver-2016 |
| Server 2019 | 0x17 | winserver-2019 |
| Server 2022 | 0x17 | winserver-2022 |
| Server 2025 | 0x17 | winserver-2025 |
Kernel module
Related APIs
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 ENDPrustTwo-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