Récupérer une persistance WMI supprimée dans OBJECTS.DATA
Pourquoi filtres, consommateurs et liaisons WMI supprimés subsistent dans OBJECTS.DATA : parcours d'index, carving, python-cim et PyWMIPersistenceFinder.
En bref. Supprimer un abonnement WMI ne l'efface pas. Ses octets restent dans OBJECTS.DATA jusqu'à ce que WMI réutilise l'espace. Parcourir l'index (l'approche de python-cim) donne ce qui est actif ; balayer l'intégralité du fichier (comme le fait PyWMIPersistenceFinder de David Pany, sur le texte) retrouve aussi ce qui a été supprimé. Utilisez les deux, et notez quelle méthode a trouvé quoi.
Pourquoi les objets supprimés subsistent
Le dépôt est un stockage organisé en pages. Lorsqu'une instance est supprimée ou réécrite, WMI met à jour INDEX.BTR et écrit une nouvelle génération du fichier MAPPING. L'ancien enregistrement n'est pas mis à zéro :
- si sa page entière est libérée, la page devient non mappée : plus référencée, mais ses octets restent intacts ;
- si d'autres enregistrements restent dans la page, l'espace de l'enregistrement devient du slack (espace résiduel) au sein d'une page toujours utilisée.
Dans les deux cas, l'enregistrement conserve son en-tête (le SHA-256 UTF-16 du nom de sa classe) et son tas de chaînes : la requête, la ligne de commande, le script, les références de la liaison. Tant qu'aucune autre écriture ne l'écrase, il reste récupérable. Cette durée dépend de l'activité de WMI sur l'hôte : procédez tôt à la collecte.
Deux façons de lire un dépôt
Lecture structurée : suivre l'index
C'est ainsi que fonctionne python-cim, et c'est ce que fait Windows. Les clés d'INDEX.BTR telles que NS_…/CI_<hash("__EVENTFILTER")>/IL_….page.record.length pointent, via le fichier MAPPING courant, vers un enregistrement. Les définitions de classe donnent à chaque propriété son nom, son type et son emplacement : chaque valeur est donc décodée exactement, y compris les valeurs par défaut de la classe et l'espace de noms de chaque objet.
Cette méthode ne voit que ce que l'index référence : les objets actifs.
Carving : balayer chaque octet
Deux variantes :
- Carving d'enregistrements. Rechercher dans
OBJECTS.DATAles hachages des noms de classe__EventFilter,__FilterToConsumerBindinget des classes de consommateurs. Chaque occurrence marque le début d'un enregistrement d'instance, actif ou non. On le décode avec la structure de la classe, puis on vérifie sa cohérence : le tas de l'enregistrement doit se terminer exactement là où l'indique son en-tête, et commencer par le nom de la classe. - Carving de chaînes. L'idée de PyWMIPersistenceFinder : chercher
__FilterToConsumerBindingsuivi de…EventConsumer.Name="…"et__EventFilter.Name="…", puis la requête du filtre et la commande du consommateur à proximité de leurs noms. Aucune structure n'est nécessaire, ce qui fonctionne encore lorsque l'en-tête d'un enregistrement a été écrasé, au prix d'une part de déduction sur le rôle de chaque chaîne.
Le carving voit tout, mais seul, il ne peut pas dire si une occurrence est active. Ce verdict vient du fichier MAPPING : une occurrence dans une page mappée, à l'intérieur d'une entrée de la table des matières, est utilisée ; une occurrence dans une page non mappée ou dans le slack est libérée.
Comment WMI Parser combine les deux
WMI Parser parcourt l'index lorsque INDEX.BTR et un fichier MAPPING sont présents, puis effectue toujours le carving de l'intégralité du fichier, et fusionne les résultats. Chaque objet indique comment il a été trouvé :
| Libellé | Signification |
|---|---|
| Index | Atteint via INDEX.BTR et le mappage courant : actif |
| Enregistrement extrait | En-tête d'enregistrement trouvé par balayage ; décodé avec la structure de la classe |
| Correspondance texte | Seul le texte de la liaison a subsisté (à la manière de PyWMIPersistenceFinder) |
Une copie carvée identique à un objet actif est fusionnée avec lui comme emplacement supplémentaire. Une copie carvée différente est listée séparément comme Récupéré, et si un objet actif possède la même clé, elle est marquée comme version antérieure. Le dépôt d'exemple illustre les trois cas : un filtre d'ouverture de session supprimé et sa liaison, trouvés par carving d'enregistrements puis à nouveau sous forme de fragment sans en-tête dans le slack d'une page ; et une version antérieure d'un consommateur qui exécutait une commande PowerShell encodée avant d'être modifié en C:\ProgramData\Intel\m64.exe -svc.
Lire les objets récupérés avec prudence
- Pas d'espace de noms. Les enregistrements ne stockent pas leur espace de noms ; seul l'index le connaît. Une liaison récupérée peut avoir résidé dans
root\subscriptionou ailleurs. - Enregistrements fragmentés. Un enregistrement plus long que sa page se poursuit sur la page logique suivante, qui peut se trouver n'importe où physiquement. Une fois libérés, les morceaux sont difficiles à réassembler ; WMI Parser signale alors une lecture partielle.
- Structures de classe. Sans les définitions de classe du dépôt (si seul
OBJECTS.DATAest fourni), le décodage repose sur la structure intégrée des classes Windows standard et sur les vérifications de cohérence ci-dessus. Les classes de consommateurs personnalisées ne sont alors trouvées que par le texte de leur liaison. - Horodatage. Les en-têtes d'instance contiennent deux FILETIME non documentés. Ils suivent souvent l'heure d'écriture de cette copie, ce qui peut aider à ordonner les versions, mais ce ne sont pas des dates de création documentées.
Une liaison récupérée prouve que l'abonnement a existé à un moment donné, pas qu'il s'est exécuté. Recherchez des traces d'exécution de la charge utile du consommateur (Prefetch, Amcache, journaux d'événements) ainsi que les événements WMI-Activity décrits dans la checklist d'investigation.
FAQ
Peut-on récupérer un abonnement WMI supprimé ?
Souvent, oui. La suppression d'un objet libère sa page ou l'espace de son enregistrement dans OBJECTS.DATA sans l'effacer : le filtre, le consommateur ou la liaison peut donc être extrait par carving tant que WMI ne réutilise pas cet espace.
Quelle différence entre python-cim et PyWMIPersistenceFinder ?
python-cim parcourt INDEX.BTR et le fichier MAPPING pour décoder les objets actifs à l'aide de la structure de leur classe. PyWMIPersistenceFinder recherche le texte des liaisons dans OBJECTS.DATA : il n'a besoin que de ce seul fichier et peut atteindre des enregistrements supprimés, mais il décode moins d'informations.
Comment savoir si un objet récupéré a été supprimé ou s'il s'agit d'une version antérieure ?
Si un objet actif possède la même clé avec un contenu différent, la copie récupérée est très probablement une version antérieure. Si aucun objet actif ne possède cette clé, l'objet a été supprimé.