> Windows Syscalls
ntoskrnl.exeT1552.002T1555T1012

NtQueryValueKey

Lee un valor de una clave del registro — primitiva de cosecha dirigida de credenciales y configuraciones.

Prototipo

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

Argumentos

NameTypeDirDescription
KeyHandleHANDLEinHandle abierto con KEY_QUERY_VALUE (parte de KEY_READ).
ValueNamePUNICODE_STRINGinNombre del valor a leer. Cadena vacía apunta al valor por defecto.
KeyValueInformationClassKEY_VALUE_INFORMATION_CLASSinFormato: KeyValueBasicInformation=0, KeyValueFullInformation=1, KeyValuePartialInformation=2, KeyValuePartialInformationAlign64=4.
KeyValueInformationPVOIDoutBúfer de salida que recibe la estructura KEY_VALUE_*_INFORMATION solicitada.
LengthULONGinTamaño de KeyValueInformation en bytes.
ResultLengthPULONGoutRecibe el tamaño requerido; permite el patrón clásico consulta-vacía y luego nueva consulta.

IDs de syscalls por versión de Windows

Versión 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

Módulo del kernel

ntoskrnl.exeNtQueryValueKey

APIs relacionadas

RegQueryValueExWRegGetValueWNtEnumerateValueKeyNtQueryMultipleValueKeyNtOpenKeyNtClose

Stub del 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

Notas no documentadas

El SSN `0x17` está congelado desde Windows 7. Las dos clases de información que importan ofensivamente son `KeyValuePartialInformation` (Type + DataLength + Data, sin nombre) y `KeyValueFullInformation` (lo mismo + nombre) — cubren ≈ 99% de las rutas de credential scraping. Idioma clásico: llamar con `Length=0` para conocer `ResultLength`, asignar, llamar de nuevo; ese baile en dos pasos es por sí mismo una señal de caza cuando apunta a un nombre de valor sensible.

Uso común por malware

**Cosecha dirigida de credenciales y configs (T1552.002, T1555)**. Los stealers no enumeran todo el registro — atacan nombres conocidos bajo claves conocidas. Objetivos de alto valor: (a) **sesiones PuTTY guardadas** en `\Registry\User\<SID>\Software\SimonTatham\PuTTY\Sessions\*` (HostName, UserName, PortNumber, PublicKeyFile, ProxyHost/User) — y donde apunte `PublicKeyFile`, la clave privada se vuelve el siguiente objetivo de NtReadFile; (b) **sesiones WinSCP guardadas** en `\...\Software\Martin Prikryl\WinSCP 2\Sessions\*` con `Password` débilmente cifrado por host+user+nombre; (c) **credenciales RDP en caché** en `\...\Software\Microsoft\Terminal Server Client\Servers\*\UsernameHint`; (d) **credenciales de correo Outlook** bajo `\...\Software\Microsoft\Office\<ver>\Outlook\Profiles\*\<profileGUID>\...`; (e) **valores de password VNC** (TightVNC, UltraVNC); (f) **clientes de correo almacenados** (Thunderbird, eM Client) con entradas cuenta/servidor que apuntan a blobs de credenciales en disco. RedLine, LummaC2, Vidar, AgentTesla, Atomic, Raccoon usan exactamente esta primitiva en sus bucles de harvest guiados por config.

Oportunidades de detección

ETW Microsoft-Windows-Kernel-Registry emite evento de query por llamada — extremadamente ruidoso, cientos por segundo por proceso, así que el volumen por sí mismo es inútil. La señal vive en la **correlación ruta/valor**: cualquier proceso distinto de la app propietaria (mstsc.exe para RDP, putty.exe para PuTTY, outlook.exe para Outlook) leyendo esas claves es un indicador de alta fidelidad. Sysmon `RegistryEvent` no cubre lecturas, por lo que la telemetría de callbacks de kernel EDR (`CmRegisterCallbackEx` → `RegNtPreQueryValueKey`/`RegNtPostQueryValueKey`) es la superficie práctica. El motor de comportamiento anti-stealer de Defender (`Behavior:Win32/StealerCredAccess.*`) marca procesos que consultan varias de estas rutas en rápida sucesión. Regla sintética útil: alertar cuando un mismo PID emite `NtQueryValueKey` contra ≥ 3 subárboles de credenciales distintos en ventana de 5 s.

Ejemplos de syscalls directos

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

Mapeos MITRE ATT&CK

Last verified: 2026-05-20