Retour à tous les articles

« Le programme a-t-il tourné ? » est la question la plus simple dans une enquête Windows et celle à laquelle les analystes répondent le plus souvent mal. La tentation est de trouver un fichier .pf portant le bon nom et de s'arrêter là. C'est correct dans la majorité des cas. C'est dangereux dans la minorité, et c'est précisément dans cette minorité que vivent les enquêtes contestées.

Ce billet traite des limites de l'affirmation « preuve d'exécution » de Prefetch. Ce qu'elle dit vraiment. Ce qu'elle ne dit pas. Et comment la durcir avec les artefacts qui survivent à côté.

L'affirmation, formulée précisément

Quand le service Prefetch observe le démarrage d'un processus et passe les dix premières secondes de son exécution, il écrit (ou met à jour) un fichier .pf. Le nom embarque le nom du binaire (en majuscules, tronqué à 29 caractères) et un hash dérivé du chemin complet de l'exécutable.

L'affirmation qu'un .pf étaye, formulée avec soin, est donc :

Un processus a été démarré sur cet hôte dont l'exécutable principal portait le nom EXAMPLE.EXE et dont le chemin résolu produisait le hash 0x1234ABCD, aux heures enregistrées dans le tableau d'horodatages d'exécution du fichier, et a été observé par le service Prefetch suffisamment longtemps pour être enregistré.

Tout ce qui sort de cette phrase est de l'inférence, pas une preuve directe.

C'est tout de même une affirmation plus forte que celle de Shimcache (« Windows a regardé les métadonnées de ce binaire à un moment ») ou d'AmCache (« Windows a énuméré ce binaire à des fins de compatibilité, ce qui implique généralement, mais pas toujours, une exécution »). Prefetch exige que le processus démarre vraiment. C'est difficile à truquer par accident.

Comment fonctionne le hash de chemin, et où il ment

Le hash dans le nom n'est pas un hash de contenu. C'est une fonction du chemin complet de l'exécutable (avec quelques champs de contexte supplémentaires sur les builds Windows récents, comme les arguments de ligne de commande et les identifiants de package pour les apps UWP). Deux binaires de même nom dans deux répertoires différents produisent deux .pf différents. C'est cette propriété qui permet de distinguer C:\Program Files\PsExec.exe de C:\Users\victim\AppData\Local\Temp\PsExec.exe d'un coup d'œil.

Mais le chemin est la seule entrée, pas le binaire. Si C:\Users\victim\AppData\Local\Temp\PsExec.exe a tourné aujourd'hui, et qu'hier un fichier totalement différent vivait à ce chemin (également nommé PsExec.exe), ils partagent un .pf. Les horodatages d'exécution s'empilent. Le compteur d'exécutions s'incrémente. La liste de chargement est écrasée par les données du run le plus récent.

C'est le mode d'échec que j'ai vu le plus souvent. Un attaquant dépose un outil, le lance, le supprime, et plus tard un fichier bénin finit au même chemin. L'enregistrement Prefetch d'origine est toujours là, mais le chemin sur le disque pointe maintenant vers quelque chose d'entièrement différent. Un analyste qui ne regarde que le chemin et le fichier actuellement sur le disque conclut que le fichier bénin a tourné. Le fichier bénin n'a pas tourné. Quelque chose d'autre au même chemin a tourné.

La parade est de ne jamais se fier à la seule résolution de chemin. Couplez le .pf à l'entrée MFT pour ce chemin au moment de l'exécution, récupérez le numéro de référence de fichier, et vérifiez si l'ID de fichier a changé depuis.

Collisions de noms et le problème des 29 caractères

Le champ de nom d'exécutable interne fait 29 caractères plus terminateur nul, UTF-16LE. Les noms plus longs sont tronqués.

Si vous avez déjà vu deux .pf portant des noms comme :

LONGNAMEDEXECUTABLE_VERSION_A-12345678.pf
LONGNAMEDEXECUTABLE_VERSION_B-87654321.pf

et que les deux champs de nom interne lisent LONGNAMEDEXECUTABLE_VERSION_, vous avez une collision sur le nom mais des hashes distincts. Ce sont les hashes qui désambiguïsent.

À l'inverse, l'espace de hash est large mais pas infini, et le hash est une valeur 32 bits. Les collisions de hashes sur des chemins distincts arrivent. Elles sont rares dans la nature mais pas inouïes, et sur un hôte chargé avec des milliers de binaires, vous ne devriez pas être surpris de voir deux .pf avec les mêmes octets de hash si les noms diffèrent. La combinaison nom-hash est la clé unique, pas l'un ou l'autre seul.

Le cas limite « exécutable remplacé après création du .pf »

Voici la version de ce cas qui s'est présentée dans mes missions.

Jour 0, un attaquant copie m1m1.exe (Mimikatz renommé) dans C:\Users\Public\m1m1.exe. Le lance. Windows crée M1M1.EXE-AABBCCDD.pf. La liste de chargement montre \Windows\System32\sekurlsa.dll et d'autres artefacts qui crient Mimikatz.

Jour 3, l'attaquant supprime le binaire.

Jour 7, un utilisateur interne, totalement étranger, écrit un petit utilitaire, le nomme m1m1.exe, le dépose dans C:\Users\Public\, l'exécute. Windows met à jour le .pf existant (même chemin, même hash). La liste de chargement reflète désormais l'utilitaire bénin. Le compteur d'exécutions vaut 2. L'horodatage le plus récent est jour 7.

Une lecture rapide dit « le binaire a tourné deux fois, le dernier run paraît bénin ». La lecture correcte demande des artefacts supplémentaires. La MFT montrera que le fichier à C:\Users\Public\m1m1.exe a été créé jour 0, supprimé jour 3, recréé jour 7 avec une référence de fichier différente. Le journal USN a les deux cycles de vie. AmCache peut avoir le hash du binaire jour 0 même après suppression du fichier. Sysmon Event ID 1 aura la ligne de commande et le SHA-256 des deux exécutions s'il était actif.

Prefetch seul dit « a tourné deux fois ». Prefetch plus les artefacts environnants disent « deux binaires différents au même chemin ont tourné, voici les hashes, voici quel utilisateur, voici ce qu'ils ont fait ».

Si vous vous arrêtez à Prefetch seul dans un tel cas, vous aurez tort de la pire manière qui soit : avec assurance.

Couplez avec EVTX 4688 et Sysmon EID 1

Les deux événements que vous voulez à côté de toute affirmation d'exécution Prefetch sont Security 4688 (création de processus, si activé) et Sysmon Event ID 1 (création de processus, avec ligne de commande, parent, hashes, niveau d'intégrité). Ils sont dans l'EVTX.

Croisez sur :

  1. Chemin d'exécutable. Le champ NewProcessName ou Image du 4688/EID 1 doit correspondre au chemin résolu enregistré par Prefetch.
  2. Heure d'exécution. L'heure Last run de Prefetch et l'heure de l'événement doivent concorder à quelques secondes près. Des écarts plus grands ne sont pas forcément faux mais méritent investigation ; la cause la plus fréquente est une dérive d'horloge entre sources de log.
  3. Niveau d'intégrité du processus et contexte utilisateur. Prefetch est par machine et ne dit rien sur qui a lancé le binaire. Sysmon et 4688 donnent le SID. Si le .pf d'un binaire montre dix exécutions et que Sysmon n'en journalise que cinq, il vous manque des logs, ou les cinq autres se sont produites pendant une fenêtre sans Sysmon, ou quelqu'un a manipulé le journal.

La paire est bien plus forte que l'un sans l'autre. Prefetch donne un inventaire complet de chaque binaire qui a tourné (sous réserve des précautions SSD/serveur traitées ailleurs) ; EVTX donne le détail par exécution. Utilisez-les ensemble et la question « cela a-t-il tourné » cesse d'être ambiguë.

Quand vous n'avez ni l'un ni l'autre, la RAM vous donne une liste de processus vivante si l'hôte n'a pas redémarré, et le pagefile contient parfois des artefacts de processus. Après un reboot sans EVTX ni Prefetch, vous êtes surtout réduit à AmCache et à l'inférence.

Distinguer « a tourné une fois » de « a tourné dans le bruit »

Le compteur d'exécutions Prefetch ne ment pas sur la borne inférieure. S'il dit 12 exécutions, le binaire a tourné au moins 12 fois. S'il a tourné plus (parce que certaines exécutions sont sorties de l'anneau de huit ou parce que Win10 1709+ a élagué des anciennes entrées), c'est ouvert.

Ce à quoi sert le compteur dans ce contexte, c'est au contraste. Sur un hôte avec un .pf de psexec.exe montrant 200 exécutions et un chemin dans Program Files, vous regardez l'outil quotidien d'un sysadmin. Sur le même hôte avec un .pf séparé de psexec.exe montrant 1 exécution et un chemin sous \AppData\Local\Temp\, vous regardez quelque chose qui est arrivé récemment et a tourné une fois. C'est le second .pf qu'il faut investiguer.

À contre-courant, les fichiers Prefetch à exécution unique sont en général les plus intéressants dans une intrusion. Les fichiers à nombreuses exécutions sont en général du bruit de fond sauf s'ils vivent à des emplacements suspects.

Un workflow qui tient

Quand la question est « est-ce que cela a tourné sur cet hôte », je déroule cette séquence :

  1. Identifier le .pf par nom et hash.
  2. Lire ses horodatages d'exécution et son compteur. Noter le chemin d'exécutable enregistré.
  3. Sortir l'entrée MFT pour ce chemin et vérifier que la référence de fichier correspond à ce que Prefetch a vu.
  4. Recouper avec EVTX 4688 et Sysmon EID 1 sur les heures d'exécution.
  5. Vérifier AmCache pour le hash du fichier. S'il correspond à un échantillon connu, le dossier se simplifie.
  6. Si le binaire n'est plus sur le disque, vérifier le journal USN pour les événements de suppression autour des heures d'exécution.

Si les étapes 3 à 6 s'accordent, l'affirmation d'exécution est essentiellement inattaquable. Si elles s'opposent, vous avez quelque chose de plus intéressant qu'une simple preuve d'exécution ; vous avez des preuves de manipulation, de remplacement, ou d'une séquence plus complexe que ce que l'entrée Prefetch seule suggère.

Pour aller plus loin

Prefetch répond à « le binaire a tourné ». Sysmon répond à « voilà ce qu'il a fait ». Traitez-les comme complémentaires et vous ne serez pas pris au dépourvu.