BAM and DAM: the RegRipper bam plugin for execution + attribution
9 min read
The Background Activity Moderator (bam) and the Desktop Activity Moderator (dam) are two Windows services that exist to throttle background tasks for power management. As a side effect, they keep a per-user list of the last time each executable ran, with the full path and an 8-byte FILETIME. The RegRipper bam plugin pulls that list out of the SYSTEM hive. It is one of the cleanest execution-plus-attribution artifacts on a modern Windows box, and it is consistently under-used. If you are working an intrusion and you have not looked at BAM, you have left a free answer on the table.
What BAM and DAM are for, and what they leak
BAM and DAM are part of the Modern Standby / connected-standby power stack. Their actual job is to decide which apps get to run in the background and how aggressively to suspend them. The forensic value is incidental: to make those decisions, the service records, per user, every executable it has seen run and when it last ran. That record persists in the registry, survives reboots, and is keyed by SID.
The two services do nearly the same thing at different layers. BAM moderates background activity; DAM moderates desktop activity and is more relevant on devices that go into connected standby. In practice you treat them as the same artifact with two storage locations.
Where it lives in the SYSTEM hive
BAM data lives under the current control set in the SYSTEM hive:
SYSTEM\CurrentControlSet\Services\bam\State\UserSettings\<SID>
DAM is the parallel path:
SYSTEM\CurrentControlSet\Services\dam\State\UserSettings\<SID>
Note the State\ segment. On the builds where this artifact first showed up, the data sat directly under Services\bam\UserSettings\<SID> without the State key; Microsoft moved it under State\UserSettings in later builds. A parser that only checks one of the two will silently miss data on the other layout. The plugins on this site check both — they walk Services\bam\State\UserSettings and fall back to Services\bam\UserSettings, and likewise for dam. If you are reading a hive by hand, check both paths before concluding BAM is empty.
Under UserSettings you get one subkey per user SID. That is the attribution. Each value under a SID subkey is one executable: the value name is the full path, the value data is the timestamp.
The per-SID structure is the whole point
This is what makes BAM worth your time. The data is filed under the user's SID, not lumped into a single system-wide table. When you find \Device\HarddiskVolume3\Users\jdoe\AppData\Local\Temp\x.exe under S-1-5-21-...-1001, you know which account ran it. You resolve the SID to a username via the ProfileList key in the SOFTWARE hive (or RegRipper's profilelist plugin) and you have a name.
The value names are stored in native object-manager path form, with the volume expressed as \Device\HarddiskVolumeN\.... You map HarddiskVolumeN to a drive letter using the MountedDevices key. A couple of bookkeeping values (for example SequenceNumber and Version) also live under each SID subkey; those are not executables and should be filtered out. Anything that looks like a \Device\HarddiskVolume... path is a real execution record.
The value data is an 8-byte FILETIME
The value data is binary. The first 8 bytes are a little-endian Windows FILETIME — 100-nanosecond intervals since 1601-01-01 UTC — recording the last time that executable ran for that user. That is the field that matters.
The RegRipper bam plugin reads those 8 bytes and converts them to a timestamp. The parser here does exactly the same thing — it takes the value's raw bytes, skips anything shorter than 8 bytes, and decodes a FILETIME at offset 0:
columns: ["User SID", "Executable", "Last execution (UTC)"]
One row per executable per SID, with the decoded UTC time. There are reports of additional bytes after the FILETIME on some builds whose meaning is not firmly established; treat anything past offset 0x08 as unverified. The 8-byte FILETIME at the start is the part that is well understood and the part you should anchor any timeline assertion on. Do not over-read the trailing bytes.
A parsed BAM record looks like:
SID: S-1-5-21-3623811015-3361044348-30300820-1013 (jdoe)
Executable: \Device\HarddiskVolume3\Users\jdoe\AppData\Local\Temp\update.exe
Last run: 2026-06-12 03:41:08 UTC
That is execution, attribution, and a timestamp from a single value. Few artifacts give you all three without a pivot.
The CurrentControlSet selection gotcha
CurrentControlSet is a runtime construct. It does not exist as a stored key in an offline hive. On the live system it is a symlink to whichever ControlSetNNN the kernel booted, but when you load C:\Windows\System32\config\SYSTEM in a forensic tool, there is no CurrentControlSet — there are ControlSet001, possibly ControlSet002, and a Select key.
To resolve the right one, read SYSTEM\Select\Current. Its DWORD value is the control set number that was current at acquisition. A value of 1 means ControlSet001. The parser here does precisely that: it reads Select\Current and builds ControlSet001-style path, falls back to ControlSet001 if Select is unreadable, and only then falls back to the literal CurrentControlSet for the rare loader that synthesizes it.
If you blindly read ControlSet001 when Select\Current points at ControlSet002, you may be reading a non-booted configuration. For BAM this rarely changes the user data itself, but get into the habit of honoring Select\Current for every SYSTEM-hive artifact — for boot-time and service-config questions it absolutely matters.
Windows version availability
BAM appeared in Windows 10 version 1709 (Fall Creators Update) and is present in every consumer and server build since, including Windows 11. DAM showed up around the same era as part of the same power-management work. If you are looking at a hive from 1703 or earlier, the bam key will not be there; do not read its absence as evidence of anything. On 1709 the data sits under Services\bam\UserSettings; the move to Services\bam\State\UserSettings came in a later build, which is why a two-path check is not optional.
Why this beats the artifacts you already check
BAM occupies a sweet spot that the more famous execution artifacts miss.
UserAssist gives you attribution — it lives in the user's NTUSER.DAT — but it only records GUI-driven launches: Explorer double-clicks, Start menu, Run dialog, taskbar. A binary launched from cmd.exe, a scheduled task, or a service never touches UserAssist. BAM does not care how the executable was started. A binary run from a command line, a scheduled task, or a parent process still gets a BAM entry, and still gets attributed to the SID under which it ran. That is the gap UserAssist leaves and BAM fills.
Shimcache (AppCompatCache) records that a binary was present and gives you a modification time, but it has no attribution at all — it is system-wide and tells you nothing about who ran what. Worse, Shimcache presence does not even guarantee execution on every build. BAM, by contrast, is an execution record (the binary actually ran) tied to a specific user.
Prefetch records execution with run counts but, like Shimcache, does not attribute to a user — see the Prefetch parser. AmCache is execution evidence but system-wide.
So the matrix is: UserAssist gives attribution but only GUI launches; Shimcache and Prefetch give execution but no attribution; BAM gives both, regardless of launch method. The one cost is that BAM only keeps the last run time per executable — there is no run count, and a re-execution overwrites the previous timestamp. Treat the timestamp as volatile, like UserAssist's LastRun: acquire once, at a known time, and pull from VSS snapshots if you need history.
Reading it cleanly
Practical workflow:
- Load the SYSTEM hive, resolve the control set via
Select\Current. - Walk
Services\bam\State\UserSettingsandServices\bam\UserSettings, then thedamequivalents. - For each SID subkey, enumerate the values, skip the bookkeeping ones, and decode the first 8 bytes of each executable value as a FILETIME.
- Resolve each SID to a username via SOFTWARE...\ProfileList.
- Map
\Device\HarddiskVolumeNto a drive letter via MountedDevices.
The interesting hits are the same ones that interest you everywhere else: executables under \AppData\Local\Temp, \ProgramData, \Users\Public, or any path that is not a legitimate install location, attributed to a user account, with a last-run time that lands inside your incident window. A powershell.exe or rundll32.exe BAM entry is unremarkable on its own; the same under a service account at 3 a.m. is a lead.
Tools
- RegRipper's
bamplugin. ReadsServices\bam\State\UserSettings, walks the per-SID subkeys, decodes the FILETIME, and prints path plus timestamp per user. The source is the reference for the exact parsing: bam.pl. There is no separatedamplugin in older RegRipper builds — point the same logic at thedamkey, which is what the parser here does. - Eric Zimmerman's RECmd with a BAM batch file produces the same data in CSV, with SID and timestamp columns.
- The parser on this site surfaces both BAM and DAM as part of its execution-artifact set; you can analyze a SYSTEM hive in your browser without installing anything, and cross-reference the rest of the RegRipper plugins reference.
Edge cases worth knowing
BAM only keeps the last run. Unlike UserAssist's run count, there is no execution counter and no history of prior runs in the live key. The timestamp you see is the most recent one. Reconstruct earlier runs from VSS copies of the SYSTEM hive.
SIDs without a profile. You will sometimes see BAM entries under service or virtual account SIDs that do not resolve cleanly via ProfileList. Resolve those against the well-known SID list and the SAM hive rather than assuming the entry is corrupt.
Clearing leaves a gap, not a clean slate. Removing a value or the SID subkey is possible, but the surrounding structure and the key's LastWrite time still tell a story, and unallocated cells in the hive may retain the removed values. RegRipper's del plugin and yarp's recovery modes are worth a pass.
Do not over-interpret the trailing bytes. Anchor your timeline on the first 8 bytes. The rest of the value data is not reliably documented across builds.
BAM will not carry a case alone, but as the bridge between "a binary ran" and "this user ran it" — without caring whether the launch was a double-click or a scheduled task — it answers a question that UserAssist, Shimcache, and Prefetch each only answer halfway. Check it early, resolve the SIDs, and pivot to Prefetch and the user's NTUSER.DAT to fill in run counts and GUI context.