Hunting commodity malware with Amcache
When you arrive at a Windows host that "feels off" — a user reported strange behaviour, an EDR threw a low-confidence alert, a firewall log shows something the host should not be doing — Amcache is one of the fastest artefacts to give you a yes/no answer on "is there obvious attacker tooling here?".
This page is the commodity-malware triage playbook: the specific filters, pivots, and queries that surface the typical attacker artefacts on the typical infected workstation.
For artefact background, see the Amcache complete reference; for combining Amcache with other execution evidence, see Amcache vs Prefetch, Amcache vs ShimCache, and Amcache vs SRUM.
Why Amcache is the right first stop#
Three properties make Amcache the natural first artefact in a malware triage:
- It records the SHA-1 of every PE the appraiser saw. That hash goes straight into a VirusTotal or TI-feed query.
- It records the full path. Path is half the story for
commodity-malware triage — anything in
\Users\<x>\AppData\Local\Temp\or\ProgramData\is suspect by default. - It survives binary deletion. See Recovering deleted-binary evidence from Amcache.
Together: parse, filter, hash-check, and you have a defensible "is this host owned?" answer in minutes.
The baseline triage filter#
Apply this to *_UnassociatedFileEntries.csv first, every time:
Import-Csv .\HOST_amcache_UnassociatedFileEntries.csv |
Where-Object {
$_.IsPeFile -eq 'True' -and
-not $_.Publisher -and
$_.FullPath -match '\\Users\\|\\ProgramData\\|\\AppData\\|\\Temp\\|\\Public\\'
} |
Select-Object KeyLastWriteTimestamp, FullPath, Hash, Size, LinkDate |
Sort-Object KeyLastWriteTimestamp -DescendingWhat this surfaces:
- Unsigned PE binaries in paths a normal user can write to.
What it misses (and you need follow-up filters for):
- Signed-but-malicious binaries (signed ransomware, signed RATs).
- Binaries in protected paths (
\Windows\System32\— would require admin to drop, but happens with privilege escalation). - Living-off-the-land binaries (LOLBINs) — those are signed and
legitimately in
\Windows\System32\.
This filter typically returns 5–50 rows on a typical infected workstation. Most are false positives (portable apps, dev tools, custom scripts). Triage from there.
False-positive controls#
A few patterns that filter out the obvious benign rows quickly:
Exclude developer / portable-app paths#
$benignPaths = @(
'\\AppData\\Local\\JetBrains\\',
'\\AppData\\Local\\Programs\\Microsoft VS Code\\',
'\\AppData\\Local\\Programs\\Slack\\',
'\\AppData\\Local\\Programs\\Notion\\',
'\\AppData\\Roaming\\Spotify\\',
'\\AppData\\Local\\GitHubDesktop\\',
'\\AppData\\Local\\Microsoft\\Teams\\',
'\\AppData\\Local\\Discord\\',
'\\AppData\\Local\\1Password\\'
)
$rows | Where-Object {
$path = $_.FullPath
-not ($benignPaths | Where-Object { $path -match $_ })
}Adjust the list to your environment's standard developer / power- user tooling. A baseline of "known portable apps in user paths" is worth maintaining once per environment.
Exclude known publishers via hash#
If you have a known-good hash list (your internal allowlist,
NSRL, Cisco Talos clean-hash feed), join the Amcache Hash
column against it and drop matches.
Exclude very common attacker decoy names you trust elsewhere#
update.exe, service.exe, svchost.exe in user paths are
suspicious because they share names with legitimate Windows
binaries — the names alone are not the signal; the path + missing
publisher are. The baseline filter already catches these.
High-precision attacker patterns#
Once you have the filtered list, these patterns are typically high-precision indicators on a typical Windows endpoint:
\AppData\Local\Temp\ + PE + unsigned#
C:\Users\bob\AppData\Local\Temp\xyz1234.tmp.exe
C:\Users\bob\AppData\Local\Temp\setup_temp\install.exe
Almost never a benign developer pattern. Common for:
- Initial-access droppers from phishing attachments.
- Office macro payloads.
- Browser-delivered downloads that execute from
Temp.
Suspicious filenames that mimic OS components#
C:\Users\Public\Documents\svchost.exe
C:\ProgramData\msmpeng.exe
C:\Users\bob\AppData\Roaming\winupdate.exe
The combination of an OS-component-style name and a path no OS component would ever sit at is a near-certain malware indicator.
LinkDate clusters#
Sort the filtered list by LinkDate:
$rows | Sort-Object LinkDateLook for clusters: 3-10 binaries with LinkDate values within
a few hours of each other. Attackers frequently compile their full
toolkit in one sitting, and the link timestamps cluster. This is a
strong "this is one campaign" signal.
Hash-only matches in Unassociated#
Take each Hash from your filtered rows and check whether it
appears anywhere in *_AssociatedFileEntries.csv (i.e. matched
to an installed application). If the same hash appears Associated
on this host or any other collected host, the binary has a
benign-or-tracked context elsewhere. If it appears only
Unassociated and only on this one host, that increases suspicion.
Confirming execution#
A row in Amcache means the binary was on disk at appraiser time; it does not mean it ran. To confirm execution:
Prefetch#
For each suspicious row, check whether C:\Windows\Prefetch\
contains a .pf for that binary:
NOTEPAD.EXE-1A2B3C4D.pf
XYZ1234.TMP.EXE-FEDCBA98.pf
A .pf is definitive execution evidence. Parse Prefetch with
PECmd.exe to get the run timestamps. See
Amcache vs Prefetch for the full
comparison.
Security 4688 / Sysmon 1#
If process-creation auditing is enabled (it should be), every launch logs a 4688 or Sysmon 1 event with full command line. Filter to the binary's path and you get every execution with arguments — usually the most useful single data point in a malware triage.
SRUM#
If the binary used measurable CPU or network, SRUM
(SRUDB.dat) has an hour-level row for it. See
Amcache vs SRUM. Especially useful for
network activity — if SRUM shows the binary sent gigabytes of
network bytes, that is your data-exfiltration timeline.
Scoping the incident across hosts#
Once you have confirmed at least one bad binary on one host, the question becomes: where else does this exist?
Hash pivot across collected Amcache#
$badHashes = @('da39a3ee5e6b4b0d3255bfef95601890afd80709',
'1234567890abcdef1234567890abcdef12345678')
Get-ChildItem -Recurse -Filter *_UnassociatedFileEntries.csv |
ForEach-Object {
$host = $_.PSChildName.Split('_')[0]
Import-Csv $_.FullName |
Where-Object { $badHashes -contains $_.Hash } |
Select-Object @{n='Host';e={$host}}, FullPath, KeyLastWriteTimestamp
} |
Sort-Object KeyLastWriteTimestampThis tells you every collected host that ever had any of the bad hashes, sorted by appraiser-seen time. Often reveals the patient zero (earliest timestamp) and the spread (subsequent timestamps on other hosts).
ProgramId pivot for tool families#
$badProgramIds = @('0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff')
Get-ChildItem -Recurse -Filter *_UnassociatedFileEntries.csv |
ForEach-Object {
$host = $_.PSChildName.Split('_')[0]
Import-Csv $_.FullName |
Where-Object { $badProgramIds -contains $_.ProgramId } |
Select-Object @{n='Host';e={$host}}, FullPath, Hash, KeyLastWriteTimestamp
}ProgramId catches family-level matches that Hash misses
(rebuilds with same metadata). See
Amcache ProgramId explained.
Path-pattern pivot#
Get-ChildItem -Recurse -Filter *_UnassociatedFileEntries.csv |
ForEach-Object {
$host = $_.PSChildName.Split('_')[0]
Import-Csv $_.FullName |
Where-Object {
$_.FullPath -match '\\AppData\\Roaming\\[a-z0-9]{8}\\update\.exe$'
} |
Select-Object @{n='Host';e={$host}}, FullPath, Hash, KeyLastWriteTimestamp
}For known attacker path patterns (a specific install location an intrusion set uses), pivot by path regex instead of hash.
The full triage flow#
A repeatable end-to-end flow:
- Collect Amcache (
Amcache.hve+.LOG1+.LOG2) from the suspect host. - Parse with
AmcacheParser.exe --mp. - Apply the baseline filter to
*_UnassociatedFileEntries.csv. - Apply false-positive controls (known portable apps, allowlist hashes).
- Triage the remainder — VirusTotal each
Hash, look at suspicious paths and names. - Confirm execution via Prefetch / 4688 / Sysmon for each confirmed-bad row.
- Time-bound the incident: take the earliest
KeyLastWriteTimestampand the latest Prefetch run time; that window is your incident timeframe. - Scope the spread via hash /
ProgramIdpivots across all collected Amcache files. - Confirm the cleanup: re-run after containment to verify the bad rows no longer have new corresponding Prefetch runs.
This flow takes hours, not days, and it works on the vast majority of commodity-malware engagements without specialised EDR.
See also#
- Amcache complete reference — the artefact in full.
- Amcache vs Prefetch — execution evidence companion.
- Amcache FileId explained — hash format for VT lookups.
- Amcache ProgramId explained — the cross-host family pivot.
- Recovering deleted-binary evidence from Amcache — when the binary itself is gone.
- Lateral movement and Amcache
ProgramIdpivoting — scoping the spread across the environment.
To triage a hive right now without installing anything, drop the 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.