> Windows Syscalls
ntoskrnl.exeT1552.002T1555T1012

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

NameTypeDirDescription
KeyHandleHANDLEinHandle ouvert avec KEY_QUERY_VALUE (inclus dans KEY_READ).
ValueNamePUNICODE_STRINGinNom de la valeur à lire. Chaîne vide cible la valeur par défaut.
KeyValueInformationClassKEY_VALUE_INFORMATION_CLASSinDisposition : KeyValueBasicInformation=0, KeyValueFullInformation=1, KeyValuePartialInformation=2, KeyValuePartialInformationAlign64=4.
KeyValueInformationPVOIDoutTampon de sortie recevant la structure KEY_VALUE_*_INFORMATION demandée.
LengthULONGinTaille en octets de KeyValueInformation.
ResultLengthPULONGoutReçoit la taille requise ; permet le pattern classique requête-vide puis nouvelle requête.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
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

Module noyau

ntoskrnl.exeNtQueryValueKey

APIs liées

RegQueryValueExWRegGetValueWNtEnumerateValueKeyNtQueryMultipleValueKeyNtOpenKeyNtClose

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 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 }
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20