Skip to content

Leçon 7.4 · Comportements malveillants· 45 min

Vol d’identifiants et keylogging

Reconnaître infostealers, accès à LSASS et keyloggers dans les échantillons et la télémétrie, vérifier les contrôles et cerner les comptes exposés.

Objectifs

  • Expliquer pourquoi le vol d’identifiants élargit un incident et comment il s’inscrit dans la tactique Credential Access d’ATT&CK
  • Reconnaître les stealers de navigateur, l’accès à LSASS et aux ruches du registre, et les keyloggers à partir de leurs imports, de leurs chaînes et de leurs motifs d’accès aux processus
  • Détecter l’accès aux identifiants avec les masques d’accès de l’événement Sysmon 10, l’audit Security et l’audit des accès aux fichiers, et ajuster ces règles en toute sécurité
  • Vérifier au niveau de la configuration Credential Guard, la protection LSA et les règles de réduction de la surface d’attaque
  • Délimiter les comptes exposés, réinitialiser et révoquer dans le bon ordre, et chasser la réutilisation des identifiants volés

Mécanismes de persistance se terminait sur un avertissement : supprimer chaque point d’ancrage ne revient pas à évincer un intrus, car les identifiants qu’il a collectés fonctionnent toujours. Cette leçon porte sur cette seconde moitié. Vous apprendrez à reconnaître le vol d’identifiants dans un échantillon avant de l’exécuter, à le voir dans la télémétrie de l’hôte au moment où il se produit, à vérifier les contrôles qui auraient dû l’arrêter, et à transformer un vol confirmé en une liste de comptes à réinitialiser, de sessions à révoquer et d’ouvertures de session à chasser.

Le vol d’identifiants correspond à la tactique ATT&CK TA0006, Credential Access. Cette leçon reste du côté du défenseur : chaque comportement est décrit par ce qu’un analyste ou un capteur observe, et non par la façon dont il est réalisé.

Pourquoi le vol d’identifiants change l’incident

Un échantillon qui ne s’exécute que sur un hôte est un problème d’hôte. Un échantillon qui vole des identifiants est un problème d’identité, et les problèmes d’identité ne restent pas sur une seule machine. Trois conséquences découlent de la confirmation d’un vol :

  • Le périmètre s’étend à tous les systèmes que les comptes volés peuvent atteindre. Le hash du mot de passe d’un administrateur de domaine récupéré sur un poste de travail ouvre des serveurs que le malware (logiciel malveillant) n’a jamais touchés. Le mouvement latéral avec du matériel volé (pass-the-hash et pass-the-ticket, ATT&CK T1550) ressemble à des ouvertures de session normales : le seul moyen fiable de le borner est donc de savoir quels comptes ont été exposés.
  • Le nettoyage doit inclure des réinitialisations, et celles-ci ont un ordre. Réinitialiser un mot de passe alors que l’intrus a encore un point d’ancrage lui livre le nouveau. Réinitialiser le mot de passe d’un utilisateur ne met pas fin à une session de navigateur dont le cookie a été volé.
  • Les jetons survivent aux mots de passe. Les cookies de session, les jetons de rafraîchissement et les tickets Kerberos restent valides jusqu’à leur expiration ou leur révocation. Un stealer qui a exporté des cookies peut permettre à l’attaquant de contourner entièrement la MFA (T1539, puis T1550.004) : révoquer les sessions est donc aussi important que changer les mots de passe.

De nombreux stealers sont des programmes one-shot sans aucune persistance : ils collectent, envoient et se terminent. C’est pourquoi la question de la persistance vue dans la leçon précédente a une question jumelle dans chaque rapport : qu’est-ce que cet échantillon a pu prendre, et à qui ?

Les familles que vous rencontrerez

Les familles ci-dessous se chevauchent en pratique (de nombreux infostealers prennent aussi des captures d’écran et lisent le presse-papiers), mais chacune laisse une trace distincte.

FamilleCe qu’elle cibleIndices statiquesTélémétrieATT&CK
Stealers de navigateurs et d’applicationsIdentifiants enregistrés, cookies, saisie automatique, fichiers de portefeuilles et de clients de messagerieChemins de profils et noms de fichiers sous forme de chaînes ; code ou chaînes SQLite ; CryptUnprotectData ; BCryptDecryptLectures de fichiers sous les profils de navigateur (Security 4663 avec une SACL, événements fichier de l’EDR) ; copie d’une base verrouillée écrite dans un dossier temporaireT1555.003, T1539, T1552.001
Magasins d’identifiants WindowsGestionnaire d’identifiants, entrées du coffreCredEnumerateW, fonctions de vaultcli.dll comme VaultEnumerateItemsTélémétrie d’API de l’EDR ; lectures sous AppData\...\Microsoft\CredentialsT1555.004
Accès à LSASSHashs, tickets et secrets détenus par la Local Security AuthorityOpenProcess avec MiniDumpWriteDump, ReadProcessMemory, ou manipulation de SeDebugPrivilege ; lsass sous forme de chaîne, souvent encodéeSysmon 10 avec TargetImage lsass.exe ; Security 4656 ; nouveau fichier de dump (Sysmon 11) ; événements de blocage ASRT1003.001
Ruches du registreHashs des comptes locaux (SAM), secrets LSA, ouvertures de session de domaine en cacheRegSaveKeyExW ; chaînes SAM, SECURITY, SYSTEM ; ligne de commande reg.exe avec un verbe saveSysmon 1 pour reg.exe ; Sysmon 11 pour des fichiers de la taille d’une ruche dans des chemins temporaires ; accès aux clichés instantanésT1003.002, T1003.004, T1003.005
KeyloggersTout ce qui est tapé, avec la fenêtre dans laquelle c’est tapéGetAsyncKeyState ou GetKeyboardState dans une boucle ; SetWindowsHookExW avec un hook clavier de bas niveau ; RegisterRawInputDevices ; GetForegroundWindow avec GetWindowTextWTélémétrie de hooks et d’API de l’EDR ; fichier journal qui grossit ; envois périodiquesT1056.001
Capture du presse-papiers et de l’écranMots de passe copiés, adresses de portefeuilles, documents visiblesOpenClipboard, GetClipboardData, AddClipboardFormatListener ; GetDC, BitBlt, encodeurs d’images GDI+Fichiers image apparaissant dans des dossiers temporaires ; événements de capture d’écran de l’EDRT1115, T1113

Les stealers se lisent comme une liste de courses

Les infostealers courants sont généralement les plus faciles à reconnaître dans les chaînes, une fois celles-ci décodées. Ils transportent de longues listes d’emplacements cibles : dossiers de profils de navigateur se terminant par User Data, noms de fichiers comme Login Data, Cookies, Web Data et Local State, les logins.json et key4.db de Firefox, fichiers de configuration de clients FTP, dossiers de session de clients de messagerie et répertoires de portefeuilles de cryptomonnaie. À côté des chemins, on trouve souvent des fragments SQL nommant les colonnes d’une table de logins, des clés JSON comme encrypted_key, et une bibliothèque SQLite liée statiquement. L’import qui relie le tout est CryptUnprotectData de crypt32.dll, l’appel DPAPI qui déballe les secrets protégés pour l’utilisateur courant, souvent suivi de BCryptDecrypt ou d’une routine AES embarquée. Comme un navigateur en cours d’exécution garde ses bases verrouillées, les stealers les copient souvent d’abord dans un dossier temporaire, et certains délèguent la copie à un utilitaire signé comme esentutl.exe ; la copie est elle-même un événement de création de fichier sur lequel vous pouvez alerter.

Deux mises en garde gardent cette lecture honnête. D’abord, ces chaînes sont précisément ce que les auteurs chiffrent : un stealer dont la liste de chaînes paraît propre n’a rien d’inhabituel ; décodez d’abord, comme dans Scripter le déchiffrement des chaînes. Ensuite, de nombreux stealers sont écrits en .NET ou sous forme de scripts, et les preuves se trouvent alors dans les métadonnées et le source plutôt que dans l’IAT ; les leçons Analyser un malware .NET et Malwares PowerShell, JavaScript et VBScript expliquent où chercher.

L’accès à LSASS est une question d’accès aux processus

lsass.exe détient le matériel d’authentification de toutes les personnes connectées à la machine. Le lire exige d’ouvrir le processus avec des droits qui autorisent la lecture de sa mémoire, ce qui en fait un problème d’accès aux processus plus qu’un problème d’API. Processus, threads et DLL a montré qu’un handle vers un autre processus est un prérequis pour toucher à sa mémoire ; pour LSASS, ce sont les droits de ce handle qui constituent le signal.

Dans un échantillon, cherchez lsass sous forme de chaîne (ou d’une forme encodée) à proximité d’une énumération de processus, d’OpenProcess, d’un ajustement de privilège pour SeDebugPrivilege, et soit de MiniDumpWriteDump issu de dbghelp.dll ou de dbgcore.dll, soit de lectures directes de mémoire. De nombreux outils évitent entièrement la table des imports grâce à la résolution dynamique ou au hachage d’API, et certains détournent des utilitaires légitimes signés pour produire le dump, ce qui déplace les preuves des imports de l’échantillon vers une ligne de commande.

Les keyloggers utilisent des API de saisie ordinaires

Les API de saisie sont les plus difficiles à juger isolément, car les jeux, les outils d’accessibilité, les utilitaires de raccourcis clavier et les logiciels d’assistance à distance les appellent aussi. Regardez la combinaison et la boucle, pas l’import isolé :

  • Polling : GetAsyncKeyState ou GetKeyState appelée sur toute la plage des codes de touches virtuelles dans une boucle à court sommeil, généralement avec MapVirtualKeyW ou ToUnicode pour convertir les codes en caractères.
  • Hooking : SetWindowsHookExW avec le type de hook clavier de bas niveau (WH_KEYBOARD_LL, valeur 13) et une boucle de messages autour de GetMessageW. La page SetWindowsHookEx traite de l’usage de la même API pour l’injection.
  • Raw input : RegisterRawInputDevices et GetRawInputData sur une fenêtre cachée.
  • Contexte et exfiltration : GetForegroundWindow et GetWindowTextW pour étiqueter les frappes par fenêtre, un motif d’ajout à un fichier, et une capacité réseau ou de messagerie pour envoyer le journal.

Un binaire qui interroge une seule touche pour gérer un raccourci est normal. Un binaire qui interroge toutes les touches, enregistre le titre de la fenêtre, écrit dans un fichier caché et dispose d’une capacité d’envoi est un keylogger. Le lab ci-dessous montre le peu qu’un import de saisie isolé vous apprend à lui seul.

Sources de détection

Événement Sysmon 10 : accès aux processus

Sysmon 10 enregistre l’ouverture d’un processus par un autre, avec SourceImage, TargetImage, GrantedAccess (le masque d’accès effectivement accordé) et CallTrace (la pile d’appels des modules à l’origine de la demande). Le collecter pour chaque processus coûte cher : la plupart des configurations n’incluent donc que des cibles sensibles comme lsass.exe.

Le masque est le cœur de la règle. Les droits qui comptent pour LSASS sont :

DroitValeurPourquoi il compte
PROCESS_VM_READ0x0010Lire la mémoire du processus
PROCESS_VM_WRITE, PROCESS_VM_OPERATION0x0020, 0x0008Modifier la mémoire ou changer les protections
PROCESS_CREATE_THREAD0x0002Exécuter du code dans le processus
PROCESS_DUP_HANDLE0x0040En copier des handles
PROCESS_QUERY_LIMITED_INFORMATION0x1000Nom, chemin, code de sortie : inoffensif

Des masques comme 0x1000 et 0x1400 (droits de requête uniquement) sont du bruit de fond provenant des services système et des outils de supervision. Les masques qui combinent un droit de requête avec 0x0010, comme 0x1010 ou 0x1410, sont le motif classique de lecture mémoire, et 0x1fffff correspond à l’accès complet. Testez des bits, pas des valeurs littérales : les attaquants font varier le masque pour esquiver les règles qui correspondent à une seule chaîne. Un CallTrace contenant UNKNOWN signifie qu’une partie de la pile d’appels s’est exécutée depuis une mémoire non adossée à un fichier sur disque, ce qui est un signe fort de code injecté ou chargé de manière réflective.

Audit Security et EDR

Avec la sous-catégorie d’audit Kernel Object activée, Windows peut journaliser les demandes de handle Security 4656 visant lsass.exe ; les builds récents fournissent à cet effet une liste de contrôle d’accès système (SACL) sur le processus : vérifiez donc le volume sur un pilote avant de l’activer sur tout le parc. Security 4663 enregistre les accès aux fichiers dotés d’une SACL, ce qui permet d’auditer les lectures des fichiers de profil de navigateur : ajoutez une entrée d’audit pour Read data sur les dossiers User Data d’un groupe pilote et excluez le navigateur lui-même. Security 4624 et 4648 comptent plus tard, pour chasser la réutilisation. Les produits EDR ajoutent ce que Windows ne journalise pas nativement : des événements au niveau des API pour les lectures mémoire, l’installation de hooks et la capture d’écran, et ils bloquent généralement d’emblée le motif LSASS ; leurs alertes sont aussi des sources de détection.

Ajustement

Chaque source rencontre ici des logiciels légitimes. Les antivirus et les agents EDR ouvrent LSASS avec tous les droits ; les outils de sauvegarde et de synchronisation lisent les profils de navigateur ; les gestionnaires de mots de passe lisent le presse-papiers. Ajustez avec des exceptions qui fixent le chemin complet de l’image et, si vos données le permettent, le signataire ou le hash issu de l’événement Sysmon 1 du processus. N’ajustez jamais sur le seul nom de fichier : un stealer nommé MsMpEng.exe qui s’exécute depuis un dossier temporaire doit toujours déclencher l’alerte. Les exceptions doivent expirer et être revues, car les produits qu’elles couvrent se mettent à jour et changent d’emplacement.

Astuce : Une règle LSASS qui ne se déclenche jamais est plus probablement cassée que simplement silencieuse. Vérifiez que votre configuration Sysmon inclut bien l’événement 10 pour lsass.exe, et rejouez un accès bénin connu (la requête d’un agent de supervision) pour valider la chaîne de bout en bout.

Contrôles préventifs à vérifier

Lorsqu’une alerte de vol d’identifiants arrive, l’une des premières questions est de savoir si les contrôles qui auraient dû le limiter étaient en place. Vérifiez-les hôte par hôte, comme des faits de configuration à consigner dans le rapport :

ContrôleÀ vérifierCe qu’il change
Credential Guard(Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard).SecurityServicesRunning inclut 1Isole les hashs NTLM et les secrets Kerberos de la mémoire de LSASS dans un processus protégé par la virtualisation ; les lectures de LSASS rapportent bien moins
Protection LSARunAsPPL sous HKLM\SYSTEM\CurrentControlSet\Control\Lsa vaut 1 (verrouillé par UEFI) ou 2 ; l’événement WinInit 12 indique que LSASS a démarré en tant que processus protégéLes processus non protégés ne peuvent pas ouvrir LSASS pour lire sa mémoire, même en administrateur
Règle ASRLe GUID de règle 9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2 (« Block credential stealing from the Windows local security authority subsystem ») apparaît dans Get-MpPreference avec l’action block, et non auditBloque les tentatives de lecture de LSASS et les journalise sous forme d’événements Defender
WDigestUseLogonCredential sous ...\SecurityProviders\WDigest absent ou à 0Empêche la présence de mots de passe en clair en mémoire ; un passage à 1 est en soi une alerte
MFAImposée pour l’accès distant, la messagerie et les portails d’administration, de préférence résistante au phishingLimite ce qu’un mot de passe volé permet à lui seul
Moindre privilègeAucune ouverture de session d’administrateur de domaine sur les postes de travail ; mots de passe d’administrateur local uniques (LAPS) ; utilisateurs privilégiés dans Protected UsersLimite les secrets que détient un poste de travail compromis

Aucun de ces contrôles n’arrête un stealer de navigateur qui s’exécute sous l’identité de l’utilisateur, puisque celui-ci peut lire ses propres données. Les contrôles sont alors la MFA, des politiques qui tiennent les identifiants hors des magasins des navigateurs lorsque votre organisation le permet, et des durées de session courtes.

Réponse à incident

Lorsque l’analyse ou une alerte confirme un accès aux identifiants, procédez dans cet ordre :

  1. Contenir d’abord. Isolez l’hôte pour que la collecte s’arrête et que rien d’autre ne sorte ; laissez-le allumé pour les preuves en mémoire, comme dans la leçon sur la persistance.
  2. Délimiter les comptes exposés. Listez toutes les personnes dont le matériel se trouvait sur l’hôte : ouvertures de session interactives et bureau à distance (Security 4624, types de logon 2, 10 et 11) pendant la fenêtre d’exposition, ouvertures de session de domaine en cache, comptes de service exécutés sur l’hôte, tâches planifiées avec identifiants enregistrés, et pour les stealers, chaque site enregistré dans les navigateurs de l’utilisateur touché et chaque application dont les fichiers de session ont été lus. La fenêtre d’exposition commence à la première exécution, pas à la détection.
  3. Supprimer le point d’ancrage avant de réinitialiser. Sinon, les nouveaux mots de passe sont volés eux aussi.
  4. Réinitialiser et révoquer. Réinitialisez les mots de passe des comptes exposés, en commençant par les plus privilégiés ; révoquez les sessions et les jetons de rafraîchissement auprès du fournisseur d’identité ; invalidez les clés d’API et les mots de passe d’application trouvés dans le périmètre ; examinez les méthodes MFA enregistrées pendant la fenêtre. Si des contrôleurs de domaine ou des secrets Kerberos ont été exposés, le matériel de clés du domaine lui-même doit faire l’objet d’une rotation planifiée, ce qui est un projet, pas une case à cocher.
  5. Chasser la réutilisation. Recherchez dans les journaux d’authentification les comptes exposés depuis de nouveaux hôtes, adresses sources ou pays ; les ouvertures de session avec identifiants explicites (4648) ; les demandes inhabituelles de tickets de service Kerberos ; les nouvelles règles de boîte aux lettres ; et les connexions réussies avec un jeton de session mais sans MFA interactive.

Rapporter le vol d’identifiants

Le rapport de triage liste le vol d’identifiants parmi les capacités. Pour les intervenants, ajoutez une section qui répond à ce qui était atteignable, et pas seulement à ce que le code peut faire :

ChampExemple
Comportement et identifiant ATT&CKLit les identifiants et cookies du navigateur, T1555.003, T1539
Niveau de preuveObservé lors d’une exécution en laboratoire / vu dans la télémétrie de l’hôte / code uniquement
CiblesDeux navigateurs basés sur Chromium, un client FTP, des dossiers de portefeuilles
ExfiltrationHTTP POST d’un ZIP vers le C2 décrit dans la section réseau
Comptes exposésUtilisateur demo (tous les sites enregistrés) ; aucune ouverture de session privilégiée dans la fenêtre
Contrôles présentsCredential Guard actif ; protection LSA activée ; règle ASR en mode audit uniquement
ActionsRéinitialisations, révocation des sessions, requêtes de chasse et leur état

Distinguez bien « peut voler » et « a volé ». Un échantillon doté d’une routine LSASS qui s’est exécutée sur un hôte où la protection LSA était activée, et qui a échoué, constitue un constat différent, avec un périmètre différent, de celui d’un échantillon qui a écrit un fichier de dump.

Lab : détecter l’accès aux identifiants dans des événements de type Sysmon

Dans ce lab, vous générez des événements synthétiques pour un poste de travail, écrivez un script Python de détection doté d’une liste d’autorisation et d’une logique de masques d’accès, et triez les imports d’un programme bénin qui interroge le clavier. Rien ici n’ouvre LSASS, ne lit un magasin de navigateur ni n’enregistre de frappes ; les événements sont des lignes JSON et tous les noms sont inventés.

  1. Préparez un dossier de travail et un environnement virtuel. Seul pefile est nécessaire, pour l’étape 5 :

    bash
    mkdir m7t && cd m7t
    python3 -m venv .venv
    .venv/bin/pip install -q pefile
    .venv/bin/python --version
    text
    Python 3.14.7
  2. Générez les événements. Les entrées Sysmon 10 montrent des processus attendus qui ouvrent LSASS ; les entrées Security 4663 montrent des lectures d’un profil de navigateur. L’intrusion simulée est un programme situé dans un dossier temporaire qui fait les deux :

    python
    # make_events.py - writes events.jsonl: synthetic Sysmon/Security-style events for one workstation.
    # No process is opened and no file is read; this script only writes JSON.
    import json
    
    HOST = "WS-DEMO-11"
    SYS32 = r"C:\Windows\System32"
    LSASS = SYS32 + r"\lsass.exe"
    PROFILE = r"C:\Users\demo\AppData\Local\DemoBrowser\User Data"
    BROWSER = r"C:\Program Files\DemoBrowser\browser.exe"
    DEFENDER = r"C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.24090.11-0\MsMpEng.exe"
    EDR = r"C:\Program Files\DemoEDR\agent.exe"
    BACKUP = r"C:\Program Files\DemoBackup\backupsvc.exe"
    ODD = r"C:\Users\demo\AppData\Local\Temp\DemoTool.exe"
    
    def ev(t, eid, **f):
        return {"UtcTime": f"2026-09-29 {t}", "EventID": eid, "Computer": HOST, **f}
    
    def access(t, src, mask, trace="ntdll.dll+9d4c4|KERNELBASE.dll+2c0fe"):
        return ev(t, 10, SourceImage=src, TargetImage=LSASS, GrantedAccess=mask, CallTrace=trace)
    
    def read(t, proc, name):
        # Security 4663 (object access, requires a SACL on the folder); 0x1 = ReadData
        return ev(t, 4663, ProcessName=proc, ObjectName=PROFILE + "\\" + name, AccessMask="0x1")
    
    events = [
        # --- expected access to lsass.exe ---
        access("09:00:01.102", SYS32 + r"\svchost.exe", "0x1000"),
        access("09:00:04.417", SYS32 + r"\wininit.exe", "0x1fffff"),
        access("09:02:10.880", DEFENDER, "0x1410"),
        access("09:02:11.035", EDR, "0x1fffff"),
        access("09:30:45.219", SYS32 + r"\Taskmgr.exe", "0x1400"),
        # --- expected reads of browser profile files ---
        read("09:15:02.334", BROWSER, r"Default\Login Data"),
        read("09:15:02.341", BROWSER, r"Default\Cookies"),
        read("11:00:00.512", BACKUP, r"Default\Login Data"),
        # --- simulated intrusion (names fictional) ---
        ev("10:22:31.006", 1, Image=ODD, ParentImage=r"C:\Windows\explorer.exe", CommandLine=f'"{ODD}"'),
        access("10:22:33.781", ODD, "0x1010", trace="ntdll.dll+9d4c4|KERNELBASE.dll+2c0fe|UNKNOWN(00000000004016a2)"),
        read("10:22:34.120", ODD, r"Default\Login Data"),
        read("10:22:34.126", ODD, r"Default\Cookies"),
        read("10:22:34.131", ODD, "Local State"),
        ev("10:22:34.402", 11, Image=ODD, TargetFilename=r"C:\Users\demo\AppData\Local\Temp\ld.tmp"),
    ]
    
    with open("events.jsonl", "w") as f:
        for e in events:
            f.write(json.dumps(e) + "\n")
    print(f"wrote {len(events)} events")
    bash
    .venv/bin/python make_events.py
    sed -n 10p events.jsonl
    text
    wrote 14 events
    {"UtcTime": "2026-09-29 10:22:33.781", "EventID": 10, "Computer": "WS-DEMO-11", "SourceImage": "C:\\Users\\demo\\AppData\\Local\\Temp\\DemoTool.exe", "TargetImage": "C:\\Windows\\System32\\lsass.exe", "GrantedAccess": "0x1010", "CallTrace": "ntdll.dll+9d4c4|KERNELBASE.dll+2c0fe|UNKNOWN(00000000004016a2)"}
  3. Écrivez le script de détection. La règle LSASS décode le masque en droits nommés et ignore les accès de requête pure avant de consulter la liste d’autorisation. La règle navigateur ignore le navigateur propriétaire, applique la liste d’autorisation et regroupe les lectures restantes par processus en une seule alerte. Les deux ajoutent l’événement de création de processus lorsqu’il existe :

    python
    # detect_creds.py - flag credential-access patterns in Sysmon/Security-style JSON lines.
    # Usage: detect_creds.py events.jsonl [--explain] [--no-tuning]
    import json, re, sys
    from collections import defaultdict
    
    USER_WRITABLE = re.compile(r"\\users\\[^\\]+\\|\\programdata\\|\\windows\\temp\\", re.I)
    
    # Process access rights that let the caller read, write or run code in lsass.exe
    RIGHTS = {0x0002: "CREATE_THREAD", 0x0008: "VM_OPERATION", 0x0010: "VM_READ",
              0x0020: "VM_WRITE", 0x0040: "DUP_HANDLE"}
    
    # Tuning: reviewed processes that legitimately open lsass.exe with memory rights.
    ALLOW_LSASS = [re.compile(p, re.I) for p in (
        r"^C:\\Windows\\System32\\(wininit|csrss|services|svchost|wmiprvse)\.exe$",
        r"^C:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\[\d.\-]+\\MsMpEng\.exe$",
        r"^C:\\Program Files\\DemoEDR\\agent\.exe$",
    )]
    
    # Browser credential stores (generic Chromium-style layout) and their ATT&CK IDs
    STORES = {r"\\User Data\\[^\\]+\\Login Data$": ("saved logins", "T1555.003"),
              r"\\User Data\\[^\\]+\\(Network\\)?Cookies$": ("cookies", "T1539"),
              r"\\User Data\\Local State$": ("key file", "T1555.003")}
    BROWSER_OWNERS = [re.compile(r"^C:\\Program Files\\DemoBrowser\\browser\.exe$", re.I)]
    ALLOW_READERS = [re.compile(r"^C:\\Program Files\\DemoBackup\\backupsvc\.exe$", re.I)]
    
    def rights(mask):
        return [n for bit, n in RIGHTS.items() if mask & bit]
    
    def matches(rxs, path):
        return any(r.search(path) for r in rxs)
    
    def main(path, explain, tuned):
        events = [json.loads(l) for l in open(path, encoding="utf-8-sig") if l.strip()]
        starts = {e["Image"].lower(): e for e in events if e["EventID"] == 1}
        alerts, ignored = [], []
        reads = defaultdict(list)
    
        for e in events:
            if e["EventID"] == 10 and e.get("TargetImage", "").lower().endswith("\\lsass.exe"):
                src, mask = e["SourceImage"], int(e["GrantedAccess"], 16)
                got = rights(mask)
                if not got:
                    ignored.append((e, f"lsass access by {src}: mask {e['GrantedAccess']} is query-only"))
                elif tuned and matches(ALLOW_LSASS, src):
                    ignored.append((e, f"lsass access by {src}: allow-listed"))
                else:
                    why = ["+".join(got)]
                    if USER_WRITABLE.search(src):
                        why.append("user-writable path")
                    if "UNKNOWN" in e.get("CallTrace", ""):
                        why.append("CallTrace has UNKNOWN frame")
                    sev = "HIGH" if len(why) > 1 else "MEDIUM"
                    alerts.append((sev, e, "T1003.001", f"{src} opened lsass.exe with {e['GrantedAccess']} ({'; '.join(why)})"))
            elif e["EventID"] == 4663:
                obj, proc = e["ObjectName"], e["ProcessName"]
                hit = next((v for k, v in STORES.items() if re.search(k, obj, re.I)), None)
                if not hit:
                    continue
                if matches(BROWSER_OWNERS, proc):
                    ignored.append((e, f"{hit[0]} read by owning browser"))
                elif tuned and matches(ALLOW_READERS, proc):
                    ignored.append((e, f"{hit[0]} read by allow-listed {proc}"))
                else:
                    reads[(e["Computer"], proc)].append((e, hit))
    
        for (host, proc), hits in reads.items():
            first = hits[0][0]
            ids = sorted({h[1] for _, h in hits})
            kinds = ", ".join(h[0] for _, h in hits)
            sev = "HIGH" if USER_WRITABLE.search(proc) else "MEDIUM"
            alerts.append((sev, first, ",".join(ids), f"{proc} read browser {kinds} ({len(hits)} files)"))
    
        if explain:
            for e, why in ignored:
                print(f"[ignored] {e['UtcTime']} EID {e['EventID']:<4} {why}")
        for sev, e, tid, msg in sorted(alerts, key=lambda a: a[1]["UtcTime"]):
            print(f"[{sev:<6}] {e['UtcTime']} {e['Computer']} EID {e['EventID']:<4} {tid} {msg}")
            img = e.get("SourceImage") or e.get("ProcessName")
            if img and img.lower() in starts:
                p = starts[img.lower()]
                print(f"           started {p['UtcTime']} by {p['ParentImage']}")
        print(f"-- {len(events)} events, {len(alerts)} alerts, {len(ignored)} ignored (tuning {'on' if tuned else 'off'})")
    
    if __name__ == "__main__":
        a = sys.argv[1:]
        main(a[0], "--explain" in a, "--no-tuning" not in a)
  4. Exécutez-le avec --explain pour voir ce qui a été écarté et pourquoi :

    bash
    .venv/bin/python detect_creds.py events.jsonl --explain
    text
    [ignored] 2026-09-29 09:00:01.102 EID 10   lsass access by C:\Windows\System32\svchost.exe: mask 0x1000 is query-only
    [ignored] 2026-09-29 09:00:04.417 EID 10   lsass access by C:\Windows\System32\wininit.exe: allow-listed
    [ignored] 2026-09-29 09:02:10.880 EID 10   lsass access by C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.24090.11-0\MsMpEng.exe: allow-listed
    [ignored] 2026-09-29 09:02:11.035 EID 10   lsass access by C:\Program Files\DemoEDR\agent.exe: allow-listed
    [ignored] 2026-09-29 09:30:45.219 EID 10   lsass access by C:\Windows\System32\Taskmgr.exe: mask 0x1400 is query-only
    [ignored] 2026-09-29 09:15:02.334 EID 4663 saved logins read by owning browser
    [ignored] 2026-09-29 09:15:02.341 EID 4663 cookies read by owning browser
    [ignored] 2026-09-29 11:00:00.512 EID 4663 saved logins read by allow-listed C:\Program Files\DemoBackup\backupsvc.exe
    [HIGH  ] 2026-09-29 10:22:33.781 WS-DEMO-11 EID 10   T1003.001 C:\Users\demo\AppData\Local\Temp\DemoTool.exe opened lsass.exe with 0x1010 (VM_READ; user-writable path; CallTrace has UNKNOWN frame)
               started 2026-09-29 10:22:31.006 by C:\Windows\explorer.exe
    [HIGH  ] 2026-09-29 10:22:34.120 WS-DEMO-11 EID 4663 T1539,T1555.003 C:\Users\demo\AppData\Local\Temp\DemoTool.exe read browser saved logins, cookies, key file (3 files)
               started 2026-09-29 10:22:31.006 by C:\Windows\explorer.exe
    -- 14 events, 2 alerts, 8 ignored (tuning on)

    Les deux alertes correspondent à l’intrusion, et elles partagent un processus lancé trois secondes plus tôt. Le Gestionnaire des tâches est un cas bénin utile : il ne figure pas dans la liste d’autorisation, mais il est correctement ignoré car 0x1400 ne comporte aucun droit mémoire. C’est la vérification du masque, et non la liste d’autorisation, qui fait l’essentiel du travail.

  5. Voyez ce que fait l’ajustement. Relancez avec les listes d’autorisation désactivées :

    bash
    .venv/bin/python detect_creds.py events.jsonl --no-tuning
    text
    [MEDIUM] 2026-09-29 09:00:04.417 WS-DEMO-11 EID 10   T1003.001 C:\Windows\System32\wininit.exe opened lsass.exe with 0x1fffff (CREATE_THREAD+VM_OPERATION+VM_READ+VM_WRITE+DUP_HANDLE)
    [HIGH  ] 2026-09-29 09:02:10.880 WS-DEMO-11 EID 10   T1003.001 C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.24090.11-0\MsMpEng.exe opened lsass.exe with 0x1410 (VM_READ; user-writable path)
    [MEDIUM] 2026-09-29 09:02:11.035 WS-DEMO-11 EID 10   T1003.001 C:\Program Files\DemoEDR\agent.exe opened lsass.exe with 0x1fffff (CREATE_THREAD+VM_OPERATION+VM_READ+VM_WRITE+DUP_HANDLE)
    [HIGH  ] 2026-09-29 10:22:33.781 WS-DEMO-11 EID 10   T1003.001 C:\Users\demo\AppData\Local\Temp\DemoTool.exe opened lsass.exe with 0x1010 (VM_READ; user-writable path; CallTrace has UNKNOWN frame)
               started 2026-09-29 10:22:31.006 by C:\Windows\explorer.exe
    [HIGH  ] 2026-09-29 10:22:34.120 WS-DEMO-11 EID 4663 T1539,T1555.003 C:\Users\demo\AppData\Local\Temp\DemoTool.exe read browser saved logins, cookies, key file (3 files)
               started 2026-09-29 10:22:31.006 by C:\Windows\explorer.exe
    [MEDIUM] 2026-09-29 11:00:00.512 WS-DEMO-11 EID 4663 T1555.003 C:\Program Files\DemoBackup\backupsvc.exe read browser saved logins (1 files)
    -- 14 events, 6 alerts, 4 ignored (tuning off)

    Remarque sur l’ajustement : Defender se déclenche en HIGH parce que son dossier de plateforme se trouve sous ProgramData, que la règle considère comme accessible en écriture par l’utilisateur. L’entrée de la liste d’autorisation corrige cela avec un motif de chemin complet qui inclut le dossier de plateforme versionné, mais ce n’est toujours qu’un chemin. En production, joignez chaque événement Sysmon 10 à l’événement Sysmon 1 de la source via SourceProcessGUID et vérifiez le signataire, et gardez la vérification de call trace UNKNOWN active même pour les images autorisées, car du code injecté dans un processus de confiance apparaît exactement de cette façon.

  6. Triez un programme bénin qui interroge le clavier. Compilez un programme qui se contente de vérifier dix fois si la barre d’espace est enfoncée et affiche la réponse. Il ne stocke et n’envoie rien :

    c
    /* keypoll.c - benign input-polling demo: reports whether the space bar is held,
       ten times, then exits. It records nothing, stores nothing and sends nothing. */
    #include <windows.h>
    #include <stdio.h>
    
    int main(void) {
        for (int i = 0; i < 10; i++) {
            SHORT s = GetAsyncKeyState(VK_SPACE);
            printf("poll %d: space %s\n", i, (s & 0x8000) ? "down" : "up");
            Sleep(200);
        }
        return 0;
    }
    bash
    x86_64-w64-mingw32-gcc -O2 -o keypoll.exe keypoll.c

    Étendez capimports.py de Lire les capacités à partir des imports avec une catégorie saisie et écran, faites correspondre le motif registry à RegSaveKeyEx, et affichez le nombre de DLL. Seules les lignes modifiées sont montrées ; la nouvelle catégorie se place après resources :

    python
        "registry":     r"^Reg(Open|Create|Set|Query|Delete|Enum|Save)",
        "input/screen": r"^(GetAsyncKeyState|GetKeyState|GetKeyboardState|SetWindowsHookEx|CallNextHookEx|GetRawInputData|RegisterRawInputDevices|GetClipboardData|OpenClipboard|AddClipboardFormatListener|BitBlt|GetDC|GetForegroundWindow|GetWindowText)",
    print(f"{sys.argv[1]}: {total} imports from {len(pe.DIRECTORY_ENTRY_IMPORT)} DLLs")
    bash
    .venv/bin/python capimports.py keypoll.exe
    text
    keypoll.exe: 42 imports from 10 DLLs
      memory        VirtualProtect (kernel32.dll)
      input/screen  GetAsyncKeyState (user32.dll)

    La catégorie de saisie s’allume sur un import de user32.dll, et le seul autre résultat est le VirtualProtect du runtime, déjà rencontré dans Lire les capacités à partir des imports. Il n’y a ni hook, ni API de titre de fenêtre, ni catégorie écriture de fichier ou réseau. Formulé comme une hypothèse : « interroge l’état du clavier ; aucune preuve de journalisation, de contexte de fenêtre ni d’exfiltration ». C’est ainsi qu’il faut rapporter un import de saisie isolé. La même ligne dans la table des imports d’un stealer, à côté de GetForegroundWindow, d’ajouts à un fichier et de WinINet, se lirait très différemment.

Questions : Quelles valeurs de masque d’accès de vos propres données Sysmon tombent dans le groupe « requête uniquement », et quels processus auraient besoin d’entrées dans la liste d’autorisation ? Le script ignore l’événement Sysmon 11 qui a écrit ld.tmp ; quelle règle l’utiliserait, et pourquoi une copie d’une base de navigateur dans un dossier temporaire compte-t-elle ? Si WS-DEMO-11 avait eu Credential Guard et la protection LSA activés, comment cela changerait-il la ligne « comptes exposés » de votre rapport, et comment le confirmeriez-vous depuis l’hôte ? Quels imports devraient rejoindre GetAsyncKeyState avant que vous ne qualifiiez keypoll.exe de keylogger ?

À retenir

  • Le vol d’identifiants transforme un incident d’hôte en incident d’identité : le périmètre suit les comptes exposés, et les jetons et cookies doivent être révoqués en plus de la réinitialisation des mots de passe.
  • Les stealers révèlent, une fois les chaînes décodées, des chemins cibles, des noms de fichiers de magasins et des appels DPAPI ; l’accès à LSASS apparaît sous forme de handles de processus dotés de droits mémoire ; les keyloggers apparaissent sous forme d’API de saisie combinées au contexte de fenêtre, à la journalisation et à l’exfiltration.
  • Détectez l’accès à LSASS avec Sysmon 10 en testant les bits du masque d’accès, pas des valeurs littérales, et surveillez les frames UNKNOWN dans CallTrace ; auditez les lectures des magasins de navigateur avec des SACL et les événements fichier de l’EDR.
  • Ajustez avec des exceptions fondées sur le chemin complet et le signataire, jamais sur les noms de fichiers, et laissez la logique de masque écarter les accès de requête pure avant que la liste d’autorisation ne le fasse.
  • Vérifiez Credential Guard, la protection LSA, la règle ASR pour LSASS, WDigest, la MFA et le moindre privilège comme des faits de configuration consignés pour chaque hôte touché.
  • Contenez, délimitez les comptes exposés, supprimez le point d’ancrage, puis réinitialisez, révoquez et chassez la réutilisation. Les leçons suivantes de ce module, la future Comprendre l’injection de processus et Hooking et rootkits en mode utilisateur, examinent comment le code à l’origine de ces comportements se cache dans d’autres processus.