Le format de fichier RecentFileCache.bcf, en termes simples
Le format BCF est un en-tête, une liste de chemins UTF-16LE préfixés de leur longueur, et c'est tout. Voici ce que signifie chaque octet et pourquoi rien n'a changé depuis 2009.
Il y a des formats de fichiers forensiques qui vous résistent. Les logs CBS, les bases ESE, la structure binaire du journal d'événements Windows. RecentFileCache.bcf n'en fait pas partie. C'est un en-tête, un compteur et une liste de chaînes. Vous pouvez le parser en douze lignes de Python et vous n'aurez pas besoin d'un éditeur hexadécimal pour quoi que ce soit.
Cette simplicité est la raison pour laquelle le format n'a pas changé depuis la sortie de Windows 7 SP1. Il n'y a rien à étendre. Microsoft est passé à l'AmCache au lieu de versionner ce fichier.
Ce que contient le fichier
C:\Windows\AppCompat\Programs\RecentFileCache.bcf est écrit par la tâche d'évaluation d'Application Experience sur Win7 SP1 et Server 2008 R2. La disposition sur disque, dans l'ordre :
- Un en-tête fixe. Vingt octets.
- Un compteur d'entrées de 32 bits little-endian.
- Zéro ou plusieurs entrées, chacune étant une chaîne UTF-16LE préfixée de sa longueur contenant le chemin NT complet d'un exécutable.
C'est tout le fichier. Pas de checksum. Pas de CRC. Pas de queue. Pas d'horodatage par entrée. Pas de hash par entrée. Si le fichier fait 18 octets, il a un en-tête et rien d'autre. S'il fait deux mégaoctets, il a grossi pendant un moment parce que l'appraiser ne s'est pas exécuté.
L'en-tête
Les vingt premiers octets sont constants sur chaque BCF que j'ai tiré d'un hôte Win7. Cinq valeurs 32 bits little-endian, toutes des constantes magiques. L'intéressante est la première : 0xFE 0xFF 0xEE 0xFE. Le reste est zéro ou padding. Si vous voyez une valeur différente dans les quatre premiers octets, vous n'avez pas un RecentFileCache.bcf. Vous avez autre chose qui partage le nom.
J'ai vu exactement un cas d'en-tête corrompu sur le terrain, et c'était causé par un antivirus qui avait mis le fichier à moitié en quarantaine. Les octets d'en-tête avaient été remplacés par des zéros. Ça vaut la peine de vérifier.
Le compteur d'entrées
Les octets 20 à 23, little-endian, sont le compteur des entrées qui suivent. Si ce nombre est zéro, l'appraiser a tourné récemment et a vidé l'ensemble de travail. S'il est grand, soit rien n'a vidé le fichier depuis des semaines, soit beaucoup de nouveaux binaires sont apparus entre les passes d'évaluation. Les deux sont intéressants à leur manière.
Le compteur fait autorité. Si le compteur dit dix et que le fichier contient onze chaînes, traitez la onzième comme du déchet. En pratique cela n'arrive pas parce que le fichier est écrit atomiquement par un seul processus, mais les parsers ne devraient pas faire confiance au simple parcours de chaînes pour trouver la fin.
Les entrées
Chaque entrée est :
- Une longueur 32 bits little-endian en octets (pas en caractères).
- Autant d'octets de données de chaîne UTF-16LE.
Les chaînes sont des chemins du gestionnaire d'objets NT. Elles commencent par \??\ puis un chemin Windows normal :
\??\C:\Windows\System32\rundll32.exe
\??\C:\Users\bob\AppData\Local\Temp\setup.exe
Traitez le préfixe \??\ comme C:\. C'est la manière du noyau de dire "espace de nommage des périphériques DOS" et il ne porte aucune signification forensique au-delà de "ceci est un chemin de fichier normal". Enlevez-le pour l'affichage, gardez-le pour la fidélité si vous exportez une sortie lisible par machine.
La longueur est en octets, ce qui fait trébucher presque tout le monde au premier parse. Un chemin de trente caractères Unicode prend soixante octets plus ce que le fichier utilise pour le null final, qui fait typiquement deux octets mais sur lequel vous ne devriez pas vous appuyer. Lisez la longueur, lisez ce nombre d'octets, décodez en UTF-16LE, retirez les nulls finaux. Fini.
Il n'y a pas de séparateur entre entrées. Le champ de longueur de l'entrée suivante commence immédiatement après les données de chaîne de l'entrée précédente. Si votre parser dérive d'un octet, vous obtiendrez du déchet pour le reste du fichier. C'est pour cela que respecter le compteur d'entrées importe : il vous donne une condition d'arrêt qui ne dépend pas du contenu de la chaîne.
Douze lignes de Python
import struct
def parse_bcf(path):
with open(path, "rb") as f:
data = f.read()
if data[:4] != b"\xfe\xff\xee\xfe":
raise ValueError("not a RecentFileCache.bcf")
count = struct.unpack_from("<I", data, 20)[0]
offset = 24
entries = []
for _ in range(count):
length = struct.unpack_from("<I", data, offset)[0]
offset += 4
s = data[offset:offset + length].decode("utf-16-le").rstrip("\x00")
entries.append(s)
offset += length
return entries
C'est suffisant pour parser n'importe quel BCF que vous rencontrerez. Pas de dépendances. Pas de cas limites que j'aie rencontrés en production. Si vous faites cela dans un environnement d'entreprise où vous ne pouvez pas exécuter du Python arbitraire sur la machine de preuve, le parser sur ce site fait la même chose dans le navigateur sans upload.
Ce que le format ne vous dit pas
C'est la partie où la simplicité du format devient une limitation. Le BCF n'enregistre pas :
- Le temps d'exécution. Vous savez que le binaire a tourné depuis la dernière passe d'évaluation, et c'est tout.
- Le nombre d'exécutions. Une exécution et cent exécutions sont identiques.
- L'utilisateur qui l'a exécuté. Le fichier est par machine.
- Un hash. Vous ne pouvez pas pivoter vers du threat intel depuis le contenu du fichier.
- Une liste de chargement. Contrairement à Prefetch, il n'y a pas d'enregistrement des DLL chargées dans le processus.
- Les arguments de ligne de commande. Aucun. Pas même l'existence d'arguments.
Si vous avez besoin de l'un de ceux-là, appariez le BCF avec Prefetch, la Master File Table, le journal USN, et n'importe quel EVTX que vous avez. Le BCF vous dit le quoi ; tout le reste vous dit le quand, le comment et le qui.
Pourquoi le format n'a pas changé
Microsoft a remplacé ce fichier par l'AmCache dans Windows 8 et n'est jamais revenu y ajouter des champs au BCF. L'AmCache est un hive façon ESE avec des dizaines de champs par entrée : hashes, métadonnées de fichier, temps d'installation, informations sur l'éditeur. Le BCF n'allait jamais grossir pour devenir cela. Il existait comme fichier de travail pour un pipeline d'évaluation qui a depuis été redessiné autour de primitives différentes.
La conséquence pratique est qu'un parser de 2014 et un parser de 2026 produisent la même sortie sur la même entrée. Il n'y a pas de dérive de version dont s'inquiéter. Les outils écrits avant que le format soit publiquement documenté fonctionnent toujours. L'analyse publique originale de Willi Ballenthin chez Mandiant en 2014 est encore exacte aujourd'hui ; je n'ai pas vu un seul octet dévier de ce qu'il a documenté.
C'est rare pour les artefacts Windows. La plupart mutent au fil des builds. Shimcache a changé cinq fois entre Win7 et Win10. Le schéma de l'AmCache a gagné et perdu des colonnes. Prefetch a été compressé dans Windows 10. Le BCF est juste là, figé, parce que personne n'avait de raison d'y toucher après Win7.
Une note sur le tronquage
Après que l'appraiser ait traité le fichier, l'OS le tronque à "en-tête plus compteur zéro". Le fichier n'est pas supprimé. Les métadonnées du système de fichiers (heure de création, entrée du répertoire parent) survivent. Cela importe pour deux raisons :
- L'heure de création de
RecentFileCache.bcflui-même peut précéder chaque chemin à l'intérieur de mois ou d'années. N'utilisez pas les horodatages du fichier lui-même pour borner les entrées. - Un binaire qui a tourné il y a deux jours et a été traité par l'appraiser hier est parti du fichier, mais le fichier est toujours là, ayant l'air innocent et petit. Vide n'est pas la même chose que jamais utilisé.
Pour aller plus loin
- Willi Ballenthin, RecentFileCache.bcf. L'analyse publique originale du format. Toujours correcte.
- Microsoft, Application Experience et inventaire de programmes. Contexte fournisseur pour la raison d'être du fichier.
- Yogesh Khatri, AmCache and friends. Pour quand vous avez besoin de savoir ce qui est venu ensuite.
Si vous avez déjà parsé un format forensique binaire, le BCF aura l'air d'un exercice d'échauffement. C'est la bonne réaction. Dépensez votre effort sur le triage de la liste des chemins, pas sur le format du fichier.