El formato .bcf, byte a byte
Una lectura práctica del formato binario de RecentFileCache.bcf con un volcado hex, un registro decodificado y los casos límite que un parser tiene que manejar.
El formato RecentFileCache.bcf es lo bastante pequeño como para aprenderlo con un café. La referencia autorizada es libyal/dtformats. Este post recorre la misma especificación con un volcado hex real y los casos límite que en la práctica hacen tropezar a los parsers.
Endianidad y codificación
- Todos los enteros son little-endian.
- Las cadenas son UTF-16LE, terminadas en NUL.
- Sin pie, sin cuerpo con prefijo de longitud. Los registros corren hasta EOF.
Si alguna vez has escrito un parser para alguno de los formatos binarios de la era AppCompat de Windows, nada de esto te sorprenderá.
La cabecera de 20 bytes
| Offset | Tamaño | Valor | Descripción |
|---|---|---|---|
| 0 | 4 | 0x0fffeefe | Firma. El único campo de cabecera que conviene tratar como crítico. |
| 4 | 4 | 0x00002211 | Desconocido. Constante observada. |
| 8 | 4 | 0x00000003 | Desconocido. Constante observada. |
| 12 | 4 | 0x00000001 | Desconocido. Constante observada. |
| 16 | 4 | variable | Posiblemente una suma de control. Ningún parser público la verifica. |
Valida la firma. Si los bytes en offset 0 no son fe ef ff 0f little-endian, no estás mirando un RecentFileCache.bcf. Sal y avisa. Las otras tres constantes han tenido el mismo valor en todas las muestras que he visto en una década de hosts Win7. Trátalas como constantes para chequeos de cordura; no condiciones el parseo a ellas.
Un registro
offset size field
0 4 char_count ; número de code units UTF-16, INCLUYE el NUL final
4 var path ; UTF-16LE, terminada en NUL
Un char_count de 5 significa 4 code units de ruta más el NUL, lo que son 10 bytes de payload más 4 del contador.
Un char_count de 0 no es un registro vacío legítimo. Todos los parsers que he visto lo tratan como corrupción o como terminador y paran. Haz lo mismo. No intentes leer 0 bytes y entrar en bucle.
Un volcado hex trabajado
Archivo mínimo que contiene C:\foo.exe y D:\bar:
00000000 fe ef ff 0f 11 22 00 00 03 00 00 00 01 00 00 00 cabecera (firma + 3 desconocidos)
00000010 00 00 00 00 posible checksum (a cero)
00000014 0b 00 00 00 char_count = 11 (10 chars + NUL)
00000018 43 00 3a 00 5c 00 66 00 6f 00 6f 00 2e 00 65 00 "C:\foo.e"
00000028 78 00 65 00 00 00 "xe" + NUL
0000002e 07 00 00 00 char_count = 7 (6 chars + NUL)
00000032 44 00 3a 00 5c 00 62 00 61 00 72 00 00 00 "D:\bar" + NUL
Decodificado:
| Registro | char_count | Ruta |
|---|---|---|
| 0 | 11 | C:\foo.exe |
| 1 | 7 | D:\bar |
Tamaño total: 64 bytes.
Nota que las rutas registradas en muestras reales casi siempre llevan el prefijo NT \??\ (p. ej. \??\C:\Users\bob\AppData\Local\Temp\setup.exe). Esa es la notación de ruta DOS del NT object manager. Quítalo durante la normalización. No intentes "arreglarlo" antes de validar la firma.
Casos límite que un parser real ha de manejar
Un parser robusto no confía en el archivo. Los .bcf reales en la naturaleza llegan con todos estos problemas.
- Firma incorrecta. Las copias forenses vienen rellenadas, prefijadas por cabeceras de adquisición o sacadas de una región mal alineada. Valida la firma; avisa pero continúa si quieres comportamiento tolerante.
char_count = 0. Trátalo como terminador. Para.char_countmayor que los bytes restantes. Archivo truncado o registro corrupto. Para y avisa. No leas más allá de EOF.- Bytes finales tras el último registro bien formado. Común, sobre todo en archivos adquiridos a mitad de reescritura. Saca una advertencia para que el analista sepa que hay datos colgantes inexplicados.
- Cuerpo de cadena vacío (
char_count = 1). Solo el NUL. Técnicamente válido. Emite una ruta vacía; no te caigas. - Pares sustitutos UTF-16. El formato los permite. Decodifica permisivamente con reemplazo en secuencias malas. No tumbes el archivo entero por un code point feo.
La implementación de referencia en este repo hace todo lo anterior y emite un array estructurado de advertencias junto a los registros.
Lo que el formato no te dice
Tras todo ese parseo cuidadoso, lo que tienes es una lista de rutas. No hay:
- marcas temporales por entrada,
- tamaños de archivo,
- hashes,
- firmantes,
- metadatos PE,
- ni indicación de cuándo se añadió una entrada relativa al último pase de
ProgramDataUpdater.
Si quieres responder "cuándo" o "qué era este binario", empareja cada hit BCF interesante con Amcache.hve de la misma era (o Prefetch, o el archivo en disco, o el registro MFT) y triangula. La BCF te da una lista de pistas. Los metadatos viven en otro sitio.