Lateral movement and Amcache: ProgramId pivoting across hosts
You confirmed attacker tooling on one Windows host. Next question: where else.
Lateral movement scoping is one of the hardest steps in an investigation. Amcache is one of the most useful tools for it because it carries two cross-host pivots that almost no other Windows artefact stores:
Hash: SHA-1 of the first 31 MiB of every PE the appraiser saw.ProgramId: the 44-character application-identity hash, stable across hosts for the same install.
One suspicious Hash or ProgramId on Host A becomes a query against the parsed Amcache CSVs of every other host you collected. This page is the full playbook.
For prerequisites, see the Amcache complete reference, Amcache FileId explained, and Amcache ProgramId explained.
Collection prerequisite#
The pattern works only if you have collected Amcache from many hosts in a way that lets you join across them. Two practical collection patterns.
KAPE per-host#
Single collection root with per-host sub-directories:
\\fileshare\incident-042\
├── HOST01\
│ └── Windows\AppCompat\Programs\Amcache.hve (+ logs)
├── HOST02\
│ └── ...
├── HOST03\
│ ...
Parse with AmcacheParser pointed at each per-host directory:
Get-ChildItem '\\fileshare\incident-042' -Directory | ForEach-Object {
$host = $_.Name
AmcacheParser.exe `
-f "$($_.FullName)\Windows\AppCompat\Programs\Amcache.hve" `
--csv "\\fileshare\parsed\$host" `
--csvf "${host}_amcache.csv" `
--mp
}You end up with <host>_amcache_UnassociatedFileEntries.csv per host, all in one directory.
Velociraptor fleet hunt#
Windows.Forensics.Amcache as a hunt deposits per-host parsed output into the Velociraptor server. CSVs are named with the host's hostname. Same cross-host queries below apply.
The hash pivot#
Simplest and highest-precision. You have a known-bad SHA-1 from Host A. Find every other host that has it.
$badHash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
Get-ChildItem -Recurse -Filter *_UnassociatedFileEntries.csv |
ForEach-Object {
$host = $_.PSChildName.Split('_')[0]
Import-Csv $_.FullName |
Where-Object { $_.Hash -eq $badHash } |
Select @{n='Host';e={$host}}, FullPath, KeyLastWriteTimestamp, Size
} |
Sort-Object KeyLastWriteTimestampOutput is a per-host timeline of when the bad binary first appeared on each host. Earliest timestamp is your patient-zero candidate. Subsequent timestamps are the spread.
When hash matches over-fit#
The hash pivot misses rebuilds of the same tool. Attackers who recompile their loader between hosts have a different hash on each. For those, fall back to ProgramId.
The ProgramId pivot#
ProgramId is more forgiving than Hash. It catches re-compiles that share name/publisher/version even when binary content differs. See Amcache ProgramId explained for how the identity is constructed.
$badProgramId = '0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff'
Get-ChildItem -Recurse -Filter *_UnassociatedFileEntries.csv |
ForEach-Object {
$host = $_.PSChildName.Split('_')[0]
Import-Csv $_.FullName |
Where-Object { $_.ProgramId -eq $badProgramId } |
Select @{n='Host';e={$host}}, FullPath, Hash, KeyLastWriteTimestamp
} |
Sort-Object KeyLastWriteTimestampFinds every host with any binary identifying as the same application, even with different content hashes. Pair with the hash pivot: hash for exact matches, ProgramId for family matches.
When ProgramId over-fits#
ProgramId produces false positives if the attacker piggybacks on a legitimate application identity. Recompile your tool as PsExec.exe with the genuine PsExec metadata and it gets the same ProgramId. Pair with Hash to disambiguate. A row matching ProgramId but with a hash nobody else in your environment has is highly suspicious.
The path-pattern pivot#
For known attacker install-path patterns, regex against FullPath:
$pattern = '\\AppData\\Roaming\\[a-z0-9]{8}\\update\.exe$'
Get-ChildItem -Recurse -Filter *_UnassociatedFileEntries.csv |
ForEach-Object {
$host = $_.PSChildName.Split('_')[0]
Import-Csv $_.FullName |
Where-Object { $_.FullPath -match $pattern } |
Select @{n='Host';e={$host}}, FullPath, Hash, ProgramId, KeyLastWriteTimestamp
}Useful when:
- You know the intrusion set's install convention.
- The attacker rotates hashes and metadata but reuses path patterns.
- You want to find variants that share path style.
Pair the matches with hash and ProgramId from the per-row results to build a richer detection.
Time-series view of spread#
For each pivot, sort the results by KeyLastWriteTimestamp to see the spread over time. A typical pattern:
2026-04-01 14:23 HOST01 <- patient zero, attacker initial access
2026-04-03 09:11 HOST02 <- 2 days later
2026-04-03 11:34 HOST07
2026-04-03 14:55 HOST09
2026-04-04 02:08 HOST15 <- weekend, attacker working overnight
2026-04-04 02:33 HOST22
2026-04-04 02:51 HOST31
Two readings:
- Multiple hosts within hours of each other is the signature of automated lateral movement (PsExec, WMI, SMB-based).
- The night-of-overnight burst is the typical attacker pattern: initial access during business hours, then accelerated movement once they have credentials and control.
Use these to time-bound the rest of your evidence collection. Pull Sysmon / Security 4624 / 4688 for each host in its KeyLastWriteTimestamp ± 1h window. You get the attacker's actual command lines and credential events with high precision.
Cross-pivoting drivers and devices#
The pattern is not limited to *_UnassociatedFileEntries.csv. For deeper investigations, run the same pivots against:
*_DriverBinaries.csv#
For BYOVD. A vulnerable driver the attacker loaded on one host is almost certainly loaded on the others they reached. Query by driver Hash or DriverName:
$badDriver = 'mhyprot2.sys'
Get-ChildItem -Recurse -Filter *_DriverBinaries.csv |
ForEach-Object {
$host = $_.PSChildName.Split('_')[0]
Import-Csv $_.FullName |
Where-Object { $_.DriverName -eq $badDriver } |
Select @{n='Host';e={$host}}, DriverName, Service, DriverSigned, KeyLastWriteTimestamp
}*_DeviceContainers.csv#
For cases involving connected hardware. Rare in remote attacks, central in insider-threat cases. Query by Manufacturer or FriendlyName:
$suspiciousVendor = 'HakShop'
Get-ChildItem -Recurse -Filter *_DeviceContainers.csv |
ForEach-Object {
$host = $_.PSChildName.Split('_')[0]
Import-Csv $_.FullName |
Where-Object { $_.Manufacturer -match $suspiciousVendor } |
Select @{n='Host';e={$host}}, FriendlyName, Manufacturer, KeyLastWriteTimestamp
}See USB and device history from Amcache for the device-side patterns in detail.
Combining with non-Amcache sources#
The Amcache pivot tells you where the binary was inventoried. To confirm execution and identify the method of movement, correlate with EVTX:
- 4624 (Logon) and 4625 (Failed logon) on destination hosts. When did the attacker authenticate, as whom?
- 4648 (Explicit credential logon). Credentialed lateral movement (PsExec, RDP with passed-in credentials).
- Sysmon 1 / 4688 (Process create) with parent process. Did the attacker process spawn under
services.exe(PsExec / remote service),wmiprvse.exe(WMI), orexplorer.exe(interactive)? - Sysmon 3 (Network connect) + Sysmon 22 (DNS query). Outbound C2.
- Velociraptor / EDR process trees. Same data, easier to navigate.
A single Amcache pivot scoping spread, joined to per-host authentication and process events on the matching hosts, gives you a defensible timeline of: who, when, from where, via what binary, using what credential.
When the pivot misses#
Three situations where the cross-host Amcache pivot underperforms.
The appraiser hasn't run yet on destinations#
Attacker moved laterally within hours of your collection. The destination hosts' appraisers haven't inventoried the binary yet. Amcache shows them clean. Re-collect 24-48 hours later. The spread will appear.
The attacker scrubbed Amcache on each host#
Uncommon (loud, most don't bother) but possible. Use the VSS recovery workflow in Where Amcache.hve is on disk on each suspect host.
Different binaries per host#
If the attacker generated per-victim implants, hash and ProgramId pivots fail by design. Fall back to path patterns and broader behavioural detection (4624 logon from the same unusual IP across many hosts).
Further reading#
- MITRE ATT&CK TA0008 Lateral Movement for technique mapping.
- Microsoft, Detecting lateral movement compendium.
- The Velociraptor docs for
Windows.Forensics.Amcacheand hunt patterns.
Related#
Related posts
- Volatility and Amcache: extracting the hive from memory images
When you only have a RAM dump, Volatility extracts the in-memory copy of Amcache.hve. Hand off to AmcacheParser. The cases where this is the only option.
- RegRipper amcache plugin: what it does and when to use it
RegRipper's amcache plugin produces a plain-text Amcache report. Useful when you're already running RegRipper across other hives or need narrative output. Use AmcacheParser when you need CSV.
- AmcacheParser output columns explained: every CSV field decoded
Field-by-field reference for AmcacheParser's CSV output. FileId, PathHash, ProgramId, LinkDate, BinFileVersion, IsPeFile, and every other column, with the pivots that matter.
- AmcacheParser download guide: official sources, mirrors, and verification
Where to get AmcacheParser. Get-ZimmermanTools, direct download, KAPE, Velociraptor. Plus checksum verification and the air-gapped install pattern.