Lateral movement and Amcache: ProgramId pivoting across hosts
When you confirm attacker tooling on one Windows host, the next question is always: where else? Lateral movement scoping — identifying every other host the attacker reached — is one of the hardest steps in an investigation, and Amcache is one of the most useful tools for it.
The reason: Amcache stores two cross-host pivots that almost no other Windows artefact stores:
Hash— the 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 application.
A single suspicious Hash or ProgramId on Host A becomes a
query you can run against the parsed Amcache CSVs of every other
host you have collected. This page is the full playbook.
For the prerequisites, see Amcache complete reference, Amcache FileId explained, and Amcache ProgramId explained.
The 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-based per-host collection#
Use a 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#
The Windows.Forensics.Amcache artifact, run as a hunt, deposits
per-host parsed output into the Velociraptor server. The CSVs are
named with the host's hostname; the same cross-host queries below
apply.
The hash pivot#
The simplest and highest-precision pivot. 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-Object @{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. The 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 before each lateral move have a
different hash on every host. For those, fall back to ProgramId.
The ProgramId pivot#
ProgramId is more forgiving than Hash — it catches
re-compilations that share name/publisher/version even when the
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-Object @{n='Host';e={$host}}, FullPath, Hash, KeyLastWriteTimestamp
} |
Sort-Object KeyLastWriteTimestampThis finds every host that has any binary identifying as the same
application — even with different content hashes. Pair with the
hash pivot: hash for high-precision matches, ProgramId for
family-level matches.
When ProgramId over-fits#
ProgramId catches false positives if the attacker piggybacks on
a legitimate application identity (e.g. recompiling
PsExec.exe-named tooling that gets the same ProgramId as
genuine PsExec). Pair with Hash to disambiguate; a row
matching ProgramId but with a hash that 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-Object @{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:
- The cadence (multiple hosts within hours of each other) is characteristic of automated lateral-movement tooling (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 patterns to time-bound the rest of your evidence
collection. Pull Sysmon / Security 4624 / 4688 for each host in
its KeyLastWriteTimestamp ± 1 h window — you get the
attacker's actual command lines and credential events with high
precision.
Cross-pivoting Driver and Device evidence#
The cross-host pattern is not limited to
*_UnassociatedFileEntries.csv. For deeper investigations, run
the same pivot patterns against:
*_DriverBinaries.csv#
For BYOVD investigations — 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-Object @{n='Host';e={$host}}, DriverName, Service, DriverSigned, KeyLastWriteTimestamp
}*_DeviceContainers.csv#
For investigations where the attacker 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-Object @{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:
- 4624 (Logon) + 4625 (Failed logon) on the destination hosts — when did the attacker authenticate, and 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), underwmiprvse.exe(WMI), or underexplorer.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 the 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 Amcache cross-host pivot underperforms:
The appraiser hasn't run on the destination hosts yet#
If the attacker moved laterally within hours of your collection, the destination hosts' appraisers may not have inventoried the binary yet. Amcache shows them as clean. Re-collect 24–48 hours later; the spread will appear.
The attacker scrubbed Amcache on each host they reached#
Uncommon (mostly because it is noisy and most attackers do not bother) but possible. Use the Volume Shadow Copy recovery workflow in Where Amcache.hve is on disk on each host where you suspect scrubbing.
The attacker used different binaries per host#
If the attacker generated per-host implants (truly per-victim
malware), hash and ProgramId pivots fail by design. Path patterns
and broader behavioural detections (4624 logon from the same
unusual IP across many hosts) become primary.
See also#
- Amcache complete reference — the artefact in full.
- Amcache FileId explained — the content-hash pivot.
- Amcache ProgramId explained — the application-identity pivot.
- Amcache for malware investigation — single-host triage that produces the bad indicators you pivot across hosts with.
- Recovering deleted-binary evidence from Amcache — when the destination-host binary was wiped after the move.
To explore your own collected hives without installing anything, drop a file on the parser home page — it parses entirely in your browser.
Related posts
- Volatility and Amcache: extracting the hive from memory images
A practical guide to recovering Amcache from a Windows memory image using Volatility — when memory-side recovery is the only option, which plugins to use, and how to hand off to AmcacheParser.
- RegRipper amcache plugin: what it does and when to use it
A practical guide to RegRipper's amcache plugin — what it parses, how its text output differs from AmcacheParser's CSV, and when to reach for it instead of (or alongside) the Zimmerman tool.
- AmcacheParser output columns explained: every CSV field decoded
A field-by-field reference for AmcacheParser's CSV output — FileId, PathHash, ProgramId, LinkDate, BinFileVersion, IsPeFile, and every other column, with the pivots that matter in DFIR.
- AmcacheParser download guide: official sources, mirrors, and verification
Every way to download Eric Zimmerman's AmcacheParser — Get-ZimmermanTools, direct download, KAPE, Velociraptor — with checksum verification and air-gapped install patterns.