Skip to content
Anti-analyseintermediate

QueryPerformanceCounter Timing

Le malware lit QueryPerformanceCounter deux fois autour d'une région de code et compare les ticks écoulés à un seuil ; le pas-à-pas ou les points d'arrêt gonflent le delta et révèlent un débogueur.

QueryPerformanceCounter (QPC) renvoie un compteur monotone de haute résolution. Comme le chronométrage basé sur RDTSC, le malware le lit avant et après un court bloc de code et mesure le delta. En exécution normale, le bloc prend quelques microsecondes ; lorsqu'un analyste l'exécute en pas-à-pas, atteint un point d'arrêt, ou que le débogueur traite des exceptions, le delta d'horloge gonfle de plusieurs ordres de grandeur — exposant le débogueur sans jamais demander au système d'exploitation s'il y en a un attaché.

QPC est attractif car c'est un minuteur documenté et stable qui masque les particularités CPU/TSC, donc le seuil est facile à régler et cohérent d'une machine à l'autre.

Fonctionnement

Le code capture deux échantillons du compteur et compare la différence à un seuil codé en dur.

c
#include <windows.h>

BOOL DebuggerByTiming(void)
{
    LARGE_INTEGER t1, t2;
    QueryPerformanceCounter(&t1);

    // ... short protected work (a few instructions) ...

    QueryPerformanceCounter(&t2);
    // Normal run: a few hundred ticks. Stepping: many thousands.
    return (t2.QuadPart - t1.QuadPart) > 0x10000;
}

Une version plus robuste lit aussi QueryPerformanceFrequency pour convertir les ticks en millisecondes, ou répète la mesure plusieurs fois et prend le minimum afin de réduire le bruit.

Détection & contournement

  • Statique — Recherchez dans IDA/Ghidra les imports de QueryPerformanceCounter (et QueryPerformanceFrequency). Cherchez deux appels encadrant une région, une soustraction de deux LARGE_INTEGER, et un cmp contre une constante de seuil suivi d'un branchement conditionnel vers un chemin « abandon/sortie ».
  • Dynamique — Dans x64dbg, posez un point d'arrêt sur kernel32!QueryPerformanceCounter/kernelbase!QueryPerformanceCounter ; notez les deux sites d'appel. Inspectez les deux valeurs renvoyées et la comparaison qui suit pour voir le seuil. Évitez le pas-à-pas dans la région chronométrée — exécutez plutôt jusqu'à un point d'arrêt placé après le second appel QPC.
  • Patch / contournement — Hookez QPC pour renvoyer de petites valeurs croissant de façon monotone afin que les deltas restent minuscules et constants. Vous pouvez aussi patcher la comparaison : NOP le saut conditionnel ou forcez la fonction de chronométrage à return 0. Vous pouvez également réécrire la constante de seuil avec une valeur énorme.
  • Outils — Les options d'évasion de chronométrage de ScyllaHide normalisent les deltas QPC/RDTSC/GetTickCount ; Interceptor Frida sur QueryPerformanceCounter pour borner la sortie ; x64dbg avec « run to » au lieu du pas-à-pas sur les blocs chronométrés.
Votes

Commentaires(0)