Skip to content

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.

Publicado el 4 min de lectura

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

OffsetTamañoValorDescripción
040x0fffeefeFirma. El único campo de cabecera que conviene tratar como crítico.
440x00002211Desconocido. Constante observada.
840x00000003Desconocido. Constante observada.
1240x00000001Desconocido. Constante observada.
164variablePosiblemente 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:

Registrochar_countRuta
011C:\foo.exe
17D:\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_count mayor 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.

Lecturas adicionales

Artículos relacionados

El formato BCF es una cabecera, una lista de rutas UTF-16LE con prefijo de longitud, y se acabó. Aquí está lo que significa cada byte y por qué nada ha cambiado desde 2009.
El ciclo de vida del BCF depende de una única tarea programada. Entiende cuándo se ejecuta ProgramDataUpdater, cuándo vacía el archivo y cómo detectar un appraiser deshabilitado.
Un endpoint Win7 marcado por el SOC, 14 rutas en el BCF, tres dignas de una segunda mirada. Una cadena de corroboración realista y cuánto contribuyó realmente el BCF.