> Windows Syscalls
ntoskrnl.exeT1497.003T1497T1106

NtQueryPerformanceCounter

Devuelve el valor actual del contador de rendimiento de alta resolución y opcionalmente su frecuencia.

Prototipo

NTSTATUS NtQueryPerformanceCounter(
  PLARGE_INTEGER PerformanceCounter,
  PLARGE_INTEGER PerformanceFrequency
);

Argumentos

NameTypeDirDescription
PerformanceCounterPLARGE_INTEGERoutRecibe el valor actual de 64 bits del contador de rendimiento.
PerformanceFrequencyPLARGE_INTEGERoutOpcional. Recibe la frecuencia de ticks del contador en Hz. Pase NULL si no se necesita.

IDs de syscalls por versión de Windows

Versión de WindowsID de syscallBuild
Win10 15070x31win10-1507
Win10 16070x31win10-1607
Win10 17030x31win10-1703
Win10 17090x31win10-1709
Win10 18030x31win10-1803
Win10 18090x31win10-1809
Win10 19030x31win10-1903
Win10 19090x31win10-1909
Win10 20040x31win10-2004
Win10 20H20x31win10-20h2
Win10 21H10x31win10-21h1
Win10 21H20x31win10-21h2
Win10 22H20x31win10-22h2
Win11 21H20x31win11-21h2
Win11 22H20x31win11-22h2
Win11 23H20x31win11-23h2
Win11 24H20x31win11-24h2
Server 20160x31winserver-2016
Server 20190x31winserver-2019
Server 20220x31winserver-2022
Server 20250x31winserver-2025

Módulo del kernel

ntoskrnl.exeNtQueryPerformanceCounter

APIs relacionadas

QueryPerformanceCounterQueryPerformanceFrequencyNtQuerySystemTimeGetTickCount64RtlQueryPerformanceCounter

Stub del syscall

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

Notablemente, el SSN de NtQueryPerformanceCounter ha sido **0x31 desde Windows 10 1507 y sigue siendo 0x31 en Windows 11 24H2 / Server 2025** — uno de los muy pocos syscalls que Microsoft ha mantenido perfectamente estable durante nueve años de builds. Su wrapper Win32 `QueryPerformanceCounter` casi siempre se satisface enteramente desde KUSER_SHARED_DATA (el bloque `QpcData` en `0x7FFE0000`) sin entrar nunca al kernel; el syscall solo se invoca como fallback cuando el fast-path user-mode detecta un cambio de fuente de reloj, una actualización de enlightenment de hipervisor, o cuando el llamante está en un proceso cuyo mapping KUSER_SHARED_DATA ha sido perturbado intencionalmente (raro). En hardware moderno la fuente del contador es TSC invariante, escalado a una frecuencia nominal de 10 MHz.

Uso común por malware

Este es el **caballo de batalla de la evasión de sandbox**. El patrón es universal: leer QPC, ejecutar un bucle apretado o una secuencia de coste conocido (p. ej. una serie de instrucciones CPUID, un hash CryptoAPI con iteraciones fijas, o un busy-loop calibrado), releer QPC, dividir por la frecuencia. Las sandboxes que aceleran el tiempo, single-stepean o instrumentan el hilo en ejecución muestran lecturas de wall-clock o demasiado rápidas (time-jumped) o demasiado lentas (instrumentadas). VMProtect y Themida incorporan escaleras QPC elaboradas en sus stubs; GuLoader, droppers AsyncRAT, IcedID y docenas de loaders de consumo usan chequeos de delta QPC de dos muestras más simples antes de desempaquetar. La ventaja sobre `rdtsc` es que QPC funciona dentro de invitados Hyper-V / VMware / KVM donde el hipervisor virtualiza el TSC — QPC lee el reloj *expuesto* por el hipervisor, que todavía muestra anomalías cuando el tiempo del invitado se desacopla del wall-clock del host con fines de análisis. NtDelayExecution + QPC es la detección clásica de sleep-skip (pedir 60 segundos, medir el QPC transcurrido real, abortar si la sandbox vuelve en milisegundos).

Oportunidades de detección

QPC es *demasiado* común para alertar directamente — cada hilo de UI, cada juego, cada pestaña de navegador lo llama constantemente. La detección debe ser conductual: busque un patrón apretado `QPC → cálculo barato → QPC → bifurcación condicional sobre delta`, especialmente cuando la bifurcación condicional es la diferencia entre ejecutar la siguiente etapa y abortar limpiamente. ETW Microsoft-Windows-Threat-Intelligence no expone esto. La detección por escaneo de memoria de stubs VMProtect/Themida atrapa el *resultado* en vez del uso de QPC en sí. El mejor marcador conductual es la firma de *delay-skip*: NtDelayExecution(60000ms) inmediatamente seguido de NtQueryPerformanceCounter, con salida anticipada si el tiempo transcurrido < umbral. Las sandboxes que arreglan esto honrando los delays son atrapadas entonces por el patrón de bucle apretado en su lugar.

Ejemplos de syscalls directos

cSleep-skip sandbox detection

// Request a long sleep; if the sandbox skips it, QPC reveals the lie.
#include <windows.h>

BOOL looks_like_sandbox(void) {
    LARGE_INTEGER freq, before, after;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&before);

    Sleep(60000);   // request 60 seconds

    QueryPerformanceCounter(&after);
    double elapsed_ms = (double)(after.QuadPart - before.QuadPart) * 1000.0 / (double)freq.QuadPart;

    // Real wall-clock 60s ± a few ms. Sandboxes that skip Sleep return in < 5s.
    return elapsed_ms < 50000.0;
}

cTight-loop instrumentation detector

// CPUID has fixed retired-instruction cost; a single-step or trace-driven
// sandbox blows past the expected wall-clock by orders of magnitude.
#include <windows.h>
#include <intrin.h>

BOOL stepped_under_debugger(void) {
    LARGE_INTEGER freq, t0, t1;
    QueryPerformanceFrequency(&freq);
    int regs[4];
    QueryPerformanceCounter(&t0);
    for (int i = 0; i < 100000; ++i) __cpuid(regs, 0);
    QueryPerformanceCounter(&t1);
    double us = (double)(t1.QuadPart - t0.QuadPart) * 1e6 / (double)freq.QuadPart;
    // Native: ~5-50 ms. Hypervisor-traced or single-stepped: seconds.
    return us > 1000000.0; // > 1s for 100k cpuid is wildly anomalous
}

asmx64 direct stub (SSN 0x31, all builds)

; NtQueryPerformanceCounter has used SSN 0x31 from Win10 1507 through Win11 24H2.
; The Win32 wrapper QueryPerformanceCounter usually services the request from
; KUSER_SHARED_DATA without invoking this syscall at all.
NtQueryPerformanceCounter PROC
    mov  r10, rcx
    mov  eax, 31h
    syscall
    ret
NtQueryPerformanceCounter ENDP

Mapeos MITRE ATT&CK

Last verified: 2026-05-20