Aller au contenu principal
Xsec

Neutralisation d'EDR

Publié le 11 min de lecture

Mis à jour le

Sérieevasion
Partie 3 sur 3
Dans cette série32 min de lecture au total
  1. Composants internes de Windows
  2. Composants Internes des EDR
  3. Neutralisation d'EDR

De la signature du driver à la preuve d’une primitive de terminaison non autorisée.

DangerCadre de l'analyse

Cet article décrit une méthode d’analyse défensive et de validation en lab. Il ne suffit jamais qu’un driver soit signé, accepte des commandes Input/Output Control (IOCTL) ou importe une routine sensible pour conclure qu’il est vulnérable. La preuve doit porter sur l’appelant, le contrôle de la cible, l’autorisation et le gain de capacité observé sur les versions exactes testées.

La technique Bring Your Own Vulnerable Driver (BYOVD) consiste à charger un driver légitime mais vulnérable afin d’obtenir du noyau une opération privilégiée. Dans le cas étudié, cette opération est la terminaison d’un processus. Le raisonnement ne part donc pas d’un nom de fonction pour remonter immédiatement à une exploitation ; il suit tout le protocole exposé par le device, depuis son ouverture jusqu’à l’effet final.

Trois observations doivent rester séparées :

  • une signature établit une relation de confiance pour le chargement selon la politique active ;
  • un IOCTL sélectionne une commande du protocole propre au driver ;
  • un import de ZwTerminateProcess indique que le binaire peut appeler cette routine.

Aucune de ces observations ne démontre, seule, qu’un processus peut terminer le PID de son choix.

De la confiance de chargement à la surface exposée

Une politique d’intégrité du code peut accepter la signature d’un driver sans connaître la qualité de chacune de ses vérifications d’autorisation. C’est précisément l’écart exploité par BYOVD : le code est admis dans le noyau, puis son interface de device fournit éventuellement une capacité trop large à un appelant qui peut l’atteindre.

Cette capacité peut rester étroite, par exemple terminer un processus, ou devenir plus générale, par exemple lire ou écrire en mémoire kernel. Une primitive de terminaison n’est ni une primitive d’écriture kernel, ni une exécution arbitraire de code kernel. Cette distinction détermine ce que le défaut permet réellement de neutraliser.

Dans ce scénario, l’effet final peut passer par ZwTerminateProcess. La routine est exécutée par ntoskrnl.exe, déclarée dans ntddk.h et résolue par un driver au moyen de NtosKrnl.lib. Elle reçoit un handle de processus et un statut de sortie, pas un Process Identifier (PID). Si l’entrée du protocole contient un PID, le driver doit donc le convertir en objet ou en handle, souvent par ZwOpenProcess, avant de demander la terminaison.

L’existence de ce chemin pose alors une question plus précise : qui peut ouvrir le device et faire accepter la commande ?

Rechercher un candidat sans confondre triage et preuve

LOLDB centralise des métadonnées sur des drivers Windows connus, dont les empreintes, certificats, imports et catégories d’usage. La base sert à réduire l’espace de recherche ; elle ne remplace ni l’analyse du binaire exact ni un test d’autorisation.

Page d'accueil de LOLDB présentant la base de drivers Windows
LOLDB, vue d'ensemble capturée le 23 juillet 2026. Les compteurs décrivent l'état de la base au moment de la capture et peuvent évoluer.

Le filtre Process Killers rapproche les entrées associées à une capacité de terminaison observée. Il ne signifie pas que tous les drivers listés exposent le même IOCTL, acceptent le même format de buffer ou sont exploitables par le même niveau de privilège.

Résultats LOLDB filtrés sur les drivers associés à la terminaison de processus
LOLDB avec le filtre Process Killers. Une fiche fournit des pistes de triage ; la version du fichier, son protocole de device et ses contrôles restent à vérifier en lab.

Le triage établit donc quels binaires méritent une analyse. Il ne montre toujours pas comment une application atteint la branche sensible. Pour répondre à cette question, il faut reconstruire la routine de dispatch du driver.

Reconstruire le contrat IOCTL

Un IOCTL est une valeur de 32 bits structurée par la macro CTL_CODE(DeviceType, Function, Method, Access). Le champ Method décrit le transport des buffers ; le champ Access indique les droits d’accès au device que le gestionnaire d’entrées/sorties exige pour créer la requête. Même FILE_ANY_ACCESS ne rend pas automatiquement le device accessible : sa liste de contrôle d’accès, ou Access Control List (ACL), décide d’abord quels principaux peuvent obtenir un handle.

Dans un driver Windows Driver Model (WDM), DriverEntry est le point d’entrée compilé dans le fichier .sys du fournisseur. Il initialise notamment la structure DRIVER_OBJECT. L’entrée MajorFunction[IRP_MJ_DEVICE_CONTROL] peut contenir l’adresse d’une routine de dispatch fournie par ce même driver. IRP_MJ_DEVICE_CONTROL est un code majeur de requête, pas une fonction exportée par Windows.

NoteCe que montre réellement le décompilateur

IDA Pro ou Ghidra permettent de suivre les imports, références croisées, branches et accès aux buffers. Le pseudo-code reconstruit reste une aide à la lecture, pas le code source du fournisseur. Les types, noms de variables et conditions d’autorisation doivent être confirmés dans le désassemblage et, si nécessaire, par une exécution instrumentée.

  1. Identifier le binaire et le device

    Conserver l’empreinte, la version et la signature du fichier analysé. Repérer la création du device, son lien symbolique et les descripteurs de sécurité qui conditionnent son ouverture.

  2. Retrouver la routine de dispatch

    Suivre l’initialisation de DRIVER_OBJECT.MajorFunction[] jusqu’à l’entrée IRP_MJ_DEVICE_CONTROL. L’adresse trouvée appartient à une fonction compilée dans le .sys, même si le code majeur est défini par le Windows Driver Kit (WDK).

  3. Décoder chaque commande

    Pour chaque valeur comparée, décoder DeviceType, Function, Method et Access, puis relever les tailles, offsets et directions des buffers. Une constante identifiée sans son format d’entrée ne suffit pas à reproduire le contrat.

  4. Suivre l'effet privilégié

    Tracer les données contrôlées par l’appelant jusqu’à ZwOpenProcess, ZwTerminateProcess ou une autre routine sensible. Relever toutes les branches qui valident l’identité de l’appelant, la cible et le contexte d’exécution.

  5. Mesurer le gain de capacité

    Comparer l’effet obtenu par le device aux droits ordinaires du même appelant. Une vulnérabilité existe lorsque le driver lui accorde une opération privilégiée qu’il ne peut pas accomplir directement.

Lire les captures de reverse engineering

La méthode précédente fournit le fil directeur. Les captures suivantes montrent comment ses différentes preuves apparaissent pendant une analyse statique. Elles documentent un échantillon et une session de lab déterminés ; elles ne décrivent pas tous les drivers portant un import similaire.

La table d’imports commence par établir quelles routines kernel le binaire peut résoudre. Elle ne dit pas encore quelle entrée utilisateur atteint ces appels.

Table d'imports kernel affichée dans IDA
Observation de lab : extrait de la table d'imports du binaire étudié. Une importation décrit une dépendance possible, pas une branche nécessairement accessible.

La présence de ZwTerminateProcess réduit la recherche à ses références. La routine apparaît ici comme un symbole importé depuis le noyau Windows.

Import ZwTerminateProcess et ses références dans IDA
Observation de lab : `ZwTerminateProcess` dans les imports. La routine s'exécute dans `ntoskrnl.exe`; la capture prouve seulement que cet échantillon peut la référencer.

Les références croisées relient ensuite l’import aux fonctions du driver qui l’appellent. Ce lien établit l’atteignabilité interne, mais pas encore l’atteignabilité depuis un IOCTL.

Références croisées vers ZwTerminateProcess dans IDA
Observation de lab : références de code vers l'import. Chaque appelant doit être suivi jusqu'à une entrée du protocole de device.

Le graphe de contrôle permet d’observer l’appel et les branches voisines. Les boucles et tests visibles doivent être interprétés avec leurs buffers et leurs bornes.

Graphe de contrôle autour d'un appel à ZwTerminateProcess
Observation de lab : branche contenant `ZwTerminateProcess`, suivie d'une fermeture de handle. Le graphe ne prouve pas encore qui fournit les handles.

Le pseudo-code rend plus lisible la relation entre le handle et l’appel. Il faut cependant remonter l’origine de cette valeur avant de conclure que la cible est contrôlée par l’appelant.

Pseudo-code montrant plusieurs appels à ZwTerminateProcess
Observation de lab : appels de terminaison sur des handles présents dans une structure locale. Leur provenance et les contrôles antérieurs déterminent la sécurité du chemin.

Les références vers cette fonction conduisent enfin vers la routine qui prépare les paramètres. Le chaînage inverse relie l’effet privilégié à sa commande.

Références croisées vers la fonction appelant ZwTerminateProcess
Observation de lab : remontée vers les appelants de la fonction de terminaison. L'objectif est d'identifier la routine de dispatch et l'origine des données.

La branche de dispatch visible sélectionne ici une constante IOCTL. Cette valeur identifie une commande pour cette version du binaire, pas un code Windows générique.

Branche de dispatch comparant un code IOCTL dans le pseudo-code
Observation de lab : la constante `0xB4A00404` atteint la fonction étudiée après un contrôle de taille. Les ACL, l'autorisation de l'appelant et les restrictions de cible restent à établir.

Ces écrans reconstituent un chemin de code. La qualification de vulnérabilité exige encore une preuve dynamique et comparative.

Quand un IOCTL de terminaison constitue réellement une vulnérabilité

Le parcours documenté sépare les contrôles du gestionnaire d’entrées/sorties, la logique propre au driver et l’effet fourni par le noyau :

Schéma technique
DE L'IOCTL À LA TERMINAISON: Un code de contrôle visible et un import kernel ne prouvent pas une atteignabilité non autorisée.ANALYSE BYOVD · CHEMIN DEVICE CONTROLDE L'IOCTL À LA TERMINAISONUn code de contrôle visible et un import kernel ne prouvent pas une atteignabilité non autorisée.MODE UTILISATEURMODE NOYAUDeviceIoControlIRPouverturelivraisonPID autoriséhandle kernelAPPELANTApplicationPID contrôléPREMIER FILTREACL du deviceOuvrir un handleI/O MANAGERDeviceIoControlConstruire un IRPSECOND FILTREAccess de l'IOCTLDroits du handle.SYS FOURNISSEURRoutine dedispatchDécoder + validerPOLITIQUE RUNTIMEContrôles appelant+ cibleAccepter ou refuserNTOSKRNL.EXEZwOpenProcessPROCESS_TERMINATENTOSKRNL.EXEZwTerminateProcessHandle, pas PIDTEST DE VULNÉRABILITÉ · APPELANT AUTORISÉ À OUVRIR · CIBLE CONTRÔLÉE · AUTORISATION ABSENTE · EFFET PRIVILÉGIÉ · IMPACT REPRODUCTIBLE
Chemin documenté de la requête. Le driver fournisseur possède le protocole du device, la validation de la cible, l'autorisation et la décision d'invoquer les routines kernel.Lecture du schémaLe chemin continu se lit de l'appelant vers l'effet final. L'ACL du device contrôle son ouverture, le champ Access de l'IOCTL contrôle la livraison de la requête et les vérifications runtime du fournisseur déterminent si la cible est autorisée. Seul un gain de capacité au-delà des droits ordinaires de l'appelant établit une vulnérabilité.

DeviceIoControl est l’API user-mode qui envoie le code de contrôle et ses buffers. Son point d’entrée est exporté par Kernel32.dll, son prototype est déclaré dans ioapiset.h et l’application le lie avec Kernel32.lib. Le gestionnaire d’entrées/sorties transforme l’appel en un I/O Request Packet (IRP) portant le code majeur IRP_MJ_DEVICE_CONTROL, puis appelle la routine de dispatch enregistrée par le driver.

ZwOpenProcess et ZwTerminateProcess s’exécutent dans ntoskrnl.exe, sont déclarées dans ntddk.h et sont liées par le driver au moyen de NtosKrnl.lib. La première peut demander un handle disposant de PROCESS_TERMINATE; la seconde tente de terminer le processus représenté par ce handle. L’une ou l’autre peut échouer si l’identifiant n’est plus valide, si l’accès n’est pas accordé, si le handle est incorrect, si la cible bénéficie de protections applicables ou si le processus est déjà en cours de terminaison.

Pour qualifier le chemin de vulnérable, l’analyse doit établir toute la chaîne :

PropriétéPreuve recherchée
AccessibilitéUn appelant situé dans le modèle de menace peut ouvrir l’objet device
AtteignabilitéL’IOCTL atteint la branche de terminaison par IRP_MJ_DEVICE_CONTROL
Contrôle de la cibleL’appelant fournit ou influence le PID ou le handle utilisé
Autorisation insuffisanteLe driver ne valide pas correctement l’identité, les droits ou la cible
Gain de capacitéLe driver termine un processus que l’appelant ne peut pas ouvrir directement avec PROCESS_TERMINATE
ImpactLa cible peut être un processus système, un service de sécurité ou le processus d’un autre utilisateur
ReproductibilitéL’effet est confirmé sur les versions exactes du driver, du produit et de Windows

La démonstration factuelle est donc comparative : un appelant peut ouvrir le device, envoyer la commande avec une cible qu’il contrôle et provoquer une terminaison qu’il ne pourrait pas obtenir directement avec ses propres droits. Si le device est réservé à SYSTEM ou à un service déterminé, si le handler authentifie l’appelant et si les cibles sont strictement limitées, le même appel à ZwTerminateProcess peut relever d’une fonction d’administration intentionnelle.

ImportantLimite de la primitive

Un IOCTL de terminaison fournit au mieux une primitive de terminaison. Il ne permet pas, à lui seul, de modifier les structures internes qui représentent des callbacks kernel. Cette opération exige un autre défaut fournissant une écriture à une adresse kernel choisie, puis une découverte propre au build étudié.

Cette frontière rejoint directement l’analyse technique des internals EDR, qui distingue l’inscription des capteurs, la livraison des événements, la corrélation et les mécanismes de réponse. Terminer un service ou retirer un signal ne fait pas disparaître les observations déjà collectées par les autres sources.

Réduire le risque BYOVD

L’analyse d’un cas individuel indique où corriger le protocole. La défense à l’échelle d’un parc doit aussi agir avant le chargement et après l’apparition du driver :

  • appliquer les mises à jour du fournisseur et retirer les versions vulnérables ;
  • activer et vérifier la Microsoft Vulnerable Driver Blocklist, puis compléter la politique avec Windows Defender Application Control ou App Control for Business selon l’environnement ;
  • utiliser Hypervisor-protected Code Integrity (HVCI) pour renforcer l’intégrité du code kernel, sans la présenter comme une garantie contre tout driver signé vulnérable ;
  • restreindre l’ACL du device, choisir un champ Access adapté et valider les tailles, l’identité de l’appelant ainsi que chaque cible dans le handler ;
  • surveiller l’installation des services de driver, le chargement d’images kernel, les ouvertures de devices sensibles et l’état de santé du produit de sécurité ;
  • confirmer sur les endpoints que les politiques attendues sont effectivement actives, car la couverture dépend de la version de Windows, de la configuration et de l’état des listes.

Conclusion

La neutralisation d’un EDR par BYOVD n’est pas la conséquence automatique d’une signature, d’un IOCTL lisible ou d’un import de ZwTerminateProcess. Elle résulte d’une chaîne complète : le driver est admis, son device est accessible, une commande atteint une routine privilégiée, la cible est contrôlable et l’autorisation échoue à contenir l’effet.

Ce fil directeur transforme une recherche de noms de fonctions en analyse de sécurité : établir le contrat, suivre les données, comparer les droits et borner exactement la primitive obtenue.

Références

Utiliser avec une IA

Actions