BAM Value Data Format: FILETIME, Paths and Internals
Byte-level walkthrough of a BAM registry value: the 24-byte REG_BINARY, the FILETIME, device paths vs package names, Version, SequenceNumber and key times.
TL;DR. Every BAM value is name = executable, data = REG_BINARY. The first 8 bytes of the data are a little-endian FILETIME (100 ns ticks since 1601-01-01 UTC); on the builds studied the data is 24 bytes long and the remaining 16 bytes are not needed to read the time. Value names are either NT device paths (\Device\HarddiskVolume3\…) or package family names. Version and SequenceNumber are bookkeeping, not programs. The SID key's own last-write time is a free extra timestamp.
If you only ever read BAM through a tool, this is the article that tells you what the tool is doing and where it can go wrong. Background first: the BAM/DAM forensics guide and where the BAM key lives.
Anatomy of a SID key
A typical bam\State\UserSettings\<SID> key looks like this (fictional sample data from this site):
| Value name | Type | Data (hex) |
|---|---|---|
Version | REG_DWORD | 01 00 00 00 |
SequenceNumber | REG_DWORD | 76 00 00 00 |
\Device\HarddiskVolume3\Users\alice\Downloads\Invoice_2026-0914.pdf.exe | REG_BINARY | 80 0d 7c 1d 29 44 dd 01 + 16 bytes |
MSTeams_8wekyb3d8bbwe | REG_BINARY | 24 bytes |
Three kinds of value, three rules:
- Bookkeeping DWORDs —
VersionandSequenceNumber. Skip them when listing programs. Their semantics aren't publicly documented; treat any interpretation (for example, thatSequenceNumbergrows with updates) as a hypothesis to test on your own reference system. - Executable entries —
REG_BINARY, name is a path, data is at least 8 bytes. - Anything else — a value that isn't
REG_BINARYor is shorter than 8 bytes is not a BAM record. A robust parser ignores it rather than crashing. Ours does exactly that, and reports unreadable values as warnings instead of dropping them silently.
Decoding the FILETIME
A FILETIME is a 64-bit count of 100-nanosecond intervals since 1 January 1601 UTC (Microsoft Learn). It is stored little-endian, so read the bytes right to left:
bytes 80 0d 7c 1d 29 44 dd 01
as u64 0x01DD44291D7C0D80 = 134338507270000000
seconds 13433850727 (÷ 10,000,000)
minus 11644473600 (1601 → 1970 offset)
unix 1789377127 → 2026-09-14 09:12:07 UTC
Two things to keep in mind:
- It's UTC. The registry does not store the host's time zone in the value. The SYSTEM hive does record the configured zone (
Control\TimeZoneInformation\TimeZoneKeyName), which our tool displays in the header so you can reason about local time. The tool's "Local" toggle converts to your browser's time zone, not the suspect's. - Precision. FILETIME has 100 ns resolution. JavaScript
Dateonly has milliseconds, so browser tools that convert throughDateround. The BAM/DAM Parser keeps the full seven decimal places in UTC view and exports the raw FILETIME as a decimal string (a 64-bit integer doesn't fit in a JSON number without loss).
What event the time marks
Maxim Suhanov's analysis of bam.sys shows the timestamp is updated both when the process is created and when it terminates (BAM internals). So the value is the latest of those events that BAM persisted. For long-running programs, prefer to call it "last recorded activity" in reports, and use Prefetch or process-creation logs if you need the start time.
The remaining 16 bytes
On the Windows 10 builds Suhanov examined, BAM values are 24 bytes: the FILETIME, then 16 more bytes. His analysis links one of the trailing DWORDs to the moderation state (whether the user left background activity to Windows or changed the setting for that app), and notes that entries with a non-default state are not subject to the age-based cleanup (source). Beyond that, the layout is not publicly documented.
Practical consequences:
- Don't assume a fixed 24-byte size; read the first 8 bytes and keep the rest.
- Keep the raw bytes in your export. Our tool shows the full data in the entry detail panel ("Raw value data") and in the
DataHexCSV column, so you can revisit the trailing bytes if new research appears.
Value names: device paths and packages
NT device paths
Most names look like \Device\HarddiskVolume3\Windows\System32\cmd.exe. That is the kernel's NT device path: the volume object, then the path on that volume. There is no drive letter because drive letters are a user-mode mapping (Microsoft Learn: defining an MS-DOS device name).
To map HarddiskVolumeN to a letter offline:
- Most of the time the system volume is the one holding
\Windows\System32\…entries; everything with that volume number is onC:. - For other numbers, correlate with other artifacts that record both forms — for example Prefetch files, which store volume device paths and serial numbers (libscca format). Live-response output that recorded both the device path and the drive letter helps too. (The
MountedDeviceskey in the same hive maps letters to volume identifiers, not toHarddiskVolumeNnumbers, so it is only a partial help.) - Volume numbers are assigned by the running system and are not a stable identifier across hosts, or even necessarily across configuration changes on one host. Don't compare them across machines.
Package family names
Packaged apps (Store, MSIX) appear by package family name: Microsoft.WindowsCalculator_8wekyb3d8bbwe. The part after the underscore is the publisher ID hash, the part before is the package name (Microsoft Learn: package identity). There is no path and no executable name. Our parser shows the package name as the program and tags the row as an app, and skips the file-path heuristics for it.
Other shapes
Occasionally you will meet a name that is neither — for example a path without the \Device\ prefix. Don't discard it; the tool classifies it as "other" and still decodes the timestamp.
The SID key's last-write time
Every registry key has a last-written FILETIME in its key node (msuhanov/regf specification). For a BAM SID key, that time moves whenever any value under it changes. It gives you:
- A sanity check: the key time should be at or after the newest value time for that user. A key time far later than every value suggests an entry was deleted or rewritten afterwards — see BAM anti-forensics.
- A lower bound on when the hive was last updated for that user.
Our tool shows it as "SID key last written" in the detail panel and exports it as SidKeyLastWriteUtc.
Hive-level fields that matter
A few base-block fields change how much you trust the values (libregf):
| Offset | Field | Why it matters for BAM |
|---|---|---|
| 0 | regf signature | Confirms it's a hive at all |
| 4 / 8 | Primary / secondary sequence numbers | Different values = dirty hive: recent BAM writes may be in the logs |
| 12 | Last written FILETIME | When the primary file was last flushed |
| 48 | Embedded file name | Distinguishes SYSTEM from SOFTWARE, SAM… when files are renamed |
How the parser maps all this
| Output column | Source |
|---|---|
LastExecutionUtc | First 8 bytes of the value data, as ISO-8601 with 100 ns |
FileTime | Same value, raw decimal |
Program | File name, or package name without publisher hash |
Path, Volume | Value name; HarddiskVolumeN when present |
SID, User | Key name; account from ProfileList or well-known SID label |
Service, ControlSet, ActiveControlSet, LegacyLayout | Where in the hive the key was found |
SidKeyLastWriteUtc | Key node timestamp |
DataHex | Full value data |
Drop a hive on the BAM/DAM Parser and open any row's detail panel to see each of these side by side. For the same exercise with other registry artifacts, Registry Parser covers the wider hive.
Sources
- Maxim Suhanov, BAM internals.
- Microsoft Learn, FILETIME.
- Maxim Suhanov, Windows registry file format specification; Joachim Metz, libregf documentation.