Leçon 5.3 · Windows pour analystes· 40 min
Le registre, les services et les tâches planifiées
Lire registre, services et tâches planifiées Windows en triage et réponse à incident, en direct ou hors ligne, et auditer les entrées de démarrage.
Objectifs
- Décrire les ruches du registre, ses clés racines, ses types de valeurs et sa vue WOW64, et localiser chaque fichier de ruche sur le disque
- Lire des clés de registre en direct avec reg query et PowerShell, et hors ligne à partir de ruches collectées avec Registry Explorer et RegRipper
- Examiner les services et les tâches planifiées : où ils sont définis, quels champs comptent et quels événements enregistrent leur création
- Auditer les emplacements de démarrage automatique courants avec Autoruns, une baseline enregistrée et la télémétrie qui consigne les modifications de chacun
- Rédiger un constat de démarrage automatique assez détaillé pour que d’autres puissent le chasser et le supprimer
Processus, threads et DLL décrivait ce qui s’exécute maintenant. Cette leçon porte sur ce que Windows a reçu l’ordre d’exécuter plus tard. Un code qui survit à un redémarrage doit être inscrit quelque part où le système d’exploitation le lira au démarrage ou à l’ouverture de session, et presque tous ces endroits se trouvent dans trois magasins de configuration : le registre, la base des services et le Planificateur de tâches. Un malware (logiciel malveillant) qui veut rester en a besoin, tout comme les logiciels légitimes, les installeurs et les administrateurs ; c’est pourquoi savoir les lire est une compétence de triage avant d’être une compétence d’analyse de malwares.
Dans Surveillance comportementale, vous avez comparé des instantanés du registre et d’Autoruns pris avant et après une exécution. Cela fonctionne dans un laboratoire que vous contrôlez. Sur un hôte réel, lors d’un incident, il n’existe pas d’instantané « avant » : vous devez reconnaître ce qui n’a rien à faire là en lisant les magasins eux-mêmes, en direct ou à partir d’une image disque.
Comment le registre est organisé
Le registre est une base de données hiérarchique. Les clés sont comme des dossiers et contiennent des sous-clés et des valeurs ; chaque valeur a un nom, un type et des données. Chaque clé porte aussi un horodatage de dernière écriture, mis à jour dès que la clé ou l’une de ses valeurs change. Les valeurs n’ont pas d’horodatage propre : l’heure de la clé est donc ce qui s’approche le plus de « quand cette entrée Run a-t-elle été ajoutée ? ».
Ruches et clés racines
Sur le disque, le registre est découpé en fichiers de ruche (hive). Les
clés racines que vous voyez dans regedit sont des vues sur ces ruches :
| Clé racine | Contenu | Fichier sous-jacent |
|---|---|---|
HKLM\SYSTEM | Jeux de contrôle : services, pilotes, configuration de démarrage | %SystemRoot%\System32\config\SYSTEM |
HKLM\SOFTWARE | Paramètres logiciels de la machine, clés Run machine, cache des tâches | %SystemRoot%\System32\config\SOFTWARE |
HKLM\SAM, HKLM\SECURITY | Comptes locaux, secrets et stratégie LSA (lisibles uniquement par SYSTEM) | config\SAM, config\SECURITY |
HKU\.DEFAULT | Profil utilisé par LocalSystem | config\DEFAULT |
HKU\<SID> | Un profil utilisateur chargé | %USERPROFILE%\NTUSER.DAT |
HKU\<SID>_Classes | Paramètres COM et associations de fichiers de cet utilisateur | %LOCALAPPDATA%\Microsoft\Windows\UsrClass.dat |
Trois clés racines sont des liens, pas des magasins. HKCU pointe vers le
HKU\<SID> de celui qui interroge, si bien que le même reg query HKCU\...
renvoie des résultats différents selon l’utilisateur, et qu’un service tournant
en SYSTEM voit HKU\S-1-5-18. HKCR est une vue fusionnée de
HKLM\SOFTWARE\Classes et de la clé des classes de l’utilisateur, les entrées de
l’utilisateur l’emportant. HKCC est un raccourci vers le profil matériel
courant.
Dans HKLM\SYSTEM, CurrentControlSet n’existe que sur un système en cours
d’exécution ; c’est un lien vers l’une des clés numérotées ControlSet00n. Dans
une ruche SYSTEM hors ligne, lisez la valeur Select\Current pour savoir quel
jeu de contrôle était utilisé.
À côté de chaque fichier de ruche se trouvent des journaux de transactions
(.LOG1, .LOG2). Une ruche copiée depuis un système en marche ou qui a planté
peut être « sale » : les modifications récentes sont encore dans les journaux.
Les outils hors ligne qui rejouent les journaux vous montrent l’état le plus
récent ; ceux qui ne le font pas peuvent manquer précisément l’entrée que vous
cherchez.
Types de valeurs
| Type | Contenu | Où vous le rencontrez |
|---|---|---|
REG_SZ | Chaîne | Valeurs Run, ObjectName d’un service |
REG_EXPAND_SZ | Chaîne dont les %VARIABLES% sont développées à la lecture | ImagePath, ServiceDll |
REG_MULTI_SZ | Liste de chaînes | Dépendances de services, groupes svchost |
REG_DWORD / REG_QWORD | Entier 32/64 bits | Start, Type |
REG_BINARY | Octets bruts | Configuration encodée, parfois des charges utiles entières |
Un gros REG_BINARY ou une chaîne anormalement longue mérite un second regard :
les familles fileless (sans fichier) conservent dans des valeurs leur
configuration chiffrée, voire du code.
La vue WOW64
Sur Windows 64 bits, les programmes 32 bits voient un HKLM\SOFTWARE redirigé :
leurs lectures et écritures vont dans HKLM\SOFTWARE\WOW6432Node. Un échantillon
32 bits qui définit une valeur Run à l’échelle de la machine atterrit donc sous
HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run, et un analyste
qui ne vérifie que le chemin 64 bits le manque. HKCU\Software n’est pas
redirigé. reg.exe accepte /reg:64 ou /reg:32 pour choisir explicitement la
vue ; Autoruns et les parseurs hors ligne montrent les deux.
Lire le registre
En direct
Pour le triage, préférez des commandes dont vous pouvez coller la sortie dans vos
notes plutôt que de naviguer dans regedit :
# One key, all values
reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
# The 32-bit view of the same key
reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /reg:32
# Recursive, e.g. every value under a suspicious vendor key
reg query "HKCU\Software\SomeVendor" /s
# PowerShell exposes keys as a drive
Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run'
# Other users: HKCU only shows yours, so walk HKU for loaded profiles
Get-ChildItem Registry::HKEY_USERS | ForEach-Object {
Get-ItemProperty "Registry::$($_.Name)\Software\Microsoft\Windows\CurrentVersion\Run" -ErrorAction SilentlyContinue
}Seuls les profils des utilisateurs actuellement connectés (ou ayant des processus
en cours) sont chargés sous HKU. Le NTUSER.DAT de tous les autres est un
fichier sur le disque, ce qui est l’une des raisons pour lesquelles l’analyse hors
ligne compte, même sur un hôte en fonctionnement.
Pour préserver les preuves plutôt que simplement les lire, reg save HKLM\SYSTEM C:\ir\SYSTEM.hiv
écrit un fichier de ruche depuis une invite élevée, et reg export écrit un fichier texte .reg. Les frameworks de collecte comme KAPE
ou Velociraptor copient les fichiers de ruche verrouillés avec leurs journaux de
transactions.
Hors ligne
Pour des ruches collectées ou une image disque montée, utilisez des outils qui analysent directement le format de fichier au lieu de charger les ruches dans votre propre registre :
- Registry Explorer (Eric Zimmerman) ouvre les fichiers de ruche, rejoue les journaux de transactions sales, affiche les heures de dernière écriture des clés et les clés supprimées récupérées dans l’espace libre, et fournit des favoris pour les artefacts courants. Son jumeau en ligne de commande, RECmd, exécute des fichiers batch de clés à extraire sur de nombreuses ruches.
- RegRipper exécute des plugins qui savent où se trouvent des artefacts
précis et les affiche sous forme de rapport.
rip.exe -r NTUSER.DAT -aexécute tous les plugins pour ce type de ruche ; les noms des plugins varient d’une version à l’autre, listez-les donc avec-l.
Analyser le fichier déjoue aussi les astuces qui trompent l’API Win32, comme le
masquage de clés de registre par octet nul :
un nom de valeur que regedit ne peut pas ouvrir reste une cellule ordinaire
dans la ruche.
Astuce : Ne chargez jamais, en première étape, des ruches issues d’une preuve dans le registre de votre propre poste avec File → Load Hive. Cela exige un fichier accessible en écriture, peut le modifier et lie votre analyse à votre propre machine. Travaillez sur une copie hachée avec un parseur.
Services
Un service est un programme démarré et supervisé par le Service Control
Manager (SCM), qui s’exécute sous le nom services.exe. Les services démarrent
avant toute ouverture de session, redémarrent en cas d’échec s’ils sont
configurés pour cela, et tournent généralement sous un compte privilégié ; c’est
pourquoi la persistance par service
attire les attaquants qui disposent déjà de droits d’administration.
Chaque service et chaque pilote est une sous-clé de
HKLM\SYSTEM\CurrentControlSet\Services :
| Valeur | Signification | À vérifier |
|---|---|---|
ImagePath | Ligne de commande exécutée par le SCM (ou chemin du pilote) | Emplacement, guillemets, arguments |
Start | 0 boot, 1 system (pilotes), 2 automatique, 3 manuel, 4 désactivé | Les services automatiques sont ceux qui assurent la persistance |
Type | 0x1 pilote noyau, 0x2 pilote de système de fichiers, 0x10 processus propre, 0x20 processus partagé | Un nouveau pilote noyau est un constat bien plus grave |
ObjectName | Compte : LocalSystem, NT AUTHORITY\LocalService, NT AUTHORITY\NetworkService, NT SERVICE\<name>, ou un utilisateur | Privilèges de ce qui s’exécute |
Parameters\ServiceDll | DLL chargée par svchost.exe pour les services en processus partagé | Chemin du code réel |
FailureActions | Que faire quand le service plante, y compris exécuter une commande | Une action « redémarrer » qui exécute autre chose |
De nombreux services Windows sont des DLL hébergées par svchost.exe. Leur
ImagePath vaut %SystemRoot%\system32\svchost.exe -k <group>, ce qui paraît
identique pour des dizaines de services ; le code réellement exécuté est désigné
par ServiceDll, et les groupes sont listés sous
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost. Un service dont
l’ImagePath semble normal mais dont le ServiceDll pointe hors de System32
est une cachette classique.
Examinez les services avec des commandes qui affichent les champs ci-dessus :
sc.exe qc Dnscache # config: binary path, start type, account
sc.exe qdescription Dnscache
Get-CimInstance Win32_Service |
Select-Object Name, StartMode, State, StartName, PathName |
Sort-Object PathName | Format-Table -AutoSizeTrier par PathName fait ressortir les cas étranges. Surveillez aussi les
chemins sans guillemets contenant des espaces :
C:\Program Files\Vendor Tools\sync agent\syncsvc.exe sans guillemets amène
Windows à essayer d’abord C:\Program.exe puis C:\Program Files\Vendor.exe.
C’est un bug de l’éditeur et une porte ouverte à l’élévation de privilèges, pas
une preuve de compromission, mais cela a sa place dans votre rapport.
Lorsqu’un service est installé, le SCM écrit l’événement System 7045 (« A
service was installed in the system ») avec le nom, le chemin de l’image, le type
de démarrage et le compte. Si Audit Security System Extension est activé, le
journal Security reçoit aussi un 4697 indiquant le sujet qui l’a installé.
Les événements Sysmon 12/13 consignent les écritures de registre sous Services
si votre configuration surveille ce chemin.
Tâches planifiées
Le Planificateur de tâches (le service Schedule, hébergé dans svchost.exe)
exécute des actions sur des déclencheurs : à l’ouverture de session, au
démarrage, sur minuterie, sur un événement, en cas d’inactivité. Les tâches
peuvent s’exécuter en SYSTEM, sous un utilisateur ou avec les privilèges les plus
élevés, et constituent le mécanisme de persistance vers lequel de nombreux
administrateurs, installeurs et attaquants se tournent en premier
(persistance par tâche planifiée).
Une tâche enregistrée existe à deux endroits :
- Une définition XML dans
C:\Windows\System32\Tasks\, un fichier par tâche, avec des dossiers qui reflètent le chemin de la tâche. Elle contient les déclencheurs, les actions, le principal ainsi que la date et l’auteur de l’enregistrement. - Un cache dans le registre sous
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache:Tree\<path>associe le nom à un GUID, etTasks\{GUID}stocke les actions, les déclencheurs et les hashs qu’utilise le service.
Les deux doivent concorder. Des attaquants ont caché des tâches à schtasks et
à l’interface graphique en supprimant la valeur SD (descripteur de sécurité)
sous la clé Tree de la tâche ; la tâche s’exécute toujours. Hors ligne,
comparez entre eux le dossier XML, Tree et Tasks.
schtasks /query /fo LIST /v
Get-ScheduledTask | Where-Object TaskPath -notlike '\Microsoft\*' |
Select-Object TaskPath, TaskName, State,
@{n='Action'; e={$_.Actions.Execute + ' ' + $_.Actions.Arguments}},
@{n='RunAs'; e={$_.Principal.UserId}}La création d’une tâche laisse plusieurs traces. Le journal
Microsoft-Windows-TaskScheduler/Operational enregistre l’enregistrement (106),
les mises à jour (140), la suppression (141) et chaque exécution (200 action
démarrée, 201 terminée) ; vérifiez qu’il est activé sur votre parc, car il a été
désactivé par défaut sur certaines versions de Windows. Avec Audit Other Object
Access Events activé, le journal Security enregistre 4698 (création, avec le
XML complet de la tâche), 4699 (suppression), 4700/4701 (activation/désactivation)
et 4702 (mise à jour). Sysmon 11 détecte l’apparition du fichier XML sous
System32\Tasks, et 12/13 les clés TaskCache, lorsque la configuration les
couvre.
Où regarder : une table de démarrage automatique pour le défenseur
Un catalogue complet des techniques de persistance est l’objet de Mécanismes de persistance. Pour le triage, commencez par les emplacements ci-dessous, et sachez quelle télémétrie aurait enregistré une modification :
| Emplacement | Chemin | Modification enregistrée par |
|---|---|---|
| Clés Run / RunOnce | HKLM et HKCU\Software\Microsoft\Windows\CurrentVersion\Run, RunOnce, plus WOW6432Node | Sysmon 12/13/14 ; heure de dernière écriture de la clé |
| Services et pilotes | HKLM\SYSTEM\CurrentControlSet\Services | System 7045, Security 4697, Sysmon 12/13 |
| Tâches planifiées | System32\Tasks, Schedule\TaskCache | Security 4698–4702, TaskScheduler Operational 106/140/141, Sysmon 11/12/13 |
| Dossiers de démarrage | %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup, %ProgramData%\...\StartUp | Sysmon 11 (création de fichier) |
| Valeurs Winlogon | HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon Userinit, Shell | Sysmon 13 |
| Abonnements WMI | Filtres, consommateurs et liaisons de root\subscription ; wbem\Repository hors ligne | Sysmon 19/20/21, WMI-Activity Operational 5861 |
Pour Winlogon, les données attendues sont courtes et bien connues : Userinit
vaut C:\Windows\system32\userinit.exe, et Shell vaut explorer.exe. Tout ce
qui est ajouté à la suite est un constat. Pour WMI, interrogez en direct avec
Get-CimInstance -Namespace root\subscription -ClassName __EventFilter (ainsi que
CommandLineEventConsumer, ActiveScriptEventConsumer,
__FilterToConsumerBinding) ; un poste de travail sain en a très peu.
Autoruns couvre la table
Autoruns énumère tous ces emplacements et bien d’autres (extensions de l’Explorateur, assistants de navigateur, codecs, fournisseurs LSA, moniteurs d’impression, Image File Execution Options), rattache chaque entrée à un fichier et peut vérifier sa signature. Trois réglages en font un outil de triage plutôt qu’un mur de texte :
- Options → Hide Microsoft Entries (et Hide Windows Entries) masque les entrées signées du système d’exploitation. Activez d’abord Verify Code Signatures, sinon le filtre se fie au nom d’éditeur que revendique le fichier.
- File → Save écrit un fichier
.arn; File → Compare avec un fichier enregistré met en vert les nouvelles entrées. - File → Analyze Offline System fait pointer Autoruns vers le répertoire
Windows d’une image montée et vers un profil utilisateur ;
autorunsc -zfait la même chose en ligne de commande.
Sa version console produit du CSV pour le scripting et la collecte sur le parc :
autorunsc64 -accepteula -a * -c -h -s -m (toutes les catégories, CSV, hashs,
vérification des signatures, masquage des entrées signées par Microsoft).
Astuce : La vérification VirusTotal d’Autoruns envoie les hashs des fichiers à un tiers. C’est généralement acceptable ; soumettre des fichiers inconnus ne l’est pas pendant un incident. Vérifiez votre politique avant de l’activer.
Baseline et comparaison
La liste de démarrage automatique d’un seul hôte est difficile à juger isolément. Deux comparaisons facilitent la tâche :
- Par rapport à une baseline. Exécutez
autorunscsur votre image de référence et après les mises à jour majeures, conservez le CSV et comparez-y les nouvelles collectes. Tout ce qui est absent de l’image doit avoir un responsable : un déploiement logiciel, un administrateur, ou personne. - À l’échelle du parc. Collectez le même CSV sur de nombreux hôtes et comptez la fréquence de chaque entrée (emplacement, nom, chemin, hash). Une entrée présente sur 4 000 machines est probablement votre logiciel ; une entrée présente sur deux machines, c’est par là qu’il faut commencer. Cette revue par « fréquence d’occurrence la plus faible » fonctionne tout aussi bien pour les services et les tâches.
Construisez ensuite une chronologie : les heures de dernière écriture des clés, les dates d’enregistrement des tâches, les événements 7045 et 4698 et les horodatages du binaire cible doivent concorder. Quand ce n’est pas le cas, demandez-vous pourquoi ; les horodatages peuvent être falsifiés, et une activité légitime réécrit certaines clés.
Ce qu’il faut mettre dans un rapport
Pour chaque constat de démarrage automatique, consignez assez d’éléments pour que quelqu’un d’autre puisse le retrouver sur un autre hôte et le supprimer proprement :
| Champ | Exemple |
|---|---|
| Emplacement | HKCU\Software\Microsoft\Windows\CurrentVersion\Run, valeur WindowsUpdateCheck |
| Données, exactement telles que stockées | C:\Users\lab\AppData\Roaming\x7q\upd.exe |
| Portée | Quel utilisateur (SID) ou toute la machine ; vue 32 ou 64 bits |
| Fichier cible | SHA-256, état de la signature, taille, horodatages du fichier |
| Chronologie | Heure de dernière écriture de la clé, événements de création (7045, 4698, Sysmon 13) |
| Compte et privilèges | Service LocalSystem, tâche exécutée avec les privilèges les plus élevés, ouverture de session utilisateur |
| Statut vis-à-vis de la baseline | Absent de l’image de référence ; vu sur N hôtes |
Normalisez les chemins propres à l’utilisateur (%APPDATA%\x7q\upd.exe) pour la
chasse, mais gardez la valeur littérale dans les preuves. La structure de
rapport de triage du module 3 comporte une section
pour les indicateurs d’hôte ; les entrées de démarrage automatique y ont leur
place. Les consignes de suppression doivent nommer à la fois l’entrée et le
fichier, car supprimer l’un laisse souvent l’autre en mesure de le restaurer.
Lab : trier un export de démarrage automatique
Vous allez construire un petit export .reg contenant des valeurs Run et des clés
de services, pour la plupart ordinaires, ainsi qu’un script Python qui signale
les entrées à l’aide d’heuristiques simples. Rien dans ce lab ne crée d’entrée de
démarrage automatique sur un quelconque système : le « registre » est un fichier
texte.
-
Préparez un dossier de travail et un environnement virtuel (seule la bibliothèque standard est nécessaire) :
bash mkdir m5r && cd m5r python3 -m venv .venv .venv/bin/python --versiontext Python 3.14.7 -
Créez l’export d’exemple.
regeditetreg exportécrivent du texte UTF-16LE avec une marque d’ordre des octets (BOM), et stockent les donnéesREG_EXPAND_SZsous forme d’octetshex(2):plutôt que de texte lisible. Ce générateur reproduit les deux, de sorte que le parseur doit faire face à ce à quoi ressemblent les vrais exports :python # make_sample.py - writes sample-autoruns.reg the way regedit exports it (UTF-16LE with BOM). # REG_EXPAND_SZ data is exported as hex(2): little-endian UTF-16 bytes plus a terminating NUL. def hex2(s): b = (s + "\0").encode("utf-16-le") return "hex(2):" + ",".join(f"{x:02x}" for x in b) def wrap(line, width=78): # regedit wraps long hex data with a trailing backslash and two-space indent out, cur = [], line while len(cur) > width: cut = cur.rfind(",", 0, width) + 1 out.append(cur[:cut] + "\\") cur = " " + cur[cut:] out.append(cur) return "\r\n".join(out) R = r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" U = r"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run" S = r"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services" lines = ["Windows Registry Editor Version 5.00", "", f"[{R}]", wrap('"SecurityHealth"=' + hex2(r"%windir%\system32\SecurityHealthSystray.exe")), r'"VendorTray"="\"C:\\Program Files\\Vendor Tools\\tray.exe\" /minimized"', "", f"[{U}]", r'"OneDrive"="\"C:\\Users\\lab\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe\" /background"', r'"WindowsUpdateCheck"="C:\\Users\\lab\\AppData\\Roaming\\x7q\\upd.exe"', "", f"[{S}\\Dnscache]", wrap('"ImagePath"=' + hex2(r"%SystemRoot%\system32\svchost.exe -k NetworkService -p")), '"Start"=dword:00000002', '"Type"=dword:00000020', r'"ObjectName"="NT AUTHORITY\\NetworkService"', "", f"[{S}\\Dnscache\\Parameters]", wrap('"ServiceDll"=' + hex2(r"%SystemRoot%\System32\dnsrslvr.dll")), "", f"[{S}\\Spooler]", wrap('"ImagePath"=' + hex2(r"%SystemRoot%\System32\spoolsv.exe")), '"Start"=dword:00000002', '"Type"=dword:00000010', '"ObjectName"="LocalSystem"', "", f"[{S}\\AcmeBackup]", r'"ImagePath"="\"C:\\Program Files\\Acme\\Backup\\acmebk.exe\" -service"', '"Start"=dword:00000003', '"Type"=dword:00000010', '"ObjectName"="LocalSystem"', "", f"[{S}\\VendorSync]", r'"ImagePath"="C:\\Program Files\\Vendor Tools\\sync agent\\syncsvc.exe"', '"Start"=dword:00000002', '"Type"=dword:00000010', '"ObjectName"="LocalSystem"', "", ] with open("sample-autoruns.reg", "wb") as f: f.write(("\ufeff" + "\r\n".join(lines) + "\r\n").encode("utf-16-le"))bash .venv/bin/python make_sample.py file sample-autoruns.reg iconv -f UTF-16 -t UTF-8 sample-autoruns.reg | tr -d '\r' | head -12text sample-autoruns.reg: Windows Registry little-endian text (Win2K or above) Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run] "SecurityHealth"=hex(2):25,00,77,00,69,00,6e,00,64,00,69,00,72,00,25,00,5c,00,\ 73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,53,00,65,00,63,00,75,\ 00,72,00,69,00,74,00,79,00,48,00,65,00,61,00,6c,00,74,00,68,00,53,00,79,00,\ 73,00,74,00,72,00,61,00,79,00,2e,00,65,00,78,00,65,00,00,00 "VendorTray"="\"C:\\Program Files\\Vendor Tools\\tray.exe\" /minimized" [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run] "OneDrive"="\"C:\\Users\\lab\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe\" /background" "WindowsUpdateCheck"="C:\\Users\\lab\\AppData\\Roaming\\x7q\\upd.exe"Notez qu’un simple
grepsurSecurityHealth.exeéchouerait doublement sur ce fichier : le texte est en UTF-16, et le chemin lui-même est encodé en hexadécimal. -
Écrivez le script de triage. Il décode l’export, collecte les valeurs Run/RunOnce et les données
ImagePath/ServiceDlldes services, et applique cinq heuristiques : emplacement accessible en écriture par l’utilisateur, binaire interpréteur ou proxy, extension inattendue, chemin sans guillemets contenant des espaces, et nom à consonance d’éditeur pointant hors des répertoires Windows et Program Files.python # triage_reg.py - flag autostart entries in a regedit .reg export by simple heuristics. import re, sys USER_WRITABLE = re.compile(r"\\users\\|%appdata%|%localappdata%|%temp%|\\programdata\\|\\windows\\temp\\", re.I) INTERPRETERS = {"cmd.exe", "powershell.exe", "pwsh.exe", "wscript.exe", "cscript.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe"} EXPECTED_EXT = {".exe", ".dll", ".sys"} TRUSTED_DIRS = re.compile(r"^(%systemroot%|%windir%|c:\\windows|c:\\program files)", re.I) VENDOR_WORDS = re.compile(r"windows|microsoft|update|defender", re.I) def read_reg(path): raw = open(path, "rb").read() text = raw.decode("utf-16") if raw[:2] in (b"\xff\xfe", b"\xfe\xff") else raw.decode("utf-8-sig") return re.sub(r"\\\r?\n\s*", "", text) # join wrapped hex lines def decode(data): if data.startswith('"'): return data[1:-1].replace('\\"', '"').replace("\\\\", "\\"), "REG_SZ" if data.startswith("dword:"): return int(data[6:], 16), "REG_DWORD" if data.startswith("hex(2):"): b = bytes(int(x, 16) for x in data[7:].split(",")) return b.decode("utf-16-le").rstrip("\0"), "REG_EXPAND_SZ" return data, "OTHER" def parse(text): keys, key = {}, None for line in text.splitlines(): if line.startswith("[") and line.endswith("]"): key = keys.setdefault(line[1:-1], {}) elif key is not None and (m := re.match(r'^"((?:[^"\\]|\\.)*)"=(.*)$', line)): key[m.group(1)] = decode(m.group(2)) return keys def split_command(cmd): """Return (executable path, was it quoted?) the way CreateProcess would read it.""" cmd = cmd.strip() if cmd.startswith('"'): return cmd[1:cmd.index('"', 1)], True m = re.match(r"^(.+?\.(exe|dll|sys|bat|cmd|ps1|vbs|js|hta|scr|com))\b", cmd, re.I) return (m.group(1) if m else cmd.split(" ")[0]), False def check(name, command): exe, quoted = split_command(command) base = exe.lower().rsplit("\\", 1)[-1] ext = "." + base.rsplit(".", 1)[-1] if "." in base else "" flags = [] if USER_WRITABLE.search(command): # whole line: catches scripts passed to interpreters flags.append("user-writable path") if base in INTERPRETERS: flags.append(f"interpreter/proxy ({base})") elif ext not in EXPECTED_EXT: flags.append(f"unusual extension ({ext or 'none'})") if not quoted and " " in exe: flags.append("unquoted path with spaces") if VENDOR_WORDS.search(name) and not TRUSTED_DIRS.match(exe): flags.append("vendor-like name outside Windows/Program Files") return exe, flags START = {0: "boot", 1: "system", 2: "auto", 3: "demand", 4: "disabled"} HIVE = {"HKEY_LOCAL_MACHINE": "HKLM", "HKEY_CURRENT_USER": "HKCU", "HKEY_USERS": "HKU"} def entries(keys): """Yield (location, name, command, details, [paths to check]).""" for key, values in keys.items(): hive, _, rest = key.partition("\\") last = rest.rsplit("\\", 1)[-1] if last.lower() in ("run", "runonce"): for name, (data, _) in values.items(): yield f"{HIVE.get(hive, hive)} {last}", name, data, "", [data] elif "\\services\\" in key.lower() and "ImagePath" in values: image = values["ImagePath"][0] start = START.get(values.get("Start", (None,))[0], "?") details = f"start={start} account={values.get('ObjectName', ('?',))[0]}" checks = [image] dll = keys.get(key + "\\Parameters", {}).get("ServiceDll") if dll: details += f" ServiceDll={dll[0]}" checks.append(dll[0]) yield "Service", last, image, details, checks def main(path): for where, name, command, details, checks in entries(parse(read_reg(path))): flags = [f for c in checks for f in check(name, c)[1]] level = "HIGH" if len(flags) >= 2 else "LOW " if flags else "ok " print(f"[{level}] {where} {name}: {command}") if details: print(f" {details}") for f in flags: print(f" - {f}") if __name__ == "__main__": main(sys.argv[1]) -
Exécutez-le sur l’export :
bash .venv/bin/python triage_reg.py sample-autoruns.regtext [ok ] HKLM Run SecurityHealth: %windir%\system32\SecurityHealthSystray.exe [ok ] HKLM Run VendorTray: "C:\Program Files\Vendor Tools\tray.exe" /minimized [LOW ] HKCU Run OneDrive: "C:\Users\lab\AppData\Local\Microsoft\OneDrive\OneDrive.exe" /background - user-writable path [HIGH] HKCU Run WindowsUpdateCheck: C:\Users\lab\AppData\Roaming\x7q\upd.exe - user-writable path - vendor-like name outside Windows/Program Files [ok ] Service Dnscache: %SystemRoot%\system32\svchost.exe -k NetworkService -p start=auto account=NT AUTHORITY\NetworkService ServiceDll=%SystemRoot%\System32\dnsrslvr.dll [ok ] Service Spooler: %SystemRoot%\System32\spoolsv.exe start=auto account=LocalSystem [ok ] Service AcmeBackup: "C:\Program Files\Acme\Backup\acmebk.exe" -service start=demand account=LocalSystem [LOW ] Service VendorSync: C:\Program Files\Vendor Tools\sync agent\syncsvc.exe start=auto account=LocalSystem - unquoted path with spacesLisez les trois lignes signalées différemment. OneDrive s’installe par conception pour chaque utilisateur sous
AppData\Local: un signal faible, expliqué par un produit connu.VendorSyncest un constat d’hygiène pour l’éditeur.WindowsUpdateCheckcombine un dossier au nom aléatoire, accessible en écriture par l’utilisateur, sousRoaming, avec un nom emprunté à Windows Update : c’est l’entrée sur laquelle vous enquêteriez en premier. -
Testez les autres heuristiques. Enregistrez ceci sous
extra.regen UTF-8 simple (le script accepte les deux encodages) et exécutez-le :text Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce] "Cleanup"="wscript.exe //B C:\\ProgramData\\q\\a.js" "Helper"="C:\\Users\\lab\\AppData\\Local\\Temp\\h.dat"bash .venv/bin/python triage_reg.py extra.regtext [HIGH] HKCU RunOnce Cleanup: wscript.exe //B C:\ProgramData\q\a.js - user-writable path - interpreter/proxy (wscript.exe) [HIGH] HKCU RunOnce Helper: C:\Users\lab\AppData\Local\Temp\h.dat - user-writable path - unusual extension (.dat) -
Faites de même sous Windows avec
reg. Dans une VM d’analyse, exportez les clés que vous voulez examiner et interrogez-les directement :powershell reg export "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" C:\ir\hkcu-run.reg /y reg export "HKLM\SYSTEM\CurrentControlSet\Services" C:\ir\services.reg /y reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /reg:32 reg query "HKLM\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters" /v ServiceDllExécutez
triage_reg.pysur les exports et expliquez chaque ligne signalée. -
Faites-le avec Autoruns. Lancez Autoruns en tant qu’administrateur. Activez Options → Verify Code Signatures, puis Hide Microsoft Entries et Hide Windows Entries, et appuyez sur F5. Enregistrez le résultat sous
baseline.arnsur un instantané sain. Plus tard, sur la même VM après avoir installé un logiciel quelconque, rouvrez Autoruns et utilisez File → Compare avecbaseline.arn: les nouvelles entrées sont mises en évidence. Consultez les onglets Logon, Services et Scheduled Tasks pour le logiciel installé. -
Recoupez avec la télémétrie. Pour le logiciel que vous avez installé, cherchez l’événement System 7045 s’il a ajouté un service, et le journal TaskScheduler Operational (événement 106) s’il a enregistré une tâche. Notez lesquels vous auriez manqués avec votre politique d’audit actuelle.
Questions : Pourquoi le script doit-il décoder les données hex(2) avant que
la moindre vérification de chemin puisse fonctionner, et qu’aurait manqué un
grep sur l’export brut ? Quelle heuristique se déclencherait sur une
installation légitime de Chrome ou de Teams par utilisateur, et comment un
comptage de fréquence sur l’ensemble du parc vous aiderait-il à l’écarter ?
Comment étendriez-vous le script pour lire la clé Run de WOW6432Node et les
actions des tâches planifiées ? Pour l’entrée WindowsUpdateCheck, listez les
preuves que vous collecteriez ensuite et les événements qui pourraient dater sa
création.
À retenir
- Le registre est un ensemble de fichiers de ruche ;
HKCUetHKCRsont des vues, et sur Windows 64 bits la vue 32 bitsWOW6432Nodedoit être vérifiée séparément. - Les heures de dernière écriture des clés sont vos horodatages du registre ; les valeurs n’en ont pas. Les parseurs hors ligne qui rejouent les journaux de transactions montrent l’état le plus récent et peuvent voir ce que l’API Win32 cache.
- Pour les services, lisez
ImagePath,Start,Type,ObjectNameet, pour les services hébergés par svchost,ServiceDll; l’installation laisse un System 7045 et, si l’audit est activé, un Security 4697. - Les tâches planifiées existent à la fois sous forme de XML dans
System32\Taskset dans les clés de registreTaskCache; leur création est journalisée dans Security 4698 et dans le journal TaskScheduler Operational lorsque ceux-ci sont activés. - Autoruns, avec la vérification des signatures, les entrées Microsoft masquées et une baseline enregistrée, transforme une revue du démarrage automatique en une courte liste ; les comptages de fréquence sur l’ensemble du parc la raccourcissent encore.
- Rapportez les constats de démarrage automatique avec l’emplacement exact, les données, la portée, le hash du fichier cible, la chronologie et le statut vis-à-vis de la baseline, afin que d’autres puissent les chasser et les supprimer.