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.
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
| Offset | Size | Value | Description |
|---|---|---|---|
| 0 | 4 | 0x0fffeefe | Signature. The only header field worth treating as load-bearing. |
| 4 | 4 | 0x00002211 | Unknown. Observed constant. |
| 8 | 4 | 0x00000003 | Unknown. Observed constant. |
| 12 | 4 | 0x00000001 | Unknown. Observed constant. |
| 16 | 4 | varies | Possibly 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:
| Record | char_count | Path |
|---|---|---|
| 0 | 11 | C:\foo.exe |
| 1 | 7 | D:\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_countlarger 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
ProgramDataUpdaterrun.
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.