Amcache ProgramId explained: the 44-character application identity
The ProgramId field in Root\InventoryApplicationFile and Root\InventoryApplication is Amcache's logical application identity. A 44-character hex string. Deterministic: the same application on a different host typically gets the same value. That makes it one of the most useful cross-host pivots in the entire Windows DFIR toolkit.
This page is the full reference. What the value is, how Windows constructs it, how to use it on a single host, and how to pivot it across an environment.
For broader context, see the Amcache complete reference. For the surrounding registry layout, see Amcache registry structure.
What it looks like#
A typical ProgramId:
0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff
44 hex characters, no prefix, no delimiters. Every entry in InventoryApplicationFile carries one. Every entry in InventoryApplication is keyed by one. The two link via the shared value: a file's ProgramId tells you which installed application it belongs to.
Structure of the 44 characters#
Not a single uniform hash. The value encodes several components:
- A type / version tag (the leading few characters).
- A hash of the application's identifying attributes (primarily name, publisher, version, language).
- A trailing discriminator (often
0000ffffor similar) Windows uses for internal classification.
The exact construction has shifted across Windows builds and is not fully documented by Microsoft. The practical rules:
- Two records with the same
ProgramIddescribe the same application. ProgramIdis stable across hosts for the same install. Office 365 on host A and the same Office 365 build on host B share aProgramId.ProgramIdis not stable across major version upgrades. Upgrading the application typically changes it.
Using ProgramId on a single host#
The headline use is joining a file to its application record.
File → application#
You found a suspicious row in *_UnassociatedFileEntries.csv. Its ProgramId is 0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff.
Look up the same ProgramId in *_AssociatedFileEntries.csv and in the parent application catalogue (Root\InventoryApplication\<ProgramId>, or the equivalent CSV your parser emits). You get:
- The application's display name and publisher.
- The install date.
- The install source (sometimes a download URL).
- The full list of files associated with that application.
This is invaluable when an attacker drops a tool that masquerades as a legitimate application. The filename and metadata might say "AdobeReaderUpdater.exe / Adobe Inc.", but its ProgramId either matches no installed Adobe product (suspicious) or matches a different application than its name suggests (very suspicious).
Application → files#
Reverse pivot, equally useful. You found a suspicious application record in InventoryApplication. Perhaps a portable build of something with empty publisher and an unusual install source. Take its ProgramId and list every file that shares it:
$pid = '0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff'
Import-Csv .\HOST_amcache_UnassociatedFileEntries.csv |
Where-Object { $_.ProgramId -eq $pid } |
Select FullPath, Hash, KeyLastWriteTimestamp
Import-Csv .\HOST_amcache_AssociatedFileEntries.csv |
Where-Object { $_.ProgramId -eq $pid } |
Select FullPath, Hash, KeyLastWriteTimestampYou get the full file footprint of that application as the appraiser saw it. Every EXE, DLL, and PE tied back to the application. For attacker tooling, this is often the quickest way to enumerate the full set of dropped binaries from a single observed file.
Cross-host pivoting#
This is where ProgramId shines as a hunt primitive. Because the identity is stable across hosts for the same install, a single suspicious ProgramId on one host becomes a query against every other host's Amcache:
$pid = '0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff'
Get-ChildItem -Recurse -Filter *_UnassociatedFileEntries.csv |
ForEach-Object {
$rows = Import-Csv $_.FullName |
Where-Object { $_.ProgramId -eq $pid }
if ($rows) {
$host = $_.PSChildName.Split('_')[0]
foreach ($r in $rows) {
[pscustomobject]@{
Host = $host
Path = $r.FullPath
Hash = $r.Hash
When = $r.KeyLastWriteTimestamp
}
}
}
} |
Sort-Object WhenThis is the lateral-movement pivot. A suspicious ProgramId on one host becomes "every host that has ever had this application, with paths and times". Tighter signal than hash-only pivots, because ProgramId survives small variations in the binary (re-signing, recompilation with the same name/publisher/version).
For the full lateral-movement playbook, see Lateral movement and Amcache ProgramId pivoting.
When ProgramId is the right pivot#
Same family, multiple builds#
An attacker compiles their tool multiple times with the same name, publisher, and version metadata. Only the binary changes. Hashes differ. ProgramId stays the same. Hashing alone misses the family. ProgramId catches it.
Renamed binaries#
mimikatz.exe → svchost64.exe → update.exe. Filename changes. Embedded PE metadata is the same. If the attacker did not bother scrubbing the version-info resource, ProgramId follows the binary across the renames.
Re-deployed tools across an environment#
The attacker drops the same tool on 20 hosts as they move laterally. Hashes may match, paths usually do not. ProgramId is the most consistent identifier across all 20.
When ProgramId is the wrong pivot#
Tools with rotating metadata#
If the attacker recompiles with different name/publisher/version each time, every build gets a different ProgramId. Fall back to file-content Hash because at least one component of the build is identical.
Living-off-the-land binaries#
net.exe, psexec.exe, certutil.exe. Every host that has run them has the same ProgramId. Matches are meaningless. Pivot on command line (4688 / Sysmon 1) or on the path the LOLBIN was executed from instead.
Truly novel binaries#
A file appearing for the very first time anywhere has a ProgramId no other host shares. Without a corpus to compare against, ProgramId does no work. Hash plus Publisher = '' AND IsPeFile = True AND FullPath under \Users\ is the working filter.
Common confusions#
Two things ProgramId is not.
Not a hash of the binary#
Hash (or FileId) is the content hash. ProgramId is an application-identity hash, computed from metadata. Two completely different binaries with identical metadata share a ProgramId. The same binary recompiled with different metadata gets a different ProgramId.
Not a unique-per-host identifier#
Common mistake: assuming ProgramId identifies a specific install. It does not. If a user installs Notepad++ on five hosts, those five hosts share one ProgramId for Notepad++. To distinguish installs, you need install dates, sources, or paths in addition to ProgramId.
Where ProgramId lives in AmcacheParser#
AmcacheParser exposes ProgramId in every category CSV that has it:
| CSV | ProgramId use |
|---|---|
*_UnassociatedFileEntries.csv |
The application a file claims to belong to (even if the parent application record is missing). |
*_AssociatedFileEntries.csv |
The application the file is genuinely associated with. |
*_ProgramEntries.csv |
The application's own identity (legacy schema). |
*_ShortcutEntries.csv |
The application the shortcut points at. |
For pivots, the standard pattern is: identify a suspicious row in UnassociatedFileEntries, take its ProgramId, run the query patterns above.
Further reading#
- Yogesh Khatri's Amcache deep dive for the original schema mapping.
- Maxim Suhanov's writeups on the appraiser's identity computation.
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.
- What is Amcache ProgramId? (glossary)
ProgramId is the 44-character application-identity hash Amcache stores. Stable across hosts for the same install. Catches re-compiles and renames where Hash misses.
- 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.