Skip to content

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é racineContenuFichier sous-jacent
HKLM\SYSTEMJeux de contrôle : services, pilotes, configuration de démarrage%SystemRoot%\System32\config\SYSTEM
HKLM\SOFTWAREParamètres logiciels de la machine, clés Run machine, cache des tâches%SystemRoot%\System32\config\SOFTWARE
HKLM\SAM, HKLM\SECURITYComptes locaux, secrets et stratégie LSA (lisibles uniquement par SYSTEM)config\SAM, config\SECURITY
HKU\.DEFAULTProfil utilisé par LocalSystemconfig\DEFAULT
HKU\<SID>Un profil utilisateur chargé%USERPROFILE%\NTUSER.DAT
HKU\<SID>_ClassesParamè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

TypeContenuOù vous le rencontrez
REG_SZChaîneValeurs Run, ObjectName d’un service
REG_EXPAND_SZChaîne dont les %VARIABLES% sont développées à la lectureImagePath, ServiceDll
REG_MULTI_SZListe de chaînesDépendances de services, groupes svchost
REG_DWORD / REG_QWORDEntier 32/64 bitsStart, Type
REG_BINARYOctets brutsConfiguration 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 :

powershell
# 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 -a exé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 :

ValeurSignificationÀ vérifier
ImagePathLigne de commande exécutée par le SCM (ou chemin du pilote)Emplacement, guillemets, arguments
Start0 boot, 1 system (pilotes), 2 automatique, 3 manuel, 4 désactivéLes services automatiques sont ceux qui assurent la persistance
Type0x1 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
ObjectNameCompte : LocalSystem, NT AUTHORITY\LocalService, NT AUTHORITY\NetworkService, NT SERVICE\<name>, ou un utilisateurPrivilèges de ce qui s’exécute
Parameters\ServiceDllDLL chargée par svchost.exe pour les services en processus partagéChemin du code réel
FailureActionsQue faire quand le service plante, y compris exécuter une commandeUne 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 :

powershell
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 -AutoSize

Trier 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, et Tasks\{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.

powershell
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 :

EmplacementCheminModification enregistrée par
Clés Run / RunOnceHKLM et HKCU\Software\Microsoft\Windows\CurrentVersion\Run, RunOnce, plus WOW6432NodeSysmon 12/13/14 ; heure de dernière écriture de la clé
Services et pilotesHKLM\SYSTEM\CurrentControlSet\ServicesSystem 7045, Security 4697, Sysmon 12/13
Tâches planifiéesSystem32\Tasks, Schedule\TaskCacheSecurity 4698–4702, TaskScheduler Operational 106/140/141, Sysmon 11/12/13
Dossiers de démarrage%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup, %ProgramData%\...\StartUpSysmon 11 (création de fichier)
Valeurs WinlogonHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon Userinit, ShellSysmon 13
Abonnements WMIFiltres, consommateurs et liaisons de root\subscription ; wbem\Repository hors ligneSysmon 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 -z fait 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 autorunsc sur 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 :

ChampExemple
EmplacementHKCU\Software\Microsoft\Windows\CurrentVersion\Run, valeur WindowsUpdateCheck
Données, exactement telles que stockéesC:\Users\lab\AppData\Roaming\x7q\upd.exe
PortéeQuel utilisateur (SID) ou toute la machine ; vue 32 ou 64 bits
Fichier cibleSHA-256, état de la signature, taille, horodatages du fichier
ChronologieHeure de dernière écriture de la clé, événements de création (7045, 4698, Sysmon 13)
Compte et privilègesService LocalSystem, tâche exécutée avec les privilèges les plus élevés, ouverture de session utilisateur
Statut vis-à-vis de la baselineAbsent 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.

  1. 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 --version
    text
    Python 3.14.7
  2. Créez l’export d’exemple. regedit et reg export écrivent du texte UTF-16LE avec une marque d’ordre des octets (BOM), et stockent les données REG_EXPAND_SZ sous forme d’octets hex(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 -12
    text
    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 grep sur SecurityHealth.exe échouerait doublement sur ce fichier : le texte est en UTF-16, et le chemin lui-même est encodé en hexadécimal.

  3. Écrivez le script de triage. Il décode l’export, collecte les valeurs Run/RunOnce et les données ImagePath/ServiceDll des 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])
  4. Exécutez-le sur l’export :

    bash
    .venv/bin/python triage_reg.py sample-autoruns.reg
    text
    [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 spaces

    Lisez 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. VendorSync est un constat d’hygiène pour l’éditeur. WindowsUpdateCheck combine un dossier au nom aléatoire, accessible en écriture par l’utilisateur, sous Roaming, avec un nom emprunté à Windows Update : c’est l’entrée sur laquelle vous enquêteriez en premier.

  5. Testez les autres heuristiques. Enregistrez ceci sous extra.reg en 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.reg
    text
    [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)
  6. 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 ServiceDll

    Exécutez triage_reg.py sur les exports et expliquez chaque ligne signalée.

  7. 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.arn sur un instantané sain. Plus tard, sur la même VM après avoir installé un logiciel quelconque, rouvrez Autoruns et utilisez File → Compare avec baseline.arn : les nouvelles entrées sont mises en évidence. Consultez les onglets Logon, Services et Scheduled Tasks pour le logiciel installé.

  8. 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 ; HKCU et HKCR sont des vues, et sur Windows 64 bits la vue 32 bits WOW6432Node doit ê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, ObjectName et, 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\Tasks et dans les clés de registre TaskCache ; 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.