ProgramId Amcache expliqué : l'identité applicative de 44 caractères

Le champ ProgramId dans Root\InventoryApplicationFile et Root\InventoryApplication est l'identité applicative logique d'Amcache. C'est une chaîne hexadécimale de 44 caractères qui identifie de manière unique une application — et, surtout, elle est déterministe : la même application sur un hôte différent obtient généralement le même ProgramId. Cela en fait l'un des pivots cross-hôtes les plus utiles de toute la trousse à outils DFIR Windows.

Cette page est la référence complète : ce qu'est la valeur, comment Windows la construit, comment l'utiliser dans une enquête mono-hôte, et comment la pivoter dans un environnement lors d'un hunt.

Pour le contexte Amcache plus large, voir la référence complète Amcache ; pour la structure de registre environnante, voir Structure du registre Amcache.


À quoi ressemble la valeur#

Un ProgramId typique issu d'une ruche réelle :

0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff

44 caractères hexadécimaux, sans préfixe, sans délimiteurs. Chaque entrée dans InventoryApplicationFile en porte un ; chaque entrée dans InventoryApplication est indexée par un. Les deux se lient via la valeur partagée : le ProgramId d'un fichier vous dit à quelle application installée il appartient.

Structure des 44 caractères#

La valeur n'est pas un hash uniforme unique. Elle encode plusieurs composants, en gros :

  • Une étiquette de type / version (les premiers caractères).
  • Un hash des attributs identifiants de l'application — principalement nom, éditeur, version et langue.
  • Un discriminateur final (souvent 0000ffff ou similaire) utilisé par Windows pour une classification interne.

La construction exacte a évolué selon les builds Windows et n'est pas entièrement documentée par Microsoft. Pour les besoins DFIR, les règles pratiques sont :

  • Deux enregistrements avec le même ProgramId concernent la même application.
  • Le ProgramId est stable entre hôtes pour le même install d'application — Office 365 sur l'hôte A et le même build Office 365 sur l'hôte B partagent un ProgramId.
  • Le ProgramId n'est pas stable entre mises à niveau majeures — la mise à niveau de l'application change généralement son ProgramId.

Utiliser ProgramId sur un hôte unique#

L'usage phare est joindre un fichier à son enregistrement applicatif.

Fichier → application#

Vous avez trouvé une ligne suspecte dans *_UnassociatedFileEntries.csv. Son ProgramId est 0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff.

Cherchez le même ProgramId dans *_AssociatedFileEntries.csv et dans le catalogue d'application parent (la clé du registre Root\InventoryApplication\<ProgramId>, ou le CSV équivalent si votre analyseur en émet un). Vous obtenez typiquement :

  • Le nom d'affichage et l'éditeur de l'application.
  • La date d'installation.
  • La source d'installation (parfois une URL de téléchargement).
  • La liste complète des fichiers associés à cette application.

C'est inestimable quand un attaquant dépose un outil qui se fait passer pour une application légitime. Le nom de fichier et les métadonnées peuvent dire « AdobeReaderUpdater.exe / Adobe Inc. », mais son ProgramId ne correspond à aucun produit Adobe installé (suspect) ou correspond à une application différente de ce que son nom suggère (très suspect).

Application → fichiers#

Le pivot inverse est tout aussi utile. Vous avez trouvé un enregistrement applicatif suspect dans InventoryApplication — peut-être un build portable de quelque chose avec un éditeur "" et une source d'installation inhabituelle. Prenez son ProgramId et listez chaque fichier qui le partage :

$pid = '0006fa0b2a9f8a4eb9d7c81e8b1f3c5d3e2a0000ffff'
 
Import-Csv .\HOST_amcache_UnassociatedFileEntries.csv |
  Where-Object { $_.ProgramId -eq $pid } |
  Select-Object FullPath, Hash, KeyLastWriteTimestamp
 
Import-Csv .\HOST_amcache_AssociatedFileEntries.csv |
  Where-Object { $_.ProgramId -eq $pid } |
  Select-Object FullPath, Hash, KeyLastWriteTimestamp

Vous obtenez l'empreinte fichier complète de cette application telle que l'appraiser l'a vue — chaque EXE, DLL et PE que l'appraiser a rattaché à cette application. Pour l'outillage attaquant, c'est souvent le moyen le plus rapide d'énumérer l'ensemble complet des binaires déposés à partir d'un seul fichier observé.


Pivot cross-hôtes avec ProgramId#

C'est là que ProgramId brille comme primitive de hunt. Parce que l'identité est stable entre hôtes pour la même application, un seul ProgramId suspect sur un hôte devient une requête que vous pouvez exécuter contre l'Amcache de chaque autre hôte :

$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 When

C'est le pivot de mouvement latéral. Si un seul hôte a un ProgramId suspect, exécuter cette requête sur vos CSVs Amcache collectés vous dit chaque autre hôte sur lequel la même application est apparue, avec chemins et heures — typiquement un signal plus serré que les pivots par hash seul, car le ProgramId survit aux petites variations du binaire (re-signing, recompilation avec les mêmes nom/éditeur/version).

Pour le playbook complet de mouvement latéral, voir Mouvement latéral et pivot avec le ProgramId Amcache.


Quand ProgramId est le plus utile#

Quelques patterns d'enquête où ProgramId est le bon pivot :

Même famille, builds multiples#

Un attaquant peut compiler son outil plusieurs fois avec les mêmes métadonnées de nom, éditeur et version — seul le binaire change. Les hashes diffèrent. Le ProgramId est le même. Le hash seul rate la famille ; ProgramId l'attrape.

Binaires renommés#

mimikatz.exesvchost64.exeupdate.exe. Le nom de fichier change ; les métadonnées PE embarquées sont les mêmes. Si l'attaquant ne s'est pas embêté à nettoyer la ressource version-info, le ProgramId reste le même.

Outils redéployés dans un environnement#

L'attaquant dépose le même outil sur 20 hôtes alors qu'il se déplace latéralement. Les hashes peuvent correspondre, les chemins généralement pas. Le ProgramId est l'identifiant le plus cohérent sur les 20 hôtes.


Quand ProgramId est le mauvais pivot#

Quelques cas où ProgramId sous-performe :

Outils à métadonnées tournantes#

Si l'attaquant recompile avec des nom/éditeur/version différents à chaque fois, chaque build obtient un ProgramId différent. Pour ceux-là, le hash de contenu du fichier (le SHA-1 dans Hash) est le meilleur pivot cross-hôtes, car au moins un composant du build est identique.

Binaires living-off-the-land#

net.exe, psexec.exe, certutil.exe — outils légitimes détournés. Chaque hôte qui a exécuté l'un d'entre eux a le même ProgramId pour eux. Les correspondances ProgramId sont essentiellement dénuées de sens ici. Pivotez plutôt sur la ligne de commande (4688 / Sysmon 1) ou sur le chemin depuis lequel le LOLBIN a été exécuté.

Binaires véritablement inédits#

Un fichier apparaissant pour la toute première fois n'importe où a un ProgramId qu'aucun autre hôte ne partage. Sans corpus contre lequel comparer, ProgramId ne fait rien ; hash plus Publisher = '' et IsPeFile = True et FullPath sous \Users\ est le filtre de triage qui fonctionne.


Confusions courantes#

Deux choses que ProgramId n'est pas :

Pas un hash du binaire#

Hash (ou FileId) est le hash de contenu. ProgramId est un hash d'identité applicative, calculé à partir des métadonnées, pas des octets du binaire. Deux binaires complètement différents avec des métadonnées identiques obtiennent le même ProgramId ; le même binaire recompilé avec des métadonnées différentes obtient un ProgramId différent.

Pas un identifiant unique par hôte#

Une erreur fréquente est de supposer que ProgramId identifie un install spécifique. Ce n'est pas le cas. Si un utilisateur installe Notepad++ sur cinq hôtes, ces cinq hôtes partagent un seul ProgramId pour Notepad++. Pour distinguer les installs, vous avez besoin des dates d'installation, des sources ou des chemins en plus du ProgramId.


Où vit ProgramId dans la sortie d'AmcacheParser#

AmcacheParser expose ProgramId dans chaque CSV de catégorie qui l'a :

CSV Usage de ProgramId
*_UnassociatedFileEntries.csv L'application à laquelle un fichier prétend appartenir (même si l'enregistrement applicatif parent est manquant).
*_AssociatedFileEntries.csv L'application à laquelle le fichier est véritablement associé.
*_ProgramEntries.csv L'identité propre de l'application (schéma legacy).
*_ShortcutEntries.csv L'application sur laquelle pointe le raccourci.

Pour les pivots, le pattern standard est : identifier une ligne suspecte dans UnassociatedFileEntries, prendre son ProgramId, et exécuter les patterns de requête ci-dessus.


Voir aussi#

Pour explorer les valeurs ProgramId sur votre propre ruche sans rien installer, déposez un fichier sur la page d'accueil de l'analyseur — il ne quitte jamais votre navigateur.

Articles liés

Retour à tous les articles