La plupart des gens qui travaillent avec Prefetch ne regardent jamais les octets. Ils lancent un parser, lisent un tableau et passent à autre chose. C'est en général très bien. Ce n'est pas très bien quand le parser se trompe, et la seule façon de savoir si votre parser se trompe est de comprendre ce qu'il est censé faire.
Le format Prefetch de Windows a changé cinq ou six fois en vingt ans. Chaque changement a cassé au moins un parser populaire à son époque. Si vous lisez un fichier .pf provenant d'un hôte Win11 avec un outil dont la dernière mise à jour date de 2019, vous devriez vous en soucier.
SCCA : le magic et la forme
Tout fichier Prefetch legacy commence par la chaîne ASCII SCCA à l'offset 0x04. Les quatre octets précédents sont le champ de version, little-endian. C'est la première chose que tout parser vérifie, et la version est la porte d'entrée vers tout le reste.
Les valeurs connues :
0x11(17 décimal) pour Windows XP et 20030x17(23) pour Vista et Windows 70x1A(26) pour Windows 8 et 8.10x1E(30) pour Windows 10 (ère non compressée et les premières builds MAM-compressées)0x1F(31) pour les builds Windows 10/11 ultérieures. À traiter comme « Win10+ récent » plutôt qu'attaché à une build précise.
Si les quatre premiers octets du fichier ne sont pas SCCA et ne sont pas la signature MAM (plus de détails dans un instant), vous n'avez pas un fichier Prefetch. Vous avez autre chose avec une extension .pf, et le cas que j'ai vu le plus est celui où quelqu'un a copié un fichier en SMB avec un outil qui a fait de la conversion d'encodage et l'a mutilé. Réacquérir.
Après la version et le magic, vient un petit bloc d'en-tête : taille totale du fichier, nom de l'exécutable encodé en UTF-16LE (jusqu'à 30 caractères, complété par des null) et un hash de chemin (également couvert séparément en détail). Le nom de l'exécutable dans l'en-tête devrait correspondre au préfixe du nom de fichier. Sinon, vous regardez un fichier renommé, une manipulation manuelle, ou plus rarement un binaire dont le champ de nom interne dépassait 29 caractères et a été tronqué.
Sections A, B, C, D, et le reste
À l'intérieur du fichier, Prefetch est organisé en sections dont le rôle est resté à peu près identique d'une version à l'autre, même si les offsets et les largeurs de champs ont changé :
- Section A est le tableau des métriques de fichiers. Une entrée par fichier touché par le binaire durant la fenêtre de traçage. Chaque entrée comporte une heure de début, une durée, un offset de chaîne de nom de fichier dans la Section C, et une référence de fichier vers la MFT (cette dernière uniquement sur les versions plus récentes).
- Section B est le tableau des chaînes de trace. Ce sont les données de séquencement des défauts de page qui intéressent réellement le système d'exploitation. D'un point de vue forensique, c'est surtout du bruit ; vous le parserez rarement explicitement.
- Section C contient les chaînes de noms de fichiers. Chemins UTF-16LE, les uns après les autres, référencés par offset depuis la Section A. Cela devient la « liste de chargement de fichiers » lorsqu'on imprime un rapport.
- Section D est le tableau d'informations de volumes. Une entrée par volume touché par le binaire. Chaque entrée comporte un chemin de volume (typiquement
\DEVICE\HARDDISKVOLUMEn), une heure de création, un numéro de série, et un index vers la table de chaînes de répertoires.
Après la Section D, on trouve généralement les chaînes de répertoires et, sur les versions plus récentes, des métadonnées supplémentaires qui varient selon la build. Les huit horodatages d'exécution vivent dans l'en-tête en Win8+, dans une disposition légèrement différente de Win7 (qui n'avait qu'un horodatage). Le compteur d'exécutions est placé à côté.
Les deux choses qui bougent entre versions sont les offsets vers chaque section (enregistrés dans l'en-tête) et la taille des entrées individuelles (l'entrée du tableau de métriques de fichiers est passée de 20 octets sous XP/7 à 32 octets sous Win10 pour accueillir la référence MFT). Un parser qui code en dur « la Section A commence à l'offset 0x98 » au lieu de lire l'offset dans l'en-tête échouera silencieusement à la prochaine sortie OS. Plusieurs traînent encore sur GitHub.
MAM : le wrapper de compression Win10
À partir d'environ Windows 10 1607, Microsoft a enveloppé les fichiers Prefetch dans une compression MAM. Le fichier ne commence plus par SCCA. Il commence par MAM\x04 (l'octet 0x04 est l'identifiant d'algorithme, Xpress Huffman). Les quatre octets suivants sont la taille décompressée, little-endian. Tout le reste est le flux compressé en Xpress Huffman contenant ce qui serait autrement le fichier SCCA brut.
Si vous décompressez correctement, vous retrouvez exactement la structure SCCA décrite ci-dessus, version 0x1E ou 0x1F. Le format sur le fil est le même ; seul le wrapper a changé.
L'API Windows pertinente est RtlDecompressBufferEx avec COMPRESSION_FORMAT_XPRESS_HUFF. PECmd d'Eric Zimmerman appelle l'API Windows sur les hôtes Windows et utilise une implémentation managée Xpress Huffman lorsqu'il tourne sous Linux. Plusieurs parsers Python embarquent un décodeur Xpress Huffman en Python pur, lent mais correct. Ceux qui ne gèrent pas MAM du tout afficheront « pas un fichier Prefetch » ou, pire, sortiront du charabia avec un code de sortie zéro.
Vérifiez toujours le support MAM de votre parser en lui fournissant un fichier Prefetch Win10 connu (n'importe quel fichier de C:\Windows\Prefetch\ sur un hôte Win10/11 actuel suffit) et en vérifiant que le compteur d'exécutions et les heures correspondent à ce que rapporte PECmd.
Ce qui a changé entre Win10 v30 et Win11 v31
Moins que ne le suggère l'incrément de version, sur le papier. La disposition interne est presque identique. Ce qui a changé en pratique :
- La façon dont le hash de chemin incorpore du contexte (arguments de ligne de commande, environnement « App-V », nom complet de paquet sur les apps UWP) a été étendue. La même EXE dans deux sandbox différentes produit deux
.pfdifférents, là où sur les anciennes builds elle pouvait entrer en collision. - Les temps de trace Prefetch sont devenus plus granulaires. En v31, on voit parfois une résolution sous-seconde que les versions antérieures arrondissaient.
- Le comportement d'expiration a changé. L'anneau de huit horodatages est documenté comme un FIFO de taille fixe, mais Win10 1709+ élague agressivement les anciennes entrées durant la maintenance en ralenti. v31 élague encore plus agressivement sur les systèmes SSD.
Un parser qui gère v30 gère généralement v31 sans modification, parce que la structure a à peine bougé. Ce qui bouge sous la structure, c'est ce que Windows lui-même écrit dedans, et ça ne se règle pas dans un parser.
Le tableau de métriques de fichiers : où regarder
Si vous n'avez le temps de regarder qu'une seule partie d'un fichier Prefetch à la main, regardez la Section A.
Chaque entrée vous dit : à quel moment dans la fenêtre de traçage de dix secondes le fichier a été touché pour la première fois, combien de temps a duré le contact, et le chemin du fichier. Sur les versions plus récentes, le numéro de référence MFT vous donne l'ID du fichier, ce qui est en or quand le chemin n'existe plus. Vous pouvez recouper la référence de fichier avec la MFT vivante et récupérer l'entrée originale même si le chemin a depuis été réutilisé par un autre fichier.
Un motif spécifique à retenir : un .pf dont la liste de chargement contient des références qui se résolvent vers des entrées MFT marquées supprimées dans la MFT vivante signifie que ces fichiers étaient sur disque quand le binaire a tourné et ont été supprimés depuis. Couplez avec le journal USN et vous pouvez reconstituer le répertoire au moment de l'exécution.
Quels parsers gèrent quelles versions
Notes opérationnelles, à jour au moment où j'écris ces lignes :
PECmdd'Eric Zimmerman est la référence. SCCA v17, v23, v26, v30, v31, et les variantes MAM de v30/v31 sont toutes correctement gérées. C'est l'outil offline que je prends par défaut.libscca(Joachim Metz) est la bibliothèque C sous plusieurs autres outils. Elle suit le format de près et c'est ce contre quoi je bâtirais si je devais écrire mon propre parser aujourd'hui.Windows-Prefetch-Parser(PoorBillionaire) est le parser Python que la plupart des gens trouvent en premier sur GitHub. Il fait l'affaire sur les fichiers pré-Win10. Il ne gère pas la compression MAM par défaut et produira silencieusement des sorties erronées sur les hôtes Win10/11 sauf à patcher un décompresseur. Lisez le README avec attention et vérifiez la date.- Le parser de ce site gère les fichiers SCCA et empaquetés MAM entièrement dans le navigateur. Utile quand vous voulez jeter un œil à un ou deux fichiers sans monter d'outillage. Pour le travail en masse, utilisez PECmd.
Si vous avez un parser dont le nom n'est pas dans cette liste et dont le dernier commit date de plus de deux ans, testez-le sur un échantillon connu avant de faire confiance à sa sortie.
Pourquoi cela compte quand on écrit un rapport
Le champ de version est la première chose à vérifier. Le wrapper MAM est la deuxième. Les offsets de section, lus depuis l'en-tête, sont la troisième. Réussissez ces trois et vous pouvez parser n'importe quel fichier Prefetch de n'importe quel Windows en production de XP à aujourd'hui. Ratez-en un et votre outillage produira un nombre, mais ce nombre sera faux, et vous ne saurez pas qu'il est faux jusqu'à ce que quelqu'un de plus soigneux que vous regarde les octets bruts en contre-interrogatoire.
J'ai été cette personne plus soigneuse, du mauvais côté de l'affaire, plus d'une fois.
Pour aller plus loin
- Joachim Metz, spécification du format de fichier Windows Prefetch — la référence canonique.
- Eric Zimmerman, PECmd — le code source est lui-même une bonne référence pour les cas limites.
- Yogesh Khatri, Prefetch et le wrapper MAM — notes pratiques sur ce qui s'est cassé quand Win10 a introduit la compression.