NtQueryValueKey
Lit une valeur d'une clé de registre — primitive de collecte ciblée de credentials et de configurations.
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 ouvert avec KEY_QUERY_VALUE (inclus dans KEY_READ). |
| ValueName | PUNICODE_STRING | in | Nom de la valeur à lire. Chaîne vide cible la valeur par défaut. |
| KeyValueInformationClass | KEY_VALUE_INFORMATION_CLASS | in | Disposition : KeyValueBasicInformation=0, KeyValueFullInformation=1, KeyValuePartialInformation=2, KeyValuePartialInformationAlign64=4. |
| KeyValueInformation | PVOID | out | Tampon de sortie recevant la structure KEY_VALUE_*_INFORMATION demandée. |
| Length | ULONG | in | Taille en octets de KeyValueInformation. |
| ResultLength | PULONG | out | Reçoit la taille requise ; permet le pattern classique requête-vide puis nouvelle requête. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | 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 |
Module noyau
APIs liées
Stub du syscall
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
Notes non documentées
Le SSN `0x17` est figé depuis Windows 7. Les deux classes d'information qui comptent vraiment côté offensif sont `KeyValuePartialInformation` (Type + DataLength + Data, sans nom) et `KeyValueFullInformation` (idem + nom) — elles couvrent ≈ 99% des chemins de credential scraping. L'idiome classique : appel avec `Length=0` pour connaître `ResultLength`, allouer, rappeler ; cette danse en deux temps est elle-même un signal de chasse quand elle vise un nom de valeur sensible.
Usage courant par les malwares
**Collecte ciblée de credentials et de configs (T1552.002, T1555)**. Les stealers n'énumèrent pas tout le registre — ils ciblent des noms de valeurs connus sous des clés connues. Cibles à haute valeur : (a) **sessions PuTTY sauvegardées** sous `\Registry\User\<SID>\Software\SimonTatham\PuTTY\Sessions\*` (HostName, UserName, PortNumber, PublicKeyFile, ProxyHost/User) — et là où pointe `PublicKeyFile`, la clé privée devient la cible NtReadFile suivante ; (b) **sessions WinSCP sauvegardées** sous `\...\Software\Martin Prikryl\WinSCP 2\Sessions\*` avec `Password` faiblement chiffré par hôte+user+nom ; (c) **credentials RDP en cache** dans `\...\Software\Microsoft\Terminal Server Client\Servers\*\UsernameHint` ; (d) **credentials mail Outlook** sous `\...\Software\Microsoft\Office\<ver>\Outlook\Profiles\*\<profileGUID>\...` ; (e) **valeurs de mots de passe VNC** (TightVNC, UltraVNC) ; (f) **clients mails stockés** (Thunderbird, eM Client) avec entrées de compte/serveur pointant vers des blobs de credentials sur disque. RedLine, LummaC2, Vidar, AgentTesla, Atomic, Raccoon utilisent tous cette primitive exacte dans leur boucle de harvest pilotée par config.
Opportunités de détection
L'ETW Microsoft-Windows-Kernel-Registry émet un évènement de query par appel — extrêmement bruyant, des centaines par seconde et par processus, le volume seul ne sert à rien. Le signal vit dans la **corrélation chemin/valeur** : tout processus autre que l'application propriétaire (mstsc.exe pour RDP, putty.exe pour PuTTY, outlook.exe pour Outlook) qui lit ces clés est un indicateur à forte fidélité. Sysmon `RegistryEvent` ne couvre pas directement les lectures, donc la télémétrie callback noyau EDR (`CmRegisterCallbackEx` → `RegNtPreQueryValueKey`/`RegNtPostQueryValueKey`) est la surface pratique. Le moteur comportemental anti-stealer de Defender (`Behavior:Win32/StealerCredAccess.*`) marque les processus qui querient plusieurs de ces chemins en rapide succession. Règle synthétique utile : alerter quand un même PID émet `NtQueryValueKey` contre ≥ 3 sous-arbres credentiels distincts dans une fenêtre de 5 s.
Exemples de syscalls directs
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 }
}Mappings MITRE ATT&CK
Last verified: 2026-05-20