> Windows Syscalls
ntoskrnl.exeT1497.003T1497T1106

NtQueryPerformanceCounter

Retourne la valeur actuelle du compteur de performance haute résolution et optionnellement sa fréquence.

Prototype

NTSTATUS NtQueryPerformanceCounter(
  PLARGE_INTEGER PerformanceCounter,
  PLARGE_INTEGER PerformanceFrequency
);

Arguments

NameTypeDirDescription
PerformanceCounterPLARGE_INTEGERoutReçoit la valeur actuelle 64 bits du compteur de performance.
PerformanceFrequencyPLARGE_INTEGERoutOptionnel. Reçoit la fréquence de ticks du compteur en Hz. Passez NULL si non nécessaire.

IDs de syscalls par version de Windows

Version 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

Module noyau

ntoskrnl.exeNtQueryPerformanceCounter

APIs liées

QueryPerformanceCounterQueryPerformanceFrequencyNtQuerySystemTimeGetTickCount64RtlQueryPerformanceCounter

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

Notes non documentées

Notable, le SSN de NtQueryPerformanceCounter est **0x31 depuis Windows 10 1507 et reste 0x31 sur Windows 11 24H2 / Server 2025** — l'un des très rares syscalls que Microsoft a maintenu parfaitement stable sur neuf ans de builds. Son wrapper Win32 `QueryPerformanceCounter` est presque toujours satisfait entièrement par KUSER_SHARED_DATA (le bloc `QpcData` à `0x7FFE0000`) sans jamais entrer dans le noyau ; le syscall n'est invoqué qu'en fallback quand le fast-path user-mode détecte un changement de source d'horloge, une mise à jour d'enlightenment hyperviseur, ou quand l'appelant est dans un processus dont le mapping KUSER_SHARED_DATA a été intentionnellement perturbé (rare). Sur matériel moderne, la source du compteur est le TSC invariant, mis à l'échelle à une fréquence nominale de 10 MHz.

Usage courant par les malwares

C'est le **cheval de bataille de l'évasion de sandbox**. Le pattern est universel : lire QPC, exécuter une boucle serrée ou une séquence à coût connu (par ex. une série d'instructions CPUID, un hash CryptoAPI à itérations fixes, ou une busy-loop calibrée), relire QPC, diviser par la fréquence. Les sandboxes qui accélèrent le temps, single-steppent ou instrumentent le thread exécutant montrent des lectures wall-clock soit beaucoup trop rapides (time-jumped) soit beaucoup trop lentes (instrumentées). VMProtect et Themida intègrent des échelles QPC élaborées dans leurs stubs ; GuLoader, les droppers AsyncRAT, IcedID et des dizaines de loaders du commerce utilisent des checks de delta QPC à deux échantillons plus simples avant le dépaquetage. L'avantage sur `rdtsc` est que QPC fonctionne dans les invités Hyper-V / VMware / KVM où l'hyperviseur virtualise le TSC — QPC lit l'horloge *exposée* par l'hyperviseur, qui montre encore des anomalies quand le temps invité est découplé du wall-clock hôte à des fins d'analyse. NtDelayExecution + QPC est la détection classique de sleep-skip (demander 60 secondes, mesurer le QPC écoulé réel, abandonner si la sandbox revient en millisecondes).

Opportunités de détection

QPC est *trop* courant pour alerter directement — chaque thread UI, chaque jeu, chaque onglet de navigateur l'appelle constamment. La détection doit être comportementale : cherchez un pattern serré `QPC → calcul léger → QPC → branchement conditionnel sur delta`, surtout quand le branchement conditionnel est la différence entre exécuter l'étape suivante et abandonner proprement. L'ETW Microsoft-Windows-Threat-Intelligence n'expose pas cela. La détection par scan mémoire des stubs VMProtect/Themida attrape le *résultat* plutôt que l'usage QPC lui-même. Le meilleur marqueur comportemental est la signature de *delay-skip* : NtDelayExecution(60000ms) immédiatement suivi de NtQueryPerformanceCounter, avec sortie anticipée si le temps écoulé < seuil. Les sandboxes qui corrigent cela en honorant les delays sont alors attrapées par le pattern de boucle serrée à la place.

Exemples de syscalls directs

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

Mappings MITRE ATT&CK

Last verified: 2026-05-20