Internals EDR : architecture de télémétrie et limites de l'évasion
Publié le 61 min de lecture
Mis à jour le
Une analyse sourcée des chemins de télémétrie Endpoint Detection and Response (EDR, détection et réponse sur les endpoints, c’est-à-dire les postes et serveurs supervisés) sous Windows, de leurs contrats documentés, de leurs limites d’application et d’une méthodologie de validation en red team.
Ouverture : ce qu’un EDR voit réellement
Considérons une séquence courte : un interpréteur démarre ; au cours de son exécution, des threads et des images mappées apparaissent ; il ouvre un handle vers un autre processus, modifie une clé de registre, écrit un fichier puis établit une connexion réseau. Du point de vue d’un opérateur, il peut s’agir d’une seule action. Windows l’expose comme des transitions d’état séparées, observées à des couches et à des instants différents, avec des garanties différentes. Le système d’exploitation ne transmet pas une « attaque » à l’EDR. Celui-ci la reconstruit à partir d’événements incomplets.
Un processus est une instance en cours d’exécution d’un programme ; un thread est l’un des fils d’exécution à l’intérieur de ce processus. Une image est ici un fichier exécutable ou une bibliothèque mappée en mémoire, ce qui signifie que Windows associe ses pages à des adresses virtuelles du processus. Un objet Windows est une entité gérée par le système, par exemple un processus, un thread ou un fichier ; un handle est la référence accordée à un processus pour manipuler cet objet avec des droits précis.
Cette distinction constitue le fil conducteur de l’article. Un callback est une fonction qu’un composant enregistre auprès de Windows afin que le système l’appelle automatiquement lorsqu’une opération précise se produit. Un hook intercepte au contraire un chemin d’exécution existant pour observer ou modifier l’appel avant de rendre la main au code original. La question utile est donc ce que chaque mécanisme peut connaître à cet instant, ce qu’il peut modifier, ce qui peut disparaître avant l’analyse et quels signaux indépendants subsistent s’il est contourné.
Les solutions Endpoint Detection and Response combinent plusieurs mécanismes d’observation et d’application de politiques. Un schéma d’événement définit les champs, leurs types et leur signification ; la mise en mémoire tampon conserve temporairement les événements lorsque leur producteur et leur lecteur n’avancent pas au même rythme. Aucune architecture unique ne décrit tous les produits : l’emplacement des capteurs, ces schémas et buffers, les dépendances cloud et les capacités de réponse restent spécifiques à chaque implémentation. Les interfaces Windows sur lesquelles de nombreux produits s’appuient sont néanmoins suffisamment documentées pour établir une base technique précise.
La télémétrie désigne les données techniques produites pendant l’exécution, par exemple une création de processus, un accès à un fichier ou une connexion. Un capteur collecte une partie de ces données. L’agent EDR est l’ensemble des composants installés sur l’endpoint ; son service local et le backend, c’est-à-dire le moteur d’analyse placé derrière l’agent, normalisent et relient les événements pour produire une détection.
Avant de suivre cette séquence capteur par capteur, il faut situer les lieux où elle se produit, ceux où elle devient observable et celui où elle est finalement interprétée. Un produit associe généralement un service en mode utilisateur à un ou plusieurs drivers en mode noyau, puis transmet une partie de leurs événements à un moteur d’analyse local ou distant. Cette description est un modèle fréquent, pas une spécification commune à tous les fournisseurs.
Ce modèle traverse d’abord deux domaines d’exécution dont les privilèges déterminent ce qu’un capteur peut observer ou modifier.
Deux domaines d’exécution.
Le mode utilisateur héberge les applications ordinaires et la majorité des services. Chaque processus y dispose de son propre espace d’adressage, l’ensemble isolé des adresses de mémoire virtuelle qu’il peut utiliser, et ne peut pas accéder directement à la mémoire du noyau. Un capteur user-mode peut observer des fonctions qu’un processus appelle, analyser certaines zones de sa mémoire ou recevoir des événements publiés par Windows. Sa visibilité dépend néanmoins du processus instrumenté et du chemin d’exécution réellement emprunté.
Le mode noyau exécute le cœur de Windows et les drivers. Un driver est un module privilégié qui permet au noyau de gérer un périphérique ou d’étendre une fonction du système. Un driver EDR peut s’enregistrer auprès de mécanismes documentés pour observer la création des processus, les accès aux objets, les opérations de fichiers ou une partie du traitement réseau. Cette position offre une observation plus proche de l’état du système, mais elle n’accorde ni une connaissance automatique de l’intention, ni un droit universel de bloquer chaque opération.
Les deux domaines communiquent notamment par des appels système, qui sont les passages contrôlés permettant à un programme de demander un service au noyau. Les requêtes d’entrée/sortie (input/output, I/O) représentent les échanges avec les fichiers, périphériques ou flux réseau. Des objets partagés sous contrôle du noyau et des files d’événements complètent ces échanges. Les deux domaines ne constituent donc pas deux copies complètes de la même réalité : chaque couche expose un contexte et des garanties différents.
La séparation entre user mode et kernel mode situe les privilèges, mais elle ne décrit pas encore la fonction de chaque brique EDR. Il faut maintenant relier ces domaines aux composants qui collectent, transportent et interprètent les événements.
Rôle des composants.
Une interface de programmation d’applications (Application Programming Interface, API) est un point d’entrée accompagné d’un contrat qui permet à un programme de demander un service à un autre composant. Un chemin d’API est la succession de fonctions réellement traversées pour satisfaire cette demande.
Un callback noyau est une fonction enregistrée par un driver que Windows appelle pendant le traitement d’une opération dans le noyau, par exemple la création d’un processus. Un filtre est un composant qui observe, autorise, bloque ou modifie une catégorie d’opérations. Un minifilter est un filtre spécialisé dans les fichiers ; il s’enregistre auprès du Filter Manager, le composant Windows qui organise ces filtres et leur transmet les opérations demandées.
Une altitude est un identifiant numérique écrit sous forme de texte qui positionne certains filtres ou callbacks dans leur chaîne de traitement. Une pré-opération intervient avant que la requête atteigne le système de fichiers ; une post-opération observe son résultat pendant le retour. Pour un minifilter, Windows appelle les pré-opérations des altitudes les plus élevées vers les plus basses, puis les post-opérations dans l’ordre inverse.
Une décision synchrone est prise avant que l’opération surveillée ne se termine, ce qui permet parfois de la bloquer immédiatement. Une action asynchrone intervient plus tard, après mise en file ou corrélation, par exemple lorsqu’un EDR termine un processus déjà démarré.
| Composant | Ce qu’il représente | Rôle technique | Limite à garder en tête |
|---|---|---|---|
| Application observée | Le processus dans lequel s’exécute le comportement : navigateur, interpréteur de scripts, outil d’administration ou binaire natif | Produit les appels, chargements d’images, accès mémoire, fichiers et connexions qui deviendront éventuellement des signaux | Une application ne décrit pas elle-même l’intention de l’opérateur |
| Capteur user-mode | Un module chargé ou injecté dans certains processus, ou un composant qui consomme leurs interfaces | Instrumente des chemins d’API, collecte du contexte d’appel et peut inspecter du contenu ou de la mémoire | Un appel qui ne traverse pas le chemin instrumenté peut échapper à ce capteur précis |
| Service EDR local | Le processus durable qui coordonne l’agent sur l’endpoint | Reçoit les événements, normalise les champs, enrichit les identités, applique une politique locale, maintient des files et dialogue avec le backend | Une notification reçue par un driver n’est pas nécessairement conservée, enrichie ou transmise |
| Driver EDR | Un module privilégié chargé dans le noyau Windows | Enregistre des callbacks, des filtres de fichiers ou des composants réseau et peut appliquer certaines décisions synchrones documentées | Un driver n’observe que les mécanismes auxquels il s’est enregistré et les opérations que ces mécanismes exposent |
| Event Tracing for Windows (ETW) | L’infrastructure de télémétrie intégrée à Windows | Le noyau et les applications y publient des événements structurés qu’un EDR peut collecter pour reconstituer une chronologie sans modifier le code observé | Une source enregistrée n’est pas forcément activée ; un canal de collecte actif ne garantit pas la conservation de chaque événement |
| Antimalware Scan Interface (AMSI) | Une interface que certaines applications, notamment les moteurs de scripts, utilisent pour présenter du contenu à un antivirus | Permet d’analyser un script ou un document après son déchiffrement ou sa reconstruction, au moment choisi par l’application | AMSI dépend de l’intégration de l’application et n’observe pas universellement tous les processus |
| Minifilter de fichiers | Un driver spécialisé dans la surveillance des opérations sur les fichiers | S’enregistre auprès du Filter Manager, le composant Windows qui organise ces filtres, puis reçoit des callbacks avant ou après une ouverture, lecture, écriture, modification ou suppression | Il ne reçoit que les opérations déclarées et sa présence ne garantit pas qu’un événement sera conservé par l’EDR |
| Windows Filtering Platform (WFP) | L’infrastructure de filtrage intégrée au traitement réseau Windows | Évalue le trafic à plusieurs points de contrôle et permet à un EDR de l’autoriser, le bloquer ou l’enrichir avec l’identité de l’application | Les informations disponibles et la capacité de blocage dépendent du point de contrôle utilisé |
| Backend d’analyse | Un moteur local ou distant qui reçoit les événements normalisés | Corrèle plusieurs capteurs, conserve l’état, enrichit la réputation et produit détections ou réponses | Le transport, la latence, la rétention et la politique peuvent modifier le résultat visible par l’analyste |
ETW, AMSI, les callbacks noyau, les minifilters et WFP ne sont donc pas des synonymes d’« EDR ». Ce sont des sources ou des points de contrôle que le produit peut combiner. Le service local et le backend transforment ensuite ces observations en entités, relations, alertes et actions.
La liste des composants décrit leurs responsabilités isolées. Leur articulation apparaît plus clairement lorsque la même activité traverse successivement plusieurs capteurs jusqu’au backend.
Parcours concret d’un événement.
Reprenons l’interpréteur de la séquence d’ouverture. Windows peut notifier la création de son processus et de ses threads au driver, signaler les images mappées, distribuer les opérations de handles et de registre aux callbacks inscrits, puis présenter les opérations de fichiers au minifilter. Si l’interpréteur intègre AMSI, le contenu du script peut aussi être soumis à cette interface. ETW peut publier d’autres éléments de contexte, tandis que la connexion atteint une ou plusieurs couches WFP.
Ces notifications n’arrivent pas nécessairement dans le même format, avec le même identifiant temporel ni dans l’ordre où le backend les affichera. Le service local doit les associer à une identité de processus, copier les champs encore valides, résoudre ce qui peut l’être sans bloquer le système, puis décider quoi conserver ou transmettre. Le backend peut enfin relier le contenu du script, l’image chargée, le fichier touché et la destination réseau à une même entité.
Le driver kernel constitue ainsi un capteur parmi plusieurs, et non le propriétaire universel de toute la télémétrie endpoint. La livraison d’un callback ne démontre pas non plus qu’un événement est conservé ou transmis. La pression sur les files, la configuration des sources, l’échantillonnage, les exclusions, la politique locale et l’ingestion backend, l’entrée des événements dans la plateforme d’analyse, peuvent chacun modifier l’ensemble de preuves final.
Acte I : naissance et mise en exécution
L’architecture prend maintenant vie avec le premier événement du fil rouge : l’apparition du processus. Les callbacks kernel exposent cette naissance, puis les threads et les images mappées montrent comment une identité devient un environnement capable d’exécuter du code. Aucun de ces signaux ne suffit encore à raconter ce que le processus fera.
Le graphe d’exécution commence par l’apparition d’un processus : cette transition fournit l’identité initiale à laquelle les événements suivants pourront être rattachés.
Création de processus.
PsSetCreateProcessNotifyRoutineEx est une fonction d’enregistrement du Process Manager, le gestionnaire de processus Windows, pas le chemin parcouru par chaque création. Son code est exporté par le module noyau ntoskrnl.exe. Le Windows Driver Kit (WDK), l’ensemble de documentation, d’en-têtes et d’outils destiné aux drivers Windows, fournit son prototype dans le header ntddk.h. Le driver référence ensuite ce point d’entrée au moyen de NtosKrnl.lib, une bibliothèque d’import utilisée par le linker ; lors du chargement du .sys, le loader kernel résout cet import vers l’adresse de l’export dans ntoskrnl.exe.
Le premier argument n’est pas le nom d’une fonction Windows : c’est l’adresse d’un callback PCREATE_PROCESS_NOTIFY_ROUTINE_EX implémenté dans l’image .sys du driver EDR. Avec Remove à FALSE, le Process Manager ajoute cette adresse à son état de notifications de processus ; avec Remove à TRUE, il retire l’inscription et attend la fin des invocations en cours. Le contrat documente une liste de routines inscrites, mais ni son symbole privé, ni sa représentation mémoire, ni son adresse ne sont stables entre les builds. Lors d’une création, le callback reçoit un pointeur PEPROCESS, qui désigne l’objet noyau représentant le processus, ainsi qu’un identifiant de processus et une structure PS_CREATE_NOTIFY_INFO contenant les informations documentées disponibles à cet instant.
Dans la suite, l’identifiant de processus correspond au Process Identifier (PID) et l’identifiant de thread au Thread Identifier (TID). Ces identifiants sont pratiques pour relier des événements, mais leur valeur peut être réutilisée après la fin de l’objet.
L’appel d’enregistrement et le chemin ultérieur de livraison de l’événement sont donc distincts. Le driver appelle une fois l’export de ntoskrnl.exe, puis Windows appelle directement l’adresse du callback située dans le driver à chaque notification correspondante. Les symboles privés représentant le conteneur interne peuvent être utiles au débogage d’un build donné, mais ne doivent pas être présentés comme des interfaces architecturales stables.
Le callback Ex s’exécute à PASSIVE_LEVEL dans le contexte du thread qui crée le processus. L’Interrupt Request Level (IRQL) est le niveau de priorité d’exécution du noyau ; plus il est élevé, plus les opérations autorisées sont limitées. PASSIVE_LEVEL est son niveau ordinaire et le moins contraignant. Le callback n’est pas purement observationnel : l’affectation d’une erreur à CreationStatus peut empêcher la création. L’utilisation de cette capacité par un EDR dépend du produit et de sa politique. EPROCESS est la structure interne par laquelle le noyau représente un processus ; un pointeur PEPROCESS y fait référence, mais ne constitue pas une copie sérialisée de tous ses champs. Aucun champ ne doit être présenté comme collecté si le capteur ne le lit pas et ne l’enregistre pas explicitement.
Intérêt pour la détection. Les relations parent-enfant, l’identité du créateur, la provenance de l’image et la sémantique de la ligne de commande peuvent alimenter les analyses de chaînes de processus. Un token est l’objet Windows qui porte l’identité et les privilèges de sécurité d’un processus ou d’un thread ; un broker est un processus intermédiaire chargé d’effectuer une opération pour un autre. Aucun de ces éléments n’est concluant isolément. Le parentage modifié, les créations via broker, les images renommées et les lignes de commande incomplètes imposent une corrélation avec les tokens, handles, images et signaux d’exécution.
L’extrait WDK suivant reste volontairement limité au contrat documenté du callback. ProcessNotify, StartProcessProbe et StopProcessProbe sont des fonctions d’exemple compilées dans le .sys, pas des exports Windows. UNREFERENCED_PARAMETER est une macro de compilation fournie par les headers du WDK pour signaler qu’un paramètre est volontairement inutilisé ; aucun code de fonction n’est recherché dans un module au runtime. L’extrait distingue une notification de fin (CreateInfo == NULL) d’une notification de création et traite les chaînes optionnelles comme telles. DbgPrintEx, déclaré dans wdm.h, lié par NtosKrnl.lib et exécuté depuis ntoskrnl.exe dans ce contexte kernel, écrit un message vers le débogueur ; il convient à une sonde dans un lab contrôlé, pas au transport de télémétrie d’un produit.
#include <ntddk.h>
static VOID ProcessNotify( PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo){ UNREFERENCED_PARAMETER(Process);
// A NULL CreateInfo denotes process exit. if (CreateInfo == NULL) { return; }
// ImageFileName is documented as optional. if (CreateInfo->ImageFileName != NULL) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "create pid=%p image=%wZ\n", ProcessId, CreateInfo->ImageFileName); }}
NTSTATUS StartProcessProbe(VOID){ return PsSetCreateProcessNotifyRoutineEx(ProcessNotify, FALSE);}
VOID StopProcessProbe(VOID){ (VOID)PsSetCreateProcessNotifyRoutineEx(ProcessNotify, TRUE);}La création du processus établit son identité et sa lignée, mais elle ne décrit pas les chemins d’exécution qui apparaissent ensuite en son sein. Les notifications de threads ajoutent ces unités d’exécution au graphe sans déterminer, à elles seules, ce qu’elles exécutent.
Création et fin de thread.
PsSetCreateThreadNotifyRoutine est exporté par ntoskrnl.exe, déclaré dans ntddk.h et référencé par un driver via NtosKrnl.lib. Il reçoit l’adresse d’un callback situé dans le .sys appelant ; le gestionnaire de threads conserve cette inscription dans un état kernel dont le layout n’est pas documenté. Le callback classique ne reçoit que ProcessId, ThreadId et un booléen Create. ETHREAD est la structure interne qui représente un thread dans le noyau, mais la signature classique du callback ne fournit aucun argument de ce type. Lors de la création, la routine s’exécute dans le contexte du thread ayant créé le nouveau thread, à un IRQL inférieur ou égal à APC_LEVEL. Une Asynchronous Procedure Call (APC) est une fonction placée en attente pour être exécutée dans le contexte d’un thread ; APC_LEVEL impose donc davantage de restrictions que PASSIVE_LEVEL.
L’événement identifie le processus cible et le nouveau thread, mais n’établit pas à lui seul une injection de thread distant, qui consiste à provoquer l’exécution d’un thread dans un autre processus pour lui faire exécuter du code choisi. Une conclusion robuste exige d’autres preuves : contexte créateur, accès préalable à un handle de processus, opérations de mémoire virtuelle, provenance de l’adresse de départ ou call stack, la liste des fonctions imbriquées ayant conduit à l’opération.
PsSetCreateThreadNotifyRoutineEx est la variante étendue montrée entre crochets dans le schéma. Elle réside elle aussi dans ntoskrnl.exe, avec la déclaration ntddk.h et l’import NtosKrnl.lib, mais son paramètre de type de notification modifie le contrat et, pour certains types, le contexte d’exécution du callback. La suppression utilise PsRemoveCreateThreadNotifyRoutine, exporté par le même module, déclaré dans le même header et importé par la même bibliothèque. L’API exacte et le type de notification doivent donc être relevés pendant les tests plutôt que déduits du seul nom « thread callback ».
L’existence d’un thread ne révèle pas encore quelles images exécutables sont introduites dans son processus. Les notifications de mapping ajoutent cette provenance de code, tout en restant distinctes de son exécution effective.
Mapping d’images exécutables.
PsSetLoadImageNotifyRoutine est exporté par ntoskrnl.exe, déclaré dans ntddk.h et importé par le driver via NtosKrnl.lib. Il reçoit l’adresse d’une routine PLOAD_IMAGE_NOTIFY_ROUTINE située dans le .sys du driver ; le Process Manager conserve l’inscription dans son état kernel, sans exposer le conteneur interne comme contrat. Le driver doit la retirer avant son déchargement au moyen de PsRemoveLoadImageNotifyRoutine, fourni par le même module, le même header et la même bibliothèque d’import.
Le mapping crée une section image puis en projette une vue dans l’espace d’adressage virtuel du processus. Une page mémoire est une unité de taille fixe que le gestionnaire de mémoire associe à une adresse virtuelle. L’expression « pages d’un fichier » désigne ici des pages virtuelles dont le contenu est adossé aux octets de l’EXE ou de la DLL ; elle ne signifie pas que le fichier est naturellement découpé en pages mémoire ni qu’il est copié intégralement en RAM. Windows peut charger le contenu d’une page à la demande lors de son premier accès. Le mapping prépare donc l’accès au code et aux données sans signifier que ce code a déjà été exécuté.
Windows invoque la routine après ce mapping et avant le point d’entrée, la première adresse à laquelle le chargeur doit transférer l’exécution. Le callback reçoit un nom complet optionnel, l’identifiant du processus cible et des données IMAGE_INFO telles que l’adresse de base et la taille. Dans le schéma, LoadLibrary désigne la famille LoadLibraryA et LoadLibraryW : ces API user-mode sont déclarées dans libloaderapi.h, liées par Kernel32.lib et exportées par Kernel32.dll. Elles demandent le chargement d’un module, mais n’appellent pas directement le callback EDR ; le loader crée le mapping, puis Windows distribue séparément la notification kernel.
La routine retourne VOID, donc aucune valeur, et ne peut pas renvoyer directement une décision d’autorisation ou de blocage. Une signature numérique relie un fichier à un éditeur et permet de vérifier qu’il n’a pas été modifié depuis sa signature. Un hash est une empreinte calculée à partir du contenu ; la prévalence indique à quelle fréquence ce fichier a été observé dans le parc. Ces informations, comme les catalogues de signatures et la réputation, ne sont pas des champs intrinsèques du callback. Un capteur peut les ajouter par d’autres mécanismes.
La première partie du parcours a établi l’identité du processus, ses unités d’exécution et les images introduites dans son espace d’adressage. Elle ne dit pas encore ce que ce processus demande aux autres objets Windows ni quelles traces il laisse sur le système.
Acte II : interactions avec le système
Le fil rouge quitte donc l’intérieur du processus. Les callbacks du registre décrivent les changements de configuration qui peuvent lui survivre, les callbacks d’objets révèlent les droits demandés sur d’autres processus ou threads, puis les minifilters suivent les effets produits sur le stockage.
Opérations sur le registre.
Le registre est la base de configuration hiérarchique de Windows. Son sous-système noyau, le Configuration Manager, permet à un driver de filtrage de s’enregistrer via CmRegisterCallbackEx. Cette fonction est exportée par ntoskrnl.exe, déclarée dans wdm.h et importée par le driver au moyen de NtosKrnl.lib. Elle reçoit notamment l’adresse du callback présent dans le .sys, l’altitude du filtre, l’objet représentant le driver et un contexte optionnel. Le Configuration Manager conserve l’inscription et retourne un cookie opaque, un identifiant que le driver doit fournir à CmUnRegisterCallback pour se désinscrire. Cette fonction de retrait appartient elle aussi à ntoskrnl.exe et au contrat wdm.h/NtosKrnl.lib. Le conteneur kernel associé au cookie reste un détail d’implémentation.
Les opérations sont identifiées par REG_NOTIFY_CLASS. Dans le schéma, Reg* et Zw* sont des noms de familles, pas deux fonctions uniques. RegSetValueExW constitue un exemple user-mode : winreg.h le déclare, Advapi32.lib fournit son import et Advapi32.dll exporte son point d’entrée. ZwSetValueKey est l’exemple kernel correspondant pour un driver : il est déclaré dans wdm.h, importé via NtosKrnl.lib et exporté par ntoskrnl.exe. Ces appelants atteignent le Configuration Manager ; ils n’appellent pas eux-mêmes le callback EDR, que le gestionnaire distribue selon les inscriptions actives.
La plupart des classes d’opérations exposent des notifications pre et post accompagnées de structures propres à l’opération. Un pre-callback peut inspecter, bloquer ou, dans les cas supportés, modifier une opération ; un post-callback observe le statut et la sortie obtenus. Les champs disponibles dépendent de la classe de notification. Affirmer que chaque événement contient un chemin complet, un PID, un TID, les données de la valeur et les permissions est donc excessif. La résolution des noms et l’attribution de l’appelant peuvent demander un traitement supplémentaire au capteur.
Intérêt pour la détection. Les clés de persistance, configurations de fournisseurs de sécurité, définitions de services et modifications de politiques prennent leur sens lorsqu’elles sont corrélées à l’identité initiatrice, à la lignée de processus, à la confiance de l’image et à la séquence temporelle. L’accès au registre n’identifie pas à lui seul l’intention.
Le registre expose les changements de configuration, mais pas les droits qu’un processus demande sur un autre processus ou thread. Les callbacks d’objets ajoutent cette relation interprocessus et peuvent, pour les droits supportés, réduire l’accès avant la création du handle.
Opérations sur les objets processus et thread.
Dans ce mécanisme, chaque handle porte un masque de droits sur l’objet Windows visé. ObRegisterCallbacks est exporté par ntoskrnl.exe, déclaré dans wdm.h et importé via NtosKrnl.lib. Le driver lui transmet une structure d’enregistrement contenant l’altitude, les types d’objets et les adresses de ses routines pre-operation et post-operation situées dans son propre .sys. ObjectPreCallback et ObjectPostCallback désignent ces rôles de callback et leurs signatures, pas deux fonctions implémentées par ntoskrnl.exe pour le compte du driver. L’Object Manager conserve l’ensemble inscrit et retourne un handle d’inscription opaque. ObUnRegisterCallbacks, fourni par le même module et le même contrat de compilation, consomme ce handle pour retirer les callbacks avant le déchargement du driver. La structure kernel située derrière ce handle n’est pas publique.
OB_OPERATION_HANDLE_CREATE et OB_OPERATION_HANDLE_DUPLICATE distinguent la création et la duplication de handles. Les fonctions OpenProcess et DuplicateHandle montrées dans le schéma sont des API user-mode exportées par Kernel32.dll et importées avec Kernel32.lib ; leurs déclarations se trouvent respectivement dans processthreadsapi.h et handleapi.h. Elles produisent des requêtes sur des objets Windows. Elles n’appellent pas directement le callback EDR : l’Object Manager reçoit l’opération kernel et distribue les pre/post-callbacks déjà enregistrés.
Ce mécanisme permet de protéger des processus sensibles en limitant des droits liés à l’accès mémoire, à la création de threads, à la modification de contexte ou à la duplication de handles. Les handles kernel et les opérations issues de composants de confiance ou protégés doivent être interprétés avec prudence. Un handle bloqué ou réduit ne démontre pas une intention de vol d’identifiants sans le comportement environnant.
Le cœur de politique ci-dessous montre le modèle d’application supporté. ProcessHandlePreOperation est le callback compilé dans le .sys du driver, tandis que IsProtectedTarget est un prédicat d’exemple appartenant à ce même driver, pas une fonction Windows. PsProcessType n’est pas une fonction : c’est un symbole de donnée déclaré par le WDK et exporté par ntoskrnl.exe, dont la valeur identifie le type d’objet processus auprès de l’Object Manager. L’enregistrement, le choix de l’altitude, la durée de vie de la cible et la synchronisation de la politique sont omis, mais l’opération sur le masque utilise les structures pre-operation documentées. Le prédicat de cible doit rester étroit ; appliquer ce masque à tous les processus casserait des logiciels légitimes.
static OB_PREOP_CALLBACK_STATUS ProcessHandlePreOperation( PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION Info){ UNREFERENCED_PARAMETER(RegistrationContext);
if (Info->ObjectType != *PsProcessType || !IsProtectedTarget((PEPROCESS)Info->Object)) { return OB_PREOP_SUCCESS; }
ACCESS_MASK *desired = NULL; if (Info->Operation == OB_OPERATION_HANDLE_CREATE) { desired = &Info->Parameters->CreateHandleInformation.DesiredAccess; } else if (Info->Operation == OB_OPERATION_HANDLE_DUPLICATE) { desired = &Info->Parameters->DuplicateHandleInformation.DesiredAccess; }
if (desired != NULL) { *desired &= ~(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE); } return OB_PREOP_SUCCESS;}Les callbacks d’objets décrivent les droits qu’un processus demande sur un autre objet, mais ils ne couvrent pas les effets produits sur le stockage. Pour observer la création, l’écriture, le renommage ou la suppression de fichiers, puis corréler ces opérations avec leur processus initiateur dans le pipeline EDR, le capteur utilise une autre couche du noyau : les minifilters du système de fichiers.
Effets sur le système de fichiers.
Un minifilter est un driver spécialisé dans la surveillance des opérations sur les fichiers. Il s’enregistre auprès du Filter Manager (FltMgr.sys), le composant Windows qui organise ces filtres, puis reçoit des callbacks avant ou après les opérations qu’il a déclarées. Un volume est la cible logique portant un système de fichiers, par exemple une partition montée ; l’attachement d’un minifilter à un volume constitue une instance.
Cette inscription passe par FltRegisterFilter, une fonction exportée par FltMgr.sys, déclarée dans fltkernel.h et importée au moyen de FltMgr.lib. Le minifilter l’appelle depuis son DriverEntry, la fonction d’entrée compilée dans son propre .sys que Windows invoque au chargement du driver. Il lui fournit une structure FLT_REGISTRATION contenant notamment les adresses de ses callbacks, elles aussi situées dans ce .sys. FltMgr ajoute le filtre à sa liste globale et retourne un pointeur PFLT_FILTER opaque. FltStartFiltering, fourni par le même module, le même header et la même bibliothèque d’import, autorise ensuite l’attachement aux volumes et la livraison des I/O. FltUnregisterFilter, de même provenance, retire l’inscription au déchargement. Les structures internes situées derrière le pointeur opaque ne font pas partie du contrat.
Dans le modèle de drivers Windows, une grande partie de ces opérations est transportée par des paquets de requête d’entrée/sortie (I/O Request Packet, IRP), des structures qui décrivent au noyau l’action demandée et son état. L’altitude attribuée positionne l’instance dans la pile ; le nom d’un produit ne définit aucun ordre fixe entre EDR et antivirus.
Pour une opération donnée, les pre-operation callbacks s’exécutent de l’altitude la plus haute vers le système de fichiers. La complétion remonte ensuite dans les post-operation callbacks de l’altitude la plus basse vers la plus haute. Un pre-callback peut laisser passer la requête, demander un post-callback, la mettre en attente, la synchroniser ou la terminer avec un statut final. La présence et le contexte d’exécution du post-callback dépendent du résultat pre-operation et du type d’opération.
Intérêt pour la détection. Les opérations de création, écriture, renommage, disposition, c’est-à-dire la demande de suppression d’un fichier, synchronisation de section et modification de métadonnées peuvent alimenter les détections de ransomware, de staging, la préparation ou le regroupement de données avant leur transfert, d’altération et de collecte. L’entropie mesure ici la dispersion des valeurs d’octets et peut signaler un contenu fortement transformé ou chiffré, sans constituer une preuve à elle seule. Le chemin, le contenu, l’entropie, le contexte du volume et l’attribution au processus sont des enrichissements produit, non un schéma universel d’événement minifilter.
Un minifilter déclare les opérations qu’il souhaite recevoir. Cette table statique ne crée aucun rapport d’ordre fixe avec le filtre d’un autre fournisseur ; FltMgr l’associe à l’altitude de l’instance au runtime. Dans l’extrait, PreCreate, PostCreate, PreWrite, PostWrite, PreSetInformation et PostSetInformation sont des fonctions fournies par le minifilter et compilées dans son .sys, pas des exports de FltMgr.sys.
CONST FLT_OPERATION_REGISTRATION Operations[] = { { IRP_MJ_CREATE, 0, PreCreate, PostCreate }, { IRP_MJ_WRITE, 0, PreWrite, PostWrite }, { IRP_MJ_SET_INFORMATION, 0, PreSetInformation, PostSetInformation },
{ IRP_MJ_OPERATION_END }};Les interactions avec le registre, les objets et les fichiers montrent ce que le processus touche localement. Elles restent toutefois réparties entre plusieurs contrats et ne décrivent ni toute la chronologie d’exécution ni, à elles seules, la connexion réseau qui clôt la séquence.
Acte III : transport et reconstruction de la télémétrie
ETW ajoute des événements structurés à la chronologie, WFP expose les points de contrôle réseau et l’instrumentation user-mode conserve le contexte des API effectivement traversées. Ces sources complètent le graphe, puis le service EDR doit encore déterminer quelles observations concernent la même entité et dans quel ordre elles se sont produites.
Event Tracing for Windows (ETW) est l’infrastructure de télémétrie intégrée à Windows : le noyau et les applications y publient des événements structurés décrivant leur activité. Un EDR peut les collecter et les corréler pour reconstituer une chronologie, par exemple la création d’un processus ou le chargement d’une Dynamic Link Library (DLL), une bibliothèque de code réutilisable chargée par un processus, sans devoir modifier directement le code observé.
Un provider est le composant qui produit les événements. Un contrôleur démarre une session, c’est-à-dire le canal temporaire et ses buffers mémoire, puis choisit les providers à activer. Un consumer lit les événements de cette session en temps réel ou depuis un fichier Event Trace Log (ETL). Un service EDR peut être à la fois contrôleur et consumer, mais le contrôleur ne relaie pas chaque événement entre le provider et le consumer.
Pour un provider user-mode classique, EventRegister inscrit l’identité du provider et retourne un handle d’inscription, puis EventWrite publie un événement avec ce handle. Ces deux fonctions publiques sont déclarées dans evntprov.h, importées via Advapi32.lib et exportées par Advapi32.dll. Les frameworks ETW générés peuvent les encapsuler, mais les fonctions du provider restent dans son EXE ou sa DLL ; ETW reçoit les données par ces points d’entrée au lieu de copier le code du provider dans le système.
Les enregistrements peuvent contenir un identifiant de provider, un identifiant et une version d’événement, un niveau représentant sa gravité, un keyword utilisé comme catégorie d’activation, un horodatage, des PID et TID, des identifiants d’activité et un payload, c’est-à-dire les données propres à l’événement. Son schéma provient d’un manifeste, des métadonnées auto-descriptives TraceLogging, du Managed Object Format (MOF) ou d’un fichier Trace Message Format (TMF), qui indiquent au consumer comment décoder les champs. ETW ne garantit ni l’activation d’un provider, ni la conservation de chaque événement, ni sa pertinence en matière de sécurité.
Les événements PowerShell, .NET, kernel et Microsoft-Windows-Threat-Intelligence appartiennent à des familles de providers distinctes, avec des conditions d’activation et d’accès différentes. .NET est la plateforme Microsoft qui exécute du code managé, c’est-à-dire du code dont l’exécution et la mémoire sont supervisées par un environnement dédié. AMSI est une interface séparée d’inspection de contenu. Ses signaux peuvent être corrélés à ETW, mais AMSI ne constitue pas une catégorie de provider ETW.
ETW apporte du contexte d’exécution et une chronologie, mais son rôle de collecte ne fournit pas l’arbitrage propre aux différentes couches réseau. WFP expose ces points de contrôle et peut rattacher une décision au flux, à l’application ou à l’utilisateur selon la couche utilisée.
Télémétrie réseau avec WFP.
Windows Filtering Platform (WFP) est l’infrastructure de filtrage intégrée à la pile réseau Windows. La pile réseau est la succession de composants qui transforme les données d’une application en paquets, puis effectue le trajet inverse à la réception. WFP expose des couches, c’est-à-dire des points de contrôle correspondant à des étapes précises de ce traitement.
À chaque couche, un filtre WFP est une règle composée de conditions et d’une action. Le moteur de filtrage évalue ces règles ; il peut autoriser ou bloquer directement le trafic, ou appeler un callout, une fonction fournie par un driver pour effectuer un traitement spécialisé. Le Base Filtering Engine (BFE, moteur de filtrage de base) gère la configuration de la plateforme. Les couches Application Layer Enforcement (ALE, application des politiques au niveau applicatif) ajoutent notamment l’identité de l’application ou de l’utilisateur à la décision.
Le BFE coordonne la politique et la configuration persistante en mode utilisateur ; la classification, c’est-à-dire la comparaison du trafic avec les filtres applicables, s’exécute en mode noyau aux couches réseau et transport. Un flux regroupe les échanges appartenant à une même communication. Aux couches ALE, ses métadonnées peuvent inclure l’application, l’utilisateur, le protocole, ainsi que les adresses et ports locaux ou distants. Leur disponibilité dépend de la couche et du type d’événement.
Un callout EDR ne se place donc pas avant WFP. Il est invoqué parce qu’un filtre correspondant à une couche sélectionne ce callout. Sa fonction de classification est un callback compilé dans le .sys du driver EDR, pas une fonction générique dont le code résiderait dans WFP. Le moteur kernel WFP appelle cette adresse enregistrée avec les métadonnées de la couche ; le callback peut les inspecter et contribuer à l’action selon l’arbitrage WFP, les règles de priorité qui déterminent l’action finale lorsque plusieurs filtres correspondent. Le payload réseau est la donnée transportée par les paquets ; le réassemblage de flux reconstruit une suite continue à partir de ces paquets. Leur inspection et la sémantique finale de blocage dépendent de la couche, de l’implémentation du callout et des droits associés à l’action.
WFP décrit la communication et son identité applicative, mais pas nécessairement le chemin d’API qui a conduit le processus à demander cette communication. L’instrumentation user-mode complète cette vue en observant les appels qui traversent effectivement les fonctions instrumentées.
Instrumentation des API en mode utilisateur.
Certains EDR injectent ou chargent un capteur user-mode et détournent des exports, les fonctions qu’un module rend appelables par d’autres composants, dans des bibliothèques telles que ntdll.dll. Un inline hook modifie les premières instructions d’une fonction, appelées son prologue, afin de brancher l’exécution vers le capteur. Un trampoline est un petit bloc de code qui rejoue les instructions déplacées puis ramène l’exécution vers la suite de la fonction originale.
Le schéma emploie NtCreateThreadEx comme exemple concret. Sur les builds Windows modernes observés, ce point d’entrée de la Native API est exporté par ntdll.dll ; son stub user-mode prépare la transition vers le service kernel correspondant. Il ne constitue pas ici une API Win32 documentée comme stable, et son stub comme son identifiant d’appel système doivent être vérifiés sur le build examiné. À un niveau public plus élevé, CreateRemoteThreadEx est déclaré dans processthreadsapi.h, importé via Kernel32.lib et exporté par Kernel32.dll. Ces deux niveaux ne doivent pas être confondus : l’API Win32 fournit un contrat applicatif, tandis que l’export ntdll.dll représente le passage natif montré pour expliquer le detour.
Les identifiants et stubs d’appels système dépendent de l’architecture et du build. Un stub est la courte séquence de code machine qui prépare l’identifiant de l’opération puis effectue la transition vers le noyau. La fixation d’un identifiant unique comme technique Windows générale est incorrecte. Microsoft Visual C++ ne supporte pas non plus l’assembleur inline pour l’architecture x86 64 bits (x64) sous la forme souvent présentée dans les exemples simplifiés. Un outil de recherche doit résoudre et valider le build cible au lieu de supposer un stub fixe.
Du signal local à l’événement corrélable
Accumuler des événements provenant de plusieurs capteurs ne suffit pas à reconstruire un comportement. Le pipeline doit d’abord déterminer quelles observations décrivent la même entité, dans quel ordre elles se sont produites et quelles absences relèvent du contrat de l’API ou d’une perte de collecte.
La première difficulté consiste à conserver une identité stable malgré la réutilisation des identifiants Windows et la durée de vie limitée des objets kernel.
Les identifiants Windows constituent des clés de corrélation pratiques, mais pas des identités permanentes. Les PID et TID peuvent être réutilisés après la fin d’un objet. Une époque désigne ici une période d’exécution unique délimitée par le redémarrage de Windows ou du capteur. Un modèle robuste associe donc l’identifiant à l’heure de création et à cette époque, puis idéalement à un identifiant d’entité généré par le produit. La corrélation tardive d’un événement réseau ou fichier avec le propriétaire actuel d’un PID réutilisé peut autrement créer une fausse lignée.
Les callbacks kernel exposent également les objets à des étapes différentes de leur cycle de vie. Un callback de création de processus s’exécute avant que le thread initial commence son exécution, tandis qu’un callback d’image arrive après la création du mapping mais avant l’exécution de son point d’entrée. Un post-callback objet observe une opération de handle terminée. Ces événements décrivent des transitions d’état différentes et ne doivent pas être réduits à un horodatage générique tel que « démarrage du processus ».
Les pointeurs d’objets ne sont valides que selon le contrat et les règles de durée de vie du callback qui les reçoit. Conserver un pointeur brut pour un traitement asynchrone, donc exécuté plus tard, sans prendre une référence appropriée est dangereux. Les capteurs de production copient généralement les champs stables dans un enregistrement borné et délèguent l’enrichissement coûteux à un worker, une tâche ou un thread de fond, ou au service user-mode.
Une identité d’entité stable indique quels événements peuvent être rapprochés, mais elle n’établit pas leur ordre causal. Il faut également préserver le temps de génération et le domaine d’horloge de chaque source.
Temps d’événement et ordre causal.
L’ordre d’invocation des callbacks n’est pas l’ordre d’arrivée au backend. Un buffer est une zone mémoire temporaire qui absorbe un écart de vitesse entre le producteur et le lecteur ; le batching regroupe plusieurs événements pour les transporter ensemble. Les événements peuvent traverser des buffers par unité centrale de traitement (Central Processing Unit, CPU), files kernel-vers-user, stockage local, compression, batching, transport et ingestion cloud avant de devenir interrogeables. Deux enregistrements produits dans un ordre kernel connu peuvent être observés dans l’ordre inverse après des chemins de buffering indépendants.
Plusieurs horloges peuvent coexister : temps système, temps d’interruption, compteur de performance haute résolution, timestamps ETW, temps de réception par le service local et temps d’ingestion backend. Un domaine d’horloge est la source de temps et l’origine utilisées pour produire un timestamp. Un modèle défendable préserve ce domaine original et documente l’incertitude de conversion. Le temps backend ne doit pas remplacer le temps de génération pour reconstruire des séquences subsecondes de processus, threads, handles ou flux réseau.
Même une chronologie correctement ordonnée reste trompeuse si l’absence d’un champ optionnel est interprétée comme une preuve d’altération. La sémantique de chaque callback doit donc précéder toute conclusion sur les données manquantes.
Champs optionnels, résolution des noms et races.
Le caractère optionnel documenté a des conséquences analytiques. ImageFileName et CommandLine dans PS_CREATE_NOTIFY_INFO peuvent être absents. FullImageName dans un callback de chargement peut être nul. Les callbacks registre reçoivent des structures propres à chaque opération plutôt qu’un chemin universel entièrement résolu. Les métadonnées réseau diffèrent selon les couches WFP. L’absence d’une donnée ne démontre donc pas une altération.
Les noms constituent aussi des vues mutables des objets. Une race apparaît lorsque l’objet change entre l’événement et sa résolution ultérieure. Un reparse point peut rediriger la résolution d’un chemin, tandis qu’un hard link donne plusieurs noms au même fichier. Un fichier peut donc être renommé ou résolu différemment après sa création ; les capteurs qui enrichissent de manière asynchrone doivent distinguer les valeurs capturées au moment de l’événement de celles résolues plus tard.
L’optionalité explique pourquoi un événement valide peut manquer d’un champ ; elle n’explique pas la disparition d’enregistrements entiers. À l’échelle du système, le débit, les files et les buffers introduisent une seconde source d’incertitude.
Débit, backpressure et perte d’événements.
La backpressure apparaît lorsqu’un producteur génère des événements plus vite que le composant suivant ne peut les traiter. Chaque chemin de collecte possède donc un budget de performance : les callbacks kernel s’exécutent dans des contextes contraints, les minifilters se trouvent sur des chemins I/O sensibles à la latence, les callouts WFP peuvent affecter le débit réseau et les sessions ETW utilisent des buffers finis. Un capteur peut échantillonner, agréger, supprimer des répétitions ou abandonner des événements de faible priorité. En mode fail-open, il laisse l’opération continuer lors d’une panne ou d’une saturation afin de préserver la disponibilité, au prix d’une visibilité ou d’un contrôle réduit.
Ces contraintes déterminent ce que le produit peut connaître avec confiance, mais pas encore ce qu’il peut empêcher. Il faut séparer la capacité d’observation de chaque source, son éventuelle influence synchrone et les réponses ultérieures du produit.
Acte IV : de l’observation à la décision
Le fil rouge est maintenant corrélé : le produit peut relier le processus, ses images, ses accès aux objets, ses fichiers et sa connexion. Cette connaissance ne dit pas encore à quel moment une action peut être empêchée. L’interface native détermine ce qu’un callback peut effectuer de façon synchrone ; les capacités de réponse du produit peuvent ajouter des actions ultérieures, mais elles ne doivent pas être attribuées à l’API de collecte elle-même.
| Mécanisme | Observation native | Influence synchrone native | Frontière importante |
|---|---|---|---|
| Process notify Ex | Création, fin et métadonnées documentées | CreationStatus peut faire échouer la création | Le produit peut employer le callback uniquement pour la télémétrie |
| Thread notify | PID cible, TID et état création/suppression | Aucun retour allow/deny documenté | L’injection exige une corrélation au-delà du callback |
| Image-load notify | Métadonnées du mapping d’image | Aucun retour allow/deny ; callback VOID | Le mapping existe déjà lors de la notification |
| Registry callback | Données pre/post typées | Le pre-callback peut bloquer ou modifier les opérations supportées | Les champs et modifications dépendent de REG_NOTIFY_CLASS |
| Object callback | Création ou duplication d’un handle process/thread | Les bits d’accès supportés peuvent être retirés | Le pre-callback retourne success ; ce n’est pas un hook de refus générique |
| Minifilter pre-op | Opérations de système de fichiers enregistrées | Laisser passer, pendre, modifier, synchroniser ou terminer | L’ordre dépend de l’altitude et des opérations enregistrées |
| Consumer ETW | Événements émis vers une session activée | Aucune via le rôle de consumer | Activation du provider et rétention sont des décisions séparées |
| Callout WFP | Métadonnées de flux, stream, packet ou ALE propres à la couche | La classification peut contribuer à permit/block | Droits, arbitrage et données disponibles varient selon la couche |
| Detour user-mode | Arguments, contexte appelant, retour et données produit | Le hook peut altérer ou refuser l’appel user-mode | Seuls les appels traversant le chemin instrumenté sont observés |
La réponse asynchrone doit être modélisée séparément. Un capteur peut observer un mapping d’image puis terminer le processus quelques millisecondes plus tard, ou enregistrer un flux réseau et isoler l’hôte après corrélation backend. Ce comportement est préventif au niveau produit, sans constituer une prévention synchrone par la source initiale de l’événement.
Une API peut offrir une capacité d’action et rester inutilisable si le composant n’est pas suffisamment approuvé, protégé ou maintenu actif. La confiance plateforme et l’autoprotection déterminent donc si cette capacité reste disponible face à une tentative d’altération.
Confiance plateforme, exécution protégée et autoprotection.
Les API d’enregistrement kernel imposent leurs propres conditions de confiance. Les callbacks processus Ex peuvent par exemple échouer si l’image contenant le callback ne possède pas la caractéristique d’intégrité exigée ; l’enregistrement d’object callbacks peut être refusé si les routines ne résident pas dans une image kernel signée. Ces contrôles augmentent le coût d’un enregistrement arbitraire sans garantir la sécurité de chaque driver signé.
Protected Process Light (PPL) est un niveau de protection Windows qui réserve certaines opérations sur un processus aux composants possédant un niveau de signature suffisant. Il peut limiter l’inspection user-mode ou l’acquisition de handles par un composant EDR dont le niveau de confiance est insuffisant. Certains produits de sécurité emploient des services protégés et des processus antimalware-light ; leur configuration exacte reste propre au fournisseur et au déploiement.
Un hyperviseur est la couche logicielle qui crée et isole des environnements d’exécution sous le système principal. Virtualization-Based Security (VBS, sécurité fondée sur la virtualisation) utilise cette isolation pour protéger certaines décisions et données. Code Integrity est le mécanisme Windows qui vérifie qu’un binaire respecte les exigences de signature et de politique avant son exécution. Hypervisor-Protected Code Integrity (HVCI, intégrité du code protégée par l’hyperviseur) s’appuie sur l’environnement isolé pour renforcer cette vérification du code autorisé à s’exécuter dans le noyau. La blocklist Microsoft refuse des drivers connus comme vulnérables, tandis qu’App Control applique une politique définissant les exécutables et drivers autorisés. Ces mécanismes réduisent l’exposition aux drivers connus ou non fiables, mais ne transforment pas les données kernel non documentées en frontière de sécurité supportée.
Kernel Patch Protection, souvent appelé PatchGuard, vérifie périodiquement l’intégrité de certains codes et structures critiques du noyau sur les systèmes 64 bits supportés. Il ne doit pas être décrit comme un détecteur complet, immédiat et publiquement spécifié de toute manipulation de callbacks ou d’ETW. Sa couverture et sa temporisation sont des détails d’implémentation. Un crash après une modification non supportée démontre une instabilité plateforme, pas un résultat fiable de détection EDR.
L’autoprotection du produit ajoute une couche. Une liste de contrôle d’accès (Access Control List, ACL) indique quelles identités peuvent agir sur un service ou un device de driver. Un watchdog surveille qu’un composant reste actif et tente de le relancer ou de signaler sa défaillance. Services protégés, contrôles de santé des callbacks, intégrité des modules, signature de configuration et état backend peuvent aussi contribuer. L’anti-tamper, qui vise à empêcher ou détecter la modification du produit, reste distinct de la détection comportementale.
Acte V : ce qui disparaît et ce qui reste visible après le contournement d’un capteur
Supposons maintenant que l’un des capteurs rencontrés dans le parcours soit contourné. Pour savoir ce qui change réellement, il faut suivre le signal au-delà de sa collecte et vérifier si les étapes suivantes disposent encore d’autres observations capables de décrire l’activité.
Un pipeline EDR est la succession d’étapes qui transforme des observations brutes de l’endpoint en données exploitables, puis éventuellement en détection ou en action. La collecte n’en constitue que la première étape ; la qualité de détection dépend aussi de la normalisation, de l’enrichissement, de l’état et de la corrélation.
| Étape | Fonction typique | Principaux modes de défaillance |
|---|---|---|
| Collecte | Recevoir callbacks, événements, I/O, flux et signaux user-mode | Capteur désactivé, callback absent, provider non activé, perte d’événements |
| Normalisation | Convertir les enregistrements produit dans un schéma interne stable | Champs manquants, identité ambiguë, dérive d’horloge ou de schéma |
| Enrichissement | Résoudre signature, hash, réputation, token, chemin, lignée et contexte asset | Dépendance réseau, cache périmé, races de chemin, objets inaccessibles |
| Corrélation | Relier les événements entre processus, threads, sessions et hôtes | Réutilisation d’identifiants, fenêtres incomplètes, échantillonnage, délai backend |
| Décision | Scorer, alerter, bloquer, isoler ou collecter des preuves | Différences de politique, seuil de modèle, suppression, latence de réponse |
La présence d’un callback ne doit pas être assimilée à une visibilité complète. Inversement, le contournement d’un callback ou d’un detour user-mode ne doit pas être assimilé à une perte de détection. L’unité d’analyse pertinente est le graphe complet du comportement et l’ensemble des capteurs indépendants capables de le décrire.
Le pipeline montre que collecte, normalisation, corrélation et décision peuvent échouer indépendamment. Évaluer une évasion consiste donc à localiser précisément l’étape altérée, puis à mesurer les preuves et actions qui subsistent dans les autres couches.
Lire une évasion comme une perte de visibilité.
Une technique d’évasion doit être décrite par le chemin de visibilité précis qu’elle affecte. Les expressions « désactiver l’EDR » ou « devenir invisible » sont trop larges sans identification du produit, de sa version, de sa politique, de l’état des capteurs et du résultat mesuré.
Le premier cas concerne les capteurs les plus proches du code user-mode : contourner leur detour modifie un chemin d’observation, sans supprimer les transitions que Windows expose ailleurs.
Unhooking user-mode et appels système directs.
Ces techniques peuvent supprimer ou contourner certains detours user-mode. Les preuves résiduelles peuvent inclure les callbacks de processus et de threads, opérations de handles, mappings d’images, ETW, WFP, les minifilters, l’état de la mémoire distante et les corrélations backend. La restauration des hooks peut elle-même produire des anomalies d’intégrité ou de protection mémoire.
Une fois le detour contourné, ETW demeure un chemin distinct pour les providers activés. Son altération doit être qualifiée par processus, fonction de publication, session et famille de providers affectés.
Altération d’ETW.
Microsoft décrit EtwEventWrite dans ntetw.h comme une fonction interne au système, susceptible de changer entre les versions. Sur les builds modernes observés, son point d’entrée user-mode est exporté par ntdll.dll ; cette localisation décrit l’implémentation examinée, pas un contrat applicatif stable. Un provider compatible doit préférer l’API publique EventWrite, déjà située plus haut dans Advapi32.dll. Lorsque son chemin de publication atteint ntdll.dll!EtwEventWrite, modifier cette copie dans un processus n’affecte que les appels qui la traversent dans ce processus. Cela ne désactive ni les providers kernel, ni les autres processus, ni les autres points d’entrée ETW, ni les capteurs non ETW. La modification de pages exécutables peut également constituer un signal d’intégrité observable. L’état ETW kernel est spécifique à l’implémentation et ne doit pas être représenté par un offset ou un layout stable.
L’altération d’un chemin ETW user-mode ne modifie pas les callbacks enregistrés séparément dans le noyau. Agir sur ces callbacks déplace donc le problème vers une primitive privilégiée et des structures propres au build.
Manipulation des callbacks kernel.
La suppression d’un callback par des structures internes non documentées exige une primitive d’écriture kernel, c’est-à-dire une capacité permettant de modifier une adresse choisie dans la mémoire du noyau, ainsi qu’une découverte propre au build. Un code de contrôle d’entrée/sortie (Input/Output Control, IOCTL) est une commande structurée envoyée par un processus à un driver ; il n’existe pas d’IOCTL Windows générique « remove callback ». Un driver vulnérable expose son propre protocole de device et ses propres primitives. L’exploitation doit donc implémenter sa sémantique de lecture/écriture et résoudre l’état kernel cible sur le build exact. Les mécanismes d’intégrité, l’autoprotection du produit, le réenregistrement des callbacks, les crashs et les autres capteurs modifient le résultat.
Une primitive kernel peut notamment provenir de l’abus d’un driver légitimement signé mais vulnérable. La question passe alors du contournement d’un capteur à la chaîne de confiance qui autorise le chargement et l’accès au device de ce driver.
Drivers signés vulnérables.
Une signature valide établit une relation de confiance pour le chargement ; elle ne garantit pas l’absence de vulnérabilité exploitable. Les contrôles modernes comprennent HVCI, la blocklist Microsoft des drivers vulnérables, les politiques App Control et une règle Attack Surface Reduction (ASR, réduction de la surface d’attaque) contre l’abus de drivers signés vulnérables. Leur couverture n’est pas absolue et l’état de la blocklist doit être vérifié sur l’endpoint testé.
Épilogue : mesurer la couverture dans un lab
Le parcours a montré qu’une activité unique devient plusieurs observations, puis que la disparition d’une source n’a de sens qu’en comparaison avec les preuves restantes. Le lab sert à mesurer ces différences sans confondre le contrat de Windows, l’implémentation d’un build et le comportement d’un produit.
Établir la portée de chaque preuve
Trois classes de preuves doivent rester séparées :
- Les contrats documentés regroupent les interfaces de programmation d’applications (Application Programming Interface, API) Windows publiques, que les programmes appellent, et les interfaces de drivers de périphériques (Device Driver Interface, DDI), qui définissent comment un driver dialogue avec le noyau, la partie centrale et privilégiée du système. Les signatures de callbacks et les règles d’ordonnancement font aussi partie de ces contrats. Ils constituent le fondement le plus solide des affirmations architecturales.
- Les détails d’implémentation observés comprennent les symboles privés, tableaux internes, offsets de structures et routines produit obtenues par reverse engineering, c’est-à-dire par l’étude du binaire et de son exécution lorsque le code source n’est pas disponible. Un symbole de débogage associe une adresse machine à un nom de fonction, de variable ou de type ; privé signifie ici qu’il ne constitue pas une interface publique supportée. Un offset est la distance d’un champ par rapport au début d’une structure ; son layout est l’organisation complète des champs en mémoire. Un build est une version compilée précise du logiciel. Ces détails ne sont valides que pour les builds Windows et produit examinés.
- Le comportement produit correspond à la télémétrie réellement conservée, enrichie, transmise et évaluée par une version et une politique EDR précises. Il doit être mesuré et ne peut pas être déduit de la seule disponibilité d’un callback.
La sixième édition de Windows Internals couvre Windows 7 et Windows Server 2008 R2. Elle reste utile pour les concepts architecturaux, mais ne constitue pas un contrat d’implémentation actuel. Le Windows Driver Kit (WDK) regroupe la documentation, les en-têtes et les outils nécessaires au développement de drivers Windows ; sa documentation courante et la septième édition de Windows Internals prévalent pour les interfaces supportées. WinDbg est le débogueur Microsoft utilisé pour observer l’état et l’exécution de Windows. Les symboles privés, offsets et observations tirés du débogage restent propres au build examiné.
Le débogage kernel et le reverse engineering deviennent nécessaires lorsque la question de recherche porte sur l’implémentation réelle plutôt que sur le contrat documenté. Ils exigent un format de preuve plus strict.
ETW Threat Intelligence (ETW-TI) est un provider Windows orienté sécurité qui publie certaines opérations sensibles ; il reste une source ETW parmi d’autres, soumise à ses propres conditions d’accès et d’activation. Le désassemblage traduit le code machine d’un binaire en instructions processeur ; le pseudocode en propose une représentation de plus haut niveau, plus lisible mais reconstruite par l’outil. Les routines de notification, enregistrements de callbacks, nœuds de minifilters et entrées encodées peuvent ainsi être étudiés à l’aide de symboles privés et du désassemblage. Une primitive issue d’un driver vulnérable peut parfois permettre de modifier la mémoire kernel. Ces techniques établissent seulement que le build examiné peut être inspecté ou altéré ; elles ne rendent pas portables les noms de symboles internes, tailles de tableaux, encodages de pointeurs, layouts de listes ou offsets de champs.
Chaque affirmation portant sur un internal devrait enregistrer :
- les versions, hashes, timestamps et adresses de chargement exacts du kernel et des drivers ;
- la source du symbol server, le dépôt qui fournit les informations de débogage, l’identité du fichier Program Database (PDB) associé au binaire et l’état de chargement de ces symboles, qui relient des adresses machine à des noms, fonctions et types ;
- l’emplacement de désassemblage ou de pseudocode établissant l’utilisation de l’objet interne ;
- le layout de structure dérivé des symboles ou du code, et non copié depuis un autre build ;
- les hypothèses de synchronisation et de durée de vie requises avant toute lecture ou modification ;
- les états avant et après, y compris réenregistrement du callback, santé du capteur, stabilité système et télémétrie résiduelle.
La propriété d’une routine par un driver de sécurité doit être établie en résolvant son adresse vers le module chargé, puis en examinant l’appel d’enregistrement ou l’implémentation de la routine. Des labels tels que WdFilter.sys, MsSecFlt.sys ou un driver tiers sont des observations sur un système, pas des membres obligatoires d’une liste de callbacks Windows. L’ordre de chargement, la version produit, les fonctions activées et les composants de sécurité plateforme en modifient la population.
Les expériences de suppression sont particulièrement faciles à surinterpréter. Effacer une entrée, détacher un nœud, désactiver un flag d’activation ou restaurer des octets peut produire un état temporaire tout en laissant intacts des pointeurs en cache, des références de rundown, qui empêchent la destruction d’un objet encore utilisé, des files de workers, watchdogs, callbacks alternatifs et preuves backend. Une écriture réussie n’équivaut pas à un contournement propre ou durable du capteur.
Ces exigences de preuve fournissent également une grille de lecture des erreurs récurrentes. Lorsqu’une analyse saute une étape entre enregistrement, livraison, enrichissement et décision, elle transforme une observation locale en conclusion générale injustifiée.
Erreurs d’analyse fréquentes.
- Confondre enregistrement et flux d’événements. Une API de setup appelée lors de l’initialisation du driver n’est pas traversée par chaque événement.
- Présenter des symboles privés comme architecture publique. Les noms et offsets internes exigent un build et une source de preuve explicites.
- Inventer des champs d’événement universels. Arguments du callback, enrichissement produit et schéma backend sont des couches séparées.
- Assimiler notification et prévention. Les callbacks
VOIDne retournent pas de décision ; la réponse asynchrone du produit doit être décrite séparément. - Assimiler un bypass à la désactivation de l’EDR. Le hook, provider, callback, callout ou la file affectés doivent être nommés précisément.
- Prendre une alerte pour seule vérité terrain. La télémétrie brute, la prévention, la santé du capteur et l’ingestion backend doivent aussi être examinées.
- Omettre les contrôles négatifs. Un test exige une baseline, c’est-à-dire une capture de référence comparable, et une opération bénigne de même forme API.
- Ignorer la récupération. Watchdogs, réenregistrement, redémarrage, actualisation de politique et mises à jour peuvent annuler une modification transitoire.
- Supposer un numéro d’appel système fixe. L’architecture et le build Windows déterminent le stub ; les exemples codés en dur, donc fixés directement dans le code, vieillissent immédiatement.
- Assimiler driver signé et comportement fiable. Signature, état de vulnérabilité, couverture de blocklist et politique runtime sont des propriétés distinctes.
Comparer les preuves résiduelles
Ces erreurs d’analyse se corrigent par un protocole qui sépare le comportement généré, le chemin de collecte testé et le résultat observé. Un plan de test exhaustif doit faire varier ces dimensions indépendamment ; le tableau décrit les preuves minimales et non une alerte attendue propre à un produit.
| Famille de test | Source primaire testée | Sources résiduelles indépendantes | Preuves de résultat requises |
|---|---|---|---|
| Création de processus | Callback Process Ex | Mapping image, ETW, lignée de handles, audit de service | Statut de création, événement brut, lignée d’entité, action de politique |
| Comportement de thread distant | Thread callback ou hook user-mode | Object callbacks, mémoire, ETW-TI, état de la cible | Identités source/cible, droits, adresse de départ, résultat du thread |
| Introduction d’image | Callback image-load | Minifilter, Code Integrity, scan mémoire, ETW | État du mapping, signature/hash enrichis, résultat d’exécution |
| Persistance registre | Registry callback | Lignée processus, télémétrie services/tâches, activation ultérieure | Statut pre/post, clé et valeur résolues, identité initiatrice |
| Accès à un processus sensible | Object callback | Événements d’audit, lectures mémoire, threads, état de protection | Accès original/réduit, handle accordé, opération suivante |
| Transformation de fichiers | Minifilter | Process/thread, analyse de contenu ou entropie, volume | Résultat pre/post, identité chemin/octets, débit et périmètre |
| Script ou code managé | Chemin ETW/AMSI | Processus, images, fichiers, réseau, mémoire | Activation provider, contenu disponible, comportement aval |
| Connexion réseau | WFP/ALE | Domain Name System (DNS), lignée processus, proxy, Transport Layer Security (TLS), logs distants | Couche, direction, endpoints, identité application, action |
| Contournement d’un hook user | Export instrumenté | Callbacks kernel, ETW, WFP, minifilter, mémoire | Intégrité du hook, chemin réel, graphe d’événements résiduel |
| Chargement ou abus d’un driver | Code Integrity et politique drivers | Callback image, registre service, minifilter, télémétrie kernel | Décision de chargement, politique, hash/version, accès device |
La conclusion la plus solide est rarement binaire. La couverture peut être complète, partielle, retardée, échantillonnée, enrichie seulement dans le backend ou suffisante pour détecter malgré l’absence d’un événement primaire. Chaque dimension doit être enregistrée séparément.
La matrice indique quelles sources comparer et quelles preuves conserver. Elle ne rend cependant pas l’expérience reproductible sans un protocole qui fige l’environnement, contrôle les variables et distingue les couches du résultat.
Protocole de recherche reproductible.
Une expérimentation défendable sur les internals EDR doit enregistrer assez d’état pour permettre la reproduction ou la contestation de la conclusion.
- Figer l’environnement. Relever l’édition, le build et le niveau de servicing Windows, l’état de la virtualisation et de VBS, les versions du produit et du capteur EDR, l’identifiant de politique et la connectivité réseau.
- Définir le comportement. Décrire l’opération de processus, thread, image, registre, objet, fichier, ETW ou réseau générée. Un nom d’alerte ne constitue pas une définition du comportement.
- Inventorier les capteurs attendus. Relever les callbacks documentés, drivers chargés, instances et altitudes des minifilters, sessions ETW et providers activés, filtres et callouts WFP et modules user-mode.
- Capturer une référence. Exécuter un contrôle bénin de même forme API et collecter les enregistrements locaux et backend avant toute condition d’évasion.
- Modifier une seule variable. Désactiver, contourner ou altérer un seul chemin de collecte en conservant le comportement et la politique.
- Mesurer la visibilité résiduelle. Comparer les événements bruts, enregistrements normalisés, alertes, actions de prévention, temps d’arrivée backend et effets de bord endpoint.
- Répéter après redémarrage et mise à jour. Les callbacks, sessions de providers, blocklists et protections peuvent être restaurés ou modifiés par le servicing et les contrôles de santé du capteur.
Les résultats doivent distinguer au minimum quatre situations : l’événement ciblé disparaît ; l’événement reste mais perd des champs ; la télémétrie reste tandis que l’alerte disparaît ; ou la prévention change tandis que la télémétrie demeure. Ces résultats appartiennent à des couches différentes du pipeline et ne doivent pas être regroupés sous une affirmation unique de « bypass réussi ».
L’inventaire en lecture seule suivant fournit un point de départ reproductible sur un hôte Windows du lab. Il n’identifie pas chaque capteur EDR, mais capture trois surfaces fréquemment mal comprises avant le début du test.
$EvidenceRoot = Join-Path $env:TEMP ("edr-surface-" + (Get-Date -Format "yyyyMMdd-HHmmss"))New-Item -ItemType Directory -Path $EvidenceRoot | Out-Null
fltmc filters | Out-File (Join-Path $EvidenceRoot "minifilters.txt")logman query -ets | Out-File (Join-Path $EvidenceRoot "etw-sessions.txt")logman query providers | Out-File (Join-Path $EvidenceRoot "etw-providers.txt")
$WfpState = Join-Path $EvidenceRoot "wfp-state.xml"netsh wfp show state "file=$WfpState"
Get-CimInstance Win32_SystemDriver | Select-Object Name, State, StartMode, PathName | Export-Csv (Join-Path $EvidenceRoot "drivers.csv") -NoTypeInformationCet instantané doit être associé aux versions du système d’exploitation (operating system, OS) et du capteur, à l’état de la politique, aux timestamps et aux preuves backend. fltmc confirme les instances et altitudes des minifilters, logman distingue les providers enregistrés des sessions actives et netsh wfp exporte l’état WFP et Internet Protocol Security (IPsec) courant.
Revenir au fil rouge
Reprenons la séquence d’ouverture. La création du processus établit une identité et une lignée. Les notifications d’image décrivent des mappings exécutables, sans décider si ces mappings sont malveillants. Un callback objet peut observer ou réduire l’accès demandé à un autre processus, tandis qu’une notification de thread ultérieure indique seulement qu’un thread est apparu. Les télémétries fichier et WFP ajoutent des effets extérieurs au processus. ETW peut apporter du contexte d’exécution. La valeur analytique naît de leur relation temporelle, pas d’un événement isolé.
Une affirmation sur la visibilité EDR peut donc être examinée avec quatre questions :
- Quelle transition d’état Windows s’est produite ? Un comportement doit être décrit indépendamment d’une alerte ou d’un nom d’outil.
- Quel mécanisme documenté peut l’observer ou l’influencer ? Enregistrement, livraison, normalisation, détection et réponse sont des étapes distinctes.
- Quels champs et quelles garanties d’ordre existent réellement ? Les données optionnelles, la réutilisation des PID, le transport asynchrone et les pertes d’événements limitent chaque conclusion.
- Quelles preuves indépendantes subsistent si ce mécanisme disparaît ? Les signaux résiduels de processus, handles, images, fichiers, ETW ou réseau déterminent si une évasion retire un événement ou rompt réellement la chaîne analytique.
Ce modèle mental ne vaut que si chaque affirmation conserve le niveau de preuve qui la supporte. La dernière étape consiste donc à distinguer contrat public, observation propre à un build et comportement mesuré d’un produit.
Hiérarchie des sources et limites.
Le WDK public définit les interfaces supportées, mais pas toutes les structures internes de dispatch utilisées par un build Windows particulier. Windows Internals explique les concepts d’implémentation tout en avertissant explicitement que les internals non documentés peuvent évoluer. Les symboles de débogage kernel et le reverse engineering peuvent établir le fonctionnement d’un build donné ; ils n’établissent pas un contrat compatible avec les versions futures. Le comportement d’un fournisseur exige une observation directe, car l’existence d’une fonction OS ne permet pas de déduire la manière dont un produit la configure ou la consomme.
Une expérimentation fondée sur les symboles WinDbg et une primitive de lecture ou d’écriture issue d’un driver vulnérable peut établir le comportement du système examiné. Elle ne prouve pas que les offsets, layouts internes, propriétaires de callbacks ou états de providers sont identiques sur un autre build.
Comprendre où se trouvent les fonctions Windows citées
Une fonction citée dans l’article peut intervenir à trois endroits différents. Son module d’exécution contient le code chargé, ou le stub qui transfère vers l’étape suivante. Son header fournit au compilateur le prototype et les types. Sa bibliothèque d’import .lib permet au linker d’inscrire la dépendance dans l’image Portable Executable (PE), mais elle ne contient pas le code utilisé au runtime. Le loader résout cette dépendance et écrit l’adresse obtenue dans l’Import Address Table (IAT), la table d’adresses d’import de l’application ou du driver.
| Fonctions citées | Module d’exécution | Déclaration et import | Rôle dans le parcours |
|---|---|---|---|
PsSetCreateProcessNotifyRoutineEx, fonctions de notification de threads et d’images, CmRegisterCallbackEx, ObRegisterCallbacks, DbgPrintEx, ZwSetValueKey | ntoskrnl.exe | ntddk.h ou wdm.h, puis NtosKrnl.lib | Enregistrement, retrait ou service fourni par le noyau |
FltRegisterFilter, FltStartFiltering, FltUnregisterFilter | FltMgr.sys | fltkernel.h, puis FltMgr.lib | Inscription et activation d’un minifilter |
EventRegister, EventWrite, RegSetValueExW | Advapi32.dll | evntprov.h ou winreg.h, puis Advapi32.lib | Publication ETW ou accès user-mode au registre |
LoadLibraryA/W, OpenProcess, DuplicateHandle, CreateRemoteThreadEx | Kernel32.dll | Header Win32 correspondant, puis Kernel32.lib | Contrat public appelé par une application |
NtCreateThreadEx, et EtwEventWrite sur les builds observés | ntdll.dll | Interface Native API ou interne à qualifier selon le build | Stub user-mode proche de la transition native ; il ne doit pas être présenté comme un contrat Win32 stable |
Les callbacks ProcessNotify, ProcessHandlePreOperation, PreCreate, PostCreate et les autres routines fournies par l’EDR ne résident dans aucun de ces modules Windows. Leur code est compilé dans le fichier .sys, l’EXE ou la DLL du produit. L’API Windows reçoit leur adresse lors de l’enregistrement, puis le gestionnaire concerné conserve une inscription qui permet de les rappeler. Le contrat peut garantir ce rappel sans publier le tableau, la liste ou le layout kernel qui matérialise l’inscription sur un build donné.
Cette distinction permet de relire chaque fonction sans interrompre le fil principal : le module indique où se trouve le code Windows, le header et la bibliothèque expliquent comment le composant y fait référence, et l’adresse du callback indique où commence réellement le code du capteur.
L’article établit ici les contrats des capteurs et leurs limites de corrélation. Neutralisation d’EDR poursuit l’analyse sur un cas plus étroit : déterminer quand l’interface IOCTL d’un driver signé accorde réellement une primitive de terminaison non autorisée, sans confondre import kernel, atteignabilité et vulnérabilité.
Références
- Microsoft, bibliothèques et headers des drivers et format PE, imports et IAT.
- Microsoft, PsSetCreateProcessNotifyRoutineEx, PS_CREATE_NOTIFY_INFO et DbgPrintEx.
- Microsoft, PsSetCreateThreadNotifyRoutine, PsSetCreateThreadNotifyRoutineEx et PCREATE_THREAD_NOTIFY_ROUTINE.
- Microsoft, PsSetLoadImageNotifyRoutine, PLOAD_IMAGE_NOTIFY_ROUTINE, mapping de fichiers et LoadLibraryW.
- Microsoft, CmRegisterCallbackEx, REG_NOTIFY_CLASS, RegSetValueExW et ZwSetValueKey.
- Microsoft, ObRegisterCallbacks, OB_OPERATION_REGISTRATION, OpenProcess et DuplicateHandle.
- Microsoft, handles d’objets et priorités matérielles et IRQL.
- Microsoft, FltRegisterFilter, FltStartFiltering, ordre des callbacks pre/post et groupes de chargement et altitudes.
- Microsoft, Présentation d’Event Tracing, EventRegister, EventWrite et EtwEventWrite, fonction interne.
- Microsoft, Antimalware Scan Interface.
- Microsoft,
logman query. - Microsoft, architecture de Windows Filtering Platform, composants WFP et Base Filtering Engine, Application Layer Enforcement et fonctionnement de WFP.
- Microsoft,
netsh wfp. - Microsoft, Detours : Using Detours et CreateRemoteThreadEx.
- Microsoft, contrôle des entrées et sorties de périphériques avec IOCTL et protection des services antimalware.
- Microsoft, règles recommandées de blocage des drivers.
- Microsoft, intégrité de la mémoire, VBS et HVCI.
- Yosifovich, Ionescu, Russinovich et Solomon, Windows Internals, septième édition, première partie, Microsoft Press, 2017.
- Russinovich, Solomon et Ionescu, Windows Internals, sixième édition, première partie, Microsoft Press, 2012. Consulté uniquement pour l’architecture historique.
- Altered Security, Evasion Lab Manual, objectifs d’apprentissage 9 à 11, 2025. Support de cours hors ligne consulté pour sa méthodologie de débogage propre au lab.