Skip to content

The .bcf format, byte-by-byte

A hands-on read of the RecentFileCache.bcf binary format with a hex dump, a decoded record, and the edge cases a parser has to handle.

Published on 4 min read

The RecentFileCache.bcf format is small enough to learn over a coffee. The authoritative reference is libyal/dtformats. This post walks the same spec with a real hex dump and the edge cases that actually trip parsers.

Endianness and encoding

  • All integers are little-endian.
  • Strings are UTF-16LE, NUL-terminated.
  • No footer, no length-prefixed body. Records run until EOF.

If you have ever written a parser for one of the Windows AppCompat-era binary formats, none of that will surprise you.

The 20-byte header

OffsetSizeValueDescription
040x0fffeefeSignature. The only header field worth treating as load-bearing.
440x00002211Unknown. Observed constant.
840x00000003Unknown. Observed constant.
1240x00000001Unknown. Observed constant.
164variesPossibly a checksum. No public parser verifies it.

Validate the signature. If the bytes at offset 0 are not fe ef ff 0f little-endian, you are not looking at a RecentFileCache.bcf. Bail out and warn. The other three constants have been the same value on every sample I have seen across a decade of Win7 hosts. Treat them as constants for sanity checks; do not gate parsing on them.

A record

offset  size  field
   0     4    char_count   ; number of UTF-16 code units, INCLUDES the trailing NUL
   4    var   path         ; UTF-16LE, NUL-terminated

A char_count of 5 means 4 code units of path plus the NUL, which is 10 bytes of payload plus 4 for the count.

A char_count of 0 is not a legitimate empty record. Every parser I have seen treats it as either corruption or a terminator and stops. Do the same. Do not try to read 0 bytes and loop forever.

A worked hex dump

Minimal file containing C:\foo.exe and D:\bar:

00000000  fe ef ff 0f 11 22 00 00  03 00 00 00 01 00 00 00   header (sig + 3 unknowns)
00000010  00 00 00 00                                         possible checksum (zeroed)

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

Decoded:

Recordchar_countPath
011C:\foo.exe
17D:\bar

Total file size: 64 bytes.

Note that the recorded paths in real samples almost always carry the \??\ NT-prefix (e.g. \??\C:\Users\bob\AppData\Local\Temp\setup.exe). That is the NT object manager's DOS-path notation. Strip it during normalization. Do not try to "fix" it before validating the signature.

Edge cases a real parser has to handle

A robust parser does not trust the file. Real .bcf files in the wild come with all of these problems.

  • Wrong signature. Forensic copies get padded, prefixed by acquisition headers, or pulled from a misaligned region. Validate the signature; warn but continue if you want forgiving behaviour.
  • char_count = 0. Treat as terminator. Stop.
  • char_count larger than the remaining bytes. Truncated file or a corrupt record. Stop and warn. Do not read past EOF.
  • Trailing bytes after the last well-formed record. Common, especially on files acquired mid-rewrite. Surface a warning so the analyst knows there is unexplained tail data.
  • Empty-string body (char_count = 1). Just the NUL. Technically valid. Emit an empty path; do not crash.
  • UTF-16 surrogate pairs. The format permits them. Decode permissively with replacement on bad sequences. Do not fail the whole file over one ugly code point.

The reference implementation in this repo does all of the above and emits a structured warning array alongside the records.

What the format does not tell you

After all that careful parsing, what you have is a list of paths. There are no:

  • per-entry timestamps,
  • file sizes,
  • hashes,
  • signers,
  • PE metadata,
  • nor any indication of when an entry was added relative to the last ProgramDataUpdater run.

If you want to answer "when" or "what was this binary", you pair every interesting BCF hit with Amcache.hve for the same era (or Prefetch, or the on-disk file itself, or the MFT record) and triangulate. The BCF gives you a list of leads. The metadata lives elsewhere.

Further reading

Related articles

The BCF format is a header, a list of length-prefixed UTF-16LE paths, and that's it. Here is what every byte means and why nothing has changed since 2009.
The BCF's lifecycle is one scheduled task. Understand when ProgramDataUpdater runs, when it clears the file, and how to spot a disabled appraiser.
A SOC-flagged Win7 endpoint, 14 paths in the BCF, three worth a second look. A realistic corroboration chain and how much the BCF actually contributed.