{"id":195,"date":"2025-11-04T09:00:00","date_gmt":"2025-11-04T09:00:00","guid":{"rendered":"http:\/\/justruss.tech\/index.php\/2023\/05\/03\/amcache-and-shimcache-the-artefacts-attackers-forget-to-clean-up\/"},"modified":"2026-05-15T10:34:55","modified_gmt":"2026-05-15T10:34:55","slug":"amcache-and-shimcache-the-artefacts-attackers-forget-to-clean-up","status":"publish","type":"post","link":"https:\/\/justruss.tech\/index.php\/2025\/11\/04\/amcache-and-shimcache-the-artefacts-attackers-forget-to-clean-up\/","title":{"rendered":"Amcache and Shimcache: The Artefacts Attackers Forget to Clean Up"},"content":{"rendered":"<p>Amcache.hve and the Shimcache are Windows compatibility subsystem artefacts that consistently surface attacker tool execution during investigations. Attackers rarely think to clear them because they are not obviously security-relevant, and most automated cleanup scripts only target the locations that are written about in red team playbooks. These artefacts can place an attacker tool at a specific location at a specific time even after the file itself has been deleted.<\/p>\n<h3>Understanding what each artefact records<\/h3>\n<p>The Shimcache (AppCompatCache) and Amcache serve different purposes and have different forensic value. Understanding the distinction is important because confusing them leads to incorrect conclusions in investigations.<\/p>\n<pre>\/\/ SHIMCACHE:\n\/\/ Registry location: HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\AppCompatCache\n\/\/ What it records: file PRESENCE (not execution, from Vista onwards)\n\/\/ Why it exists: application compatibility layer caching\n\/\/ Key fields: file path, last modified timestamp\n\/\/ Coverage: every executable touched by the filesystem, even if not run\n\/\/ Forensic value: proves a file existed on this system at this path\n\/\/                 even if the file was deleted\n\/\/                 DOES NOT prove the file executed (Vista and later)\n\n\/\/ AMCACHE:\n\/\/ Registry location (as a hive): C:\\Windows\\AppCompat\\Programs\\Amcache.hve\n\/\/ What it records: file EXECUTION (actual program runs)\n\/\/ Why it exists: application compatibility and telemetry\n\/\/ Key fields: file path, SHA1 hash, compile timestamp, publisher, first execution time\n\/\/ Coverage: programs that actually ran on the system\n\/\/ Forensic value: proves a file executed + provides hash for identification\n\/\/                 even if the file was deleted\n\/\/                 SHA1 hash survives renaming and can identify malware<\/pre>\n<h3>Shimcache: parsing and analysis<\/h3>\n<pre>\/\/ Extract the SYSTEM hive from a live system\nreg save HKLM\\SYSTEM C:\\ir\\SYSTEM.hive \/y\n\n\/\/ Extract from a forensic image\n\/\/ Mount the image first, then copy:\ncp \/mnt\/image\/Windows\/System32\/config\/SYSTEM \/tmp\/SYSTEM.hive\n\n\/\/ Parse with AppCompatCacheParser (Eric Zimmermann toolkit)\n\/\/ Download: https:\/\/ericzimmerman.github.io\/\n\nAppCompatCacheParser.exe -f SYSTEM.hive --csv C:\\ir\\ --csvf shimcache.csv\n\n\/\/ Sample output fields:\n\/\/ ControlSet, CacheEntryPosition, Path, LastModifiedTimeUTC, Executed, Duplicate\n\n\/\/ Key analysis: find executables in unusual locations\npython3 &lt;&lt; EOF\nimport csv\nfrom datetime import datetime\n\nsuspicious_paths = []\nwith open(&quot;shimcache.csv&quot;) as f:\n    for row in csv.DictReader(f):\n        path = row.get(&quot;Path&quot;, &quot;&quot;)\n        # Flag executables outside of standard Windows locations\n        if path and not any(path.startswith(p) for p in [\n            &quot;C:\\\\Windows\\\\&quot;, &quot;C:\\\\Program Files&quot;, &quot;C:\\\\Program Files (x86)&quot;\n        ]):\n            suspicious_paths.append({\n                &quot;path&quot;: path,\n                &quot;modified&quot;: row.get(&quot;LastModifiedTimeUTC&quot;, &quot;&quot;),\n                &quot;position&quot;: row.get(&quot;CacheEntryPosition&quot;, &quot;&quot;)\n            })\n\nsuspicious_paths.sort(key=lambda x: x[&quot;modified&quot;], reverse=True)\nfor entry in suspicious_paths[:50]:\n    print(f&quot;[{entry[&#039;position&#039;]:4s}] {entry[&#039;modified&#039;]}  {entry[&#039;path&#039;]}&quot;)\nEOF<\/pre>\n<h3>Amcache: parsing and analysis<\/h3>\n<pre>\/\/ The Amcache hive cannot be copied while the system is running\n\/\/ Option 1: Copy via reg save (may not work for Amcache specifically)\n\/\/ Option 2: Use a shadow copy or VSS snapshot\n\/\/ Option 3: Extract from a forensic image\n\n\/\/ Parse with AmcacheParser (Eric Zimmermann toolkit)\nAmcacheParser.exe -f Amcache.hve --csv C:\\ir\\ --csvf amcache.csv\n\n\/\/ This produces multiple CSV files:\n\/\/ amcache_UnassociatedFileEntries.csv  - programs that ran without being installed\n\/\/ amcache_ProgramEntries.csv           - installed applications\n\/\/ amcache_DriverBinaries.csv           - driver files\n\n\/\/ The UnassociatedFileEntries is most interesting for IR\n\/\/ It contains executables that ran but were never formally installed\n\npython3 &lt;&lt; EOF\nimport csv, hashlib, subprocess\n\nprint(&quot;Checking Amcache SHA1 hashes against VirusTotal...&quot;)\nprint(&quot;(Rate limit: 4 requests\/minute for free tier)&quot;)\nprint()\n\nwith open(&quot;amcache_UnassociatedFileEntries.csv&quot;) as f:\n    for row in csv.DictReader(f):\n        sha1 = row.get(&quot;SHA1&quot;, &quot;&quot;)\n        path = row.get(&quot;FullPath&quot;, &quot;&quot;)\n        first_run = row.get(&quot;FileKeyLastWriteTimestamp&quot;, &quot;&quot;)\n\n        if not sha1:\n            continue\n\n        # Flag executables from unusual locations\n        if not any(path.startswith(p) for p in [\n            &quot;c:\\\\windows\\\\&quot;, &quot;c:\\\\program files&quot;\n        ]):\n            print(f&quot;UNUSUAL PATH: {path}&quot;)\n            print(f&quot;  First execution: {first_run}&quot;)\n            print(f&quot;  SHA1: {sha1}&quot;)\n            print(f&quot;  VT check: https:\/\/www.virustotal.com\/gui\/file\/{sha1}&quot;)\n            print()\nEOF<\/pre>\n<h3>Timeline correlation between both artefacts<\/h3>\n<pre>\/\/ Build a unified execution timeline from both sources\npython3 &lt;&lt; EOF\nimport csv\nfrom datetime import datetime\n\nevents = []\n\n# Load Shimcache entries\ntry:\n    with open(&quot;shimcache.csv&quot;) as f:\n        for row in csv.DictReader(f):\n            path = row.get(&quot;Path&quot;, &quot;&quot;)\n            ts_str = row.get(&quot;LastModifiedTimeUTC&quot;, &quot;&quot;)\n            if path and ts_str:\n                try:\n                    ts = datetime.strptime(ts_str, &quot;%Y-%m-%d %H:%M:%S&quot;)\n                    events.append({\n                        &quot;source&quot;: &quot;SHIMCACHE&quot;,\n                        &quot;time&quot;:   ts,\n                        &quot;path&quot;:   path,\n                        &quot;detail&quot;: f&quot;File modified\/touched&quot;\n                    })\n                except:\n                    pass\nexcept FileNotFoundError:\n    print(&quot;shimcache.csv not found&quot;)\n\n# Load Amcache entries\ntry:\n    with open(&quot;amcache_UnassociatedFileEntries.csv&quot;) as f:\n        for row in csv.DictReader(f):\n            path = row.get(&quot;FullPath&quot;, &quot;&quot;)\n            ts_str = row.get(&quot;FileKeyLastWriteTimestamp&quot;, &quot;&quot;)\n            sha1 = row.get(&quot;SHA1&quot;, &quot;&quot;)\n            if path and ts_str:\n                try:\n                    ts = datetime.strptime(ts_str, &quot;%Y-%m-%d %H:%M:%S&quot;)\n                    events.append({\n                        &quot;source&quot;: &quot;AMCACHE&quot;,\n                        &quot;time&quot;:   ts,\n                        &quot;path&quot;:   path,\n                        &quot;detail&quot;: f&quot;SHA1={sha1}&quot;\n                    })\n                except:\n                    pass\nexcept FileNotFoundError:\n    print(&quot;amcache_UnassociatedFileEntries.csv not found&quot;)\n\n# Define incident window\nincident_start = datetime(2023, 9, 18, 22, 0, 0)\nincident_end   = datetime(2023, 9, 19,  2, 0, 0)\n\n# Filter and sort by time\nwindow_events = [e for e in events if incident_start &lt;= e[&quot;time&quot;] &lt;= incident_end]\nwindow_events.sort(key=lambda x: x[&quot;time&quot;])\n\nprint(f&quot;Events in incident window ({incident_start} to {incident_end}):&quot;)\nprint()\nfor e in window_events:\n    # Highlight executables from suspicious locations\n    suspicious = not any(e[&quot;path&quot;].startswith(p) for p in [\n        &quot;C:\\\\Windows\\\\&quot;, &quot;c:\\\\windows\\\\&quot;,\n        &quot;C:\\\\Program Files&quot;, &quot;c:\\\\program files&quot;\n    ])\n    flag = &quot; &lt;&lt;&lt; SUSPICIOUS LOCATION&quot; if suspicious else &quot;&quot;\n    print(f&quot;[{e[&#039;source&#039;]:10s}] {e[&#039;time&#039;]}  {e[&#039;path&#039;]}{flag}&quot;)\n    if e[&quot;detail&quot;]:\n        print(f&quot;  {e[&#039;detail&#039;]}&quot;)\nEOF<\/pre>\n<h3>Practical investigation example<\/h3>\n<pre>\/\/ Scenario: Attacker ran Mimikatz from C:\\Users\\Public\\ and deleted it\n\/\/ How to find it:\n\n\/\/ 1. Shimcache shows the file existed (even if deleted)\ngrep -i \"mimikatz\\|mimi\" shimcache.csv\n\/\/ Returns: C:\\Users\\Public\\m.exe  (possibly renamed, check the hash)\n\n\/\/ 2. Amcache shows the file ran and provides the hash\ngrep -i \"Users\\\\\\\\Public\" amcache_UnassociatedFileEntries.csv\n\/\/ Returns: C:\\Users\\Public\\m.exe  SHA1=abc123...\n\/\/ Check SHA1 on VirusTotal: \"Win64\/Mimikatz.A detected by 68\/72 vendors\"\n\n\/\/ 3. Prefetch confirms the exact run times\nGet-ChildItem C:\\Windows\\Prefetch\\*.pf | Where-Object {$_.Name -match \"^M\\.EXE\"}\n\/\/ M.EXE-3A27C2B8.pf  LastWriteTime: 2023-09-18 23:28:44\n\n\/\/ The attacker deleted m.exe but the trail is still there across three artefacts\n\/\/ Shimcache proves it existed at that path\n\/\/ Amcache proves it ran and provides the hash that identifies it\n\/\/ Prefetch gives the exact execution timestamp and referenced files<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Two Windows artefacts that consistently come up in<br \/>\ninvestigations because attackers rarely think to clear them.<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-195","post","type-post","status-publish","format-standard","hentry","category-dfir"],"_links":{"self":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/195","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/comments?post=195"}],"version-history":[{"count":4,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/195\/revisions"}],"predecessor-version":[{"id":284,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/195\/revisions\/284"}],"wp:attachment":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/media?parent=195"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/categories?post=195"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/tags?post=195"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}