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:

  1. It records the SHA-1 of every PE the appraiser saw. That hash goes straight into a VirusTotal or TI-feed query.
  2. 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.
  3. 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 -Descending

What 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 LinkDate

Look 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 KeyLastWriteTimestamp

This 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:

  1. Collect Amcache (Amcache.hve + .LOG1 + .LOG2) from the suspect host.
  2. Parse with AmcacheParser.exe --mp.
  3. Apply the baseline filter to *_UnassociatedFileEntries.csv.
  4. Apply false-positive controls (known portable apps, allowlist hashes).
  5. Triage the remainder — VirusTotal each Hash, look at suspicious paths and names.
  6. Confirm execution via Prefetch / 4688 / Sysmon for each confirmed-bad row.
  7. Time-bound the incident: take the earliest KeyLastWriteTimestamp and the latest Prefetch run time; that window is your incident timeframe.
  8. Scope the spread via hash / ProgramId pivots across all collected Amcache files.
  9. 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#

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

Back to all posts