Internals EDR : architecture de télémétrie et limites de l'évasion
Publié le 38 min de lecture
Une analyse sourcée des chemins de télémétrie Endpoint Detection and Response (EDR, détection et réponse sur les endpoints) sous Windows, de leurs contrats documentés, de leurs limites d’application et d’une méthodologie de validation en red team.
Considérons une séquence courte : un processus démarre, mappe une image, ouvre un handle vers un autre processus, crée un thread, é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.
Cette distinction constitue le fil conducteur de l’article. La question utile n’est pas seulement de savoir si un callback ou un hook existe, mais ce que le 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. Aucune architecture unique ne décrit tous les produits : l’emplacement des capteurs, les schémas d’événements, la mise en mémoire tampon, 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.
Cet article distingue trois classes de preuves :
- Les contrats documentés regroupent les interfaces de programmation d’applications (Application Programming Interface, API) Windows publiques, les interfaces de pilotes de périphériques (Device Driver Interface, DDI), les signatures de callbacks et les règles d’ordonnancement. 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. Ils 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 fournie avec le matériel de recherche 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. La documentation du Windows Driver Kit (WDK) courant et la septième édition de Windows Internals prévalent pour les interfaces supportées. Le manuel Evasion Lab a servi de source secondaire pour la méthodologie WinDbg et de reverse engineering ; ses symboles privés, offsets et observations de lab sont explicitement traités comme propres à un build.
Architecture EDR multicouche
Une architecture EDR se comprend plus facilement en séparant les lieux où un comportement se produit, ceux où il peut être observé et ceux où il est finalement interprété. Un produit associe généralement un service en mode utilisateur à un ou plusieurs pilotes 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.
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 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 pilotes. Il arbitre notamment la création des processus, les accès aux objets, les opérations de fichiers et une partie du traitement réseau. Un pilote EDR peut s’enregistrer auprès de mécanismes documentés pour être rappelé lorsque ces transitions se produisent. 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 par des appels système, des requêtes d’entrée/sortie (input/output, I/O), des objets partagés sous contrôle du noyau et des files d’événements. Ils ne constituent donc pas deux copies complètes de la même réalité : chaque couche expose un contexte et des garanties différents.
À quoi sert chaque composant
| 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 pilote n’est pas nécessairement conservée, enrichie ou transmise |
| Pilote 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 pilote 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 traçage de Windows, organisée autour de providers, sessions, contrôleurs et consumers | Transporte des événements structurés du noyau ou des applications vers un consumer temps réel ou un fichier | Un provider enregistré n’est pas forcément activé ; une session active ne garantit pas la rétention de chaque événement |
| Antimalware Scan Interface (AMSI) | Une interface d’inspection de contenu intégrée par certaines applications, notamment des moteurs de scripts | Soumet du contenu déchiffré ou reconstruit à un fournisseur antimalware au moment choisi par l’application hôte | AMSI n’est ni un provider ETW, ni un capteur universel de tous les processus |
| Minifilter de fichiers | Un pilote branché au Filter Manager de Windows pour une sélection d’opérations de système de fichiers | Observe ou influence des créations, lectures, écritures, renommages ou suppressions selon son enregistrement | Son altitude ordonne les filtres ; elle ne garantit pas qu’un événement sera analysé avant toute autre couche |
| Windows Filtering Platform (WFP) | La plateforme de filtrage de la pile réseau Windows | Classe le trafic à des couches précises et peut invoquer un callout EDR sélectionné par un filtre | Les métadonnées disponibles et la capacité de blocage dépendent de la couche WFP invoquée |
| 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.
Parcours concret d’un événement
Prenons le lancement d’un interpréteur de scripts qui charge une bibliothèque, ouvre un fichier puis initie une connexion. Windows peut notifier la création du processus au pilote, publier des événements ETW, signaler le mapping de l’image et présenter les opérations de fichiers au minifilter. Si l’interpréteur intègre AMSI, le contenu du script peut aussi être soumis à l’interface. La connexion atteint ensuite 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 pilote 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 providers, l’échantillonnage, les exclusions, la politique locale et l’ingestion backend peuvent chacun modifier l’ensemble de preuves final.
Télémétrie des callbacks kernel
Création de processus
PsSetCreateProcessNotifyRoutineEx enregistre un callback PCREATE_PROCESS_NOTIFY_ROUTINE_EX. Lors d’une création, ce callback reçoit un pointeur PEPROCESS, un identifiant de processus et une structure PS_CREATE_NOTIFY_INFO. Les champs documentés comprennent notamment l’identifiant du processus parent, les identifiants du processus et du thread créateurs, l’objet fichier de l’exécutable, le nom de l’image et la ligne de commande lorsqu’ils sont disponibles.
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 distincts. Les symboles privés représentant par exemple un tableau interne de callbacks 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. Il 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. Un pointeur PEPROCESS n’équivaut pas à une structure EPROCESS sérialisée ; 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. 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. Il distingue une notification de fin (CreateInfo == NULL) d’une notification de création et traite les chaînes optionnelles comme telles. DbgPrintEx 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);}Création et fin de thread
PsSetCreateThreadNotifyRoutine enregistre un callback dont la signature classique ne contient que ProcessId, ThreadId et un booléen Create. La documentation ne définit aucun argument ETHREAD. Lors de la création, la routine s’exécute dans le contexte du thread ayant créé le nouveau thread, à un niveau de requête d’interruption (Interrupt Request Level, IRQL) inférieur ou égal à APC_LEVEL, le niveau associé aux Asynchronous Procedure Calls (APC).
L’événement identifie le processus cible et le nouveau thread, mais n’établit pas à lui seul une injection de thread distant. 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. Les variantes étendues modifient le contrat d’exécution disponible ; l’API d’enregistrement exacte doit donc être relevée pendant les tests.
Mapping d’images exécutables
PsSetLoadImageNotifyRoutine enregistre une routine PLOAD_IMAGE_NOTIFY_ROUTINE. Windows l’invoque après le mapping d’une image exécutable en mémoire virtuelle et avant l’exécution de son point d’entrée. Le callback reçoit un nom complet optionnel, l’identifiant du processus cible et des données IMAGE_INFO comprenant des propriétés de mapping telles que l’adresse de base et la taille.
La routine retourne VOID et ne peut pas renvoyer directement une décision d’autorisation ou de blocage. Le statut de signature, la réputation du hash, les informations de catalogue et la prévalence ne sont pas des champs intrinsèques du callback. Un capteur peut enrichir l’événement par d’autres mécanismes. Une action préventive exige un contrôle distinct ou un chemin de réponse propre au produit.
Opérations sur le registre
Sur les versions actuelles de Windows, un pilote de filtrage du registre s’enregistre via CmRegisterCallbackEx. Le Configuration Manager invoque le callback pour des opérations identifiées par REG_NOTIFY_CLASS. Les API de registre user-mode et les routines kernel Zw* peuvent toutes deux atteindre ce chemin.
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.
Opérations sur les objets processus et thread
ObRegisterCallbacks enregistre des routines pre-operation et post-operation pour les opérations supportées sur les handles de processus, de threads et de bureaux. OB_OPERATION_HANDLE_CREATE et OB_OPERATION_HANDLE_DUPLICATE distinguent la création et la duplication de handles.
Un ObjectPreCallback doit retourner OB_PREOP_SUCCESS ; il ne renvoie pas un statut arbitraire de refus. Pour les droits de processus et de thread modifiables, le callback peut retirer des bits de DesiredAccess, mais ne peut pas ajouter de droits absents de la requête initiale. Un post-callback peut observer le statut final et l’accès accordé, sans modifier rétroactivement l’opération terminée.
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é. 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;}Minifilters du système de fichiers
FltMgr.sys gère les instances de minifilters attachées aux volumes. Chaque minifilter s’enregistre seulement pour les opérations d’entrée/sortie (input/output, I/O) sélectionnées. Dans le modèle de pilotes Windows, celles-ci sont transportées par des paquets de requête d’entrée/sortie (I/O Request Packet, IRP). L’altitude attribuée détermine la position relative du minifilter 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, synchronisation de section et modification de métadonnées peuvent alimenter les détections de ransomware, staging, altération et collecte. 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.
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 }};Event Tracing for Windows
ETW comprend des providers, sessions, contrôleurs et consumers. Un contrôleur démarre et configure une session puis active des providers. Les providers activés écrivent leurs événements dans les buffers de session. Un consumer lit ces événements en temps réel ou depuis un fichier Event Trace Log (ETL). Un service EDR peut cumuler les rôles de contrôleur et de consumer, mais le contrôleur ne constitue pas une étape intermédiaire du flux d’événements.
Les enregistrements peuvent contenir un identifiant de provider, un identifiant et une version d’événement, un niveau, un keyword, un horodatage, des PID et TID, des identifiants d’activité et un payload défini par le provider. Le schéma exact provient du manifeste, des métadonnées TraceLogging, du Managed Object Format (MOF) ou d’un fichier Trace Message Format (TMF). ETW est une infrastructure de transport et d’instrumentation ; elle 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. 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.
Télémétrie réseau avec WFP
Windows Filtering Platform expose des couches de filtrage à des points sélectionnés de la pile réseau. Des shims extraient les valeurs classifiables et invoquent le filter engine. Les filtres évaluent leurs conditions à une couche et peuvent sélectionner une action intégrée ou invoquer un callout enregistré pour 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) permettent notamment une classification tenant compte de l’application et de l’utilisateur.
Le BFE coordonne la politique et la configuration persistante en mode utilisateur ; la classification aux couches réseau et transport s’exécute en mode noyau. Aux couches ALE, les métadonnées peuvent relier un flux à l’identité d’une application ou d’un utilisateur, au protocole et aux extrémités locales ou distantes. 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. La fonction de classification peut inspecter les métadonnées et contribuer à l’action selon les règles d’arbitrage WFP. L’inspection de payload, le réassemblage de flux 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.
Instrumentation des API en mode utilisateur
Certains EDR injectent ou chargent un capteur user-mode et détournent des exports sélectionnés de modules tels que ntdll.dll. Un inline hook courant remplace le prologue d’une fonction par un branchement vers le code du capteur, produit la télémétrie, puis utilise un trampoline pour exécuter les instructions déplacées avant de reprendre le chemin original.
Ce pattern n’est pas universel. Les produits peuvent employer d’autres stratégies d’injection, frameworks d’instrumentation, sources d’événements et politiques de processus protégés. Un appel système direct peut contourner le detour d’un export user-mode, mais il ne contourne ni l’appel système lui-même ni les callbacks kernel, providers ETW, couches WFP, minifilters, contrôles de handles, scanners mémoire ou corrélations interprocessus indépendants.
Les identifiants et stubs d’appels système dépendent de l’architecture et du build. 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.
Sémantique des capteurs et qualité des données
Identité et durée de vie des objets
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. Un modèle robuste associe donc l’identifiant à l’heure de création, à l’époque de boot ou du capteur et, 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 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 ou au service user-mode.
Temps d’événement et ordre causal
L’ordre d’invocation des callbacks n’est pas l’ordre d’arrivée au backend. 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, interrupt time, performance counter, timestamps ETW, temps de réception par le service local et temps d’ingestion backend. Un modèle défendable préserve le domaine d’horloge 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.
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. Un fichier peut être renommé après sa création ; un chemin peut inclure des reparse points, device paths, hard links ou redirecteurs réseau ; le nom d’une image processus peut diverger de l’état ultérieur sur disque. 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.
Débit, backpressure et perte d’événements
Chaque chemin de collecte possède 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, abandonner des événements de faible priorité ou fonctionner en mode fail-open lorsqu’une file est saturée.
Les contrôleurs ETW peuvent exposer des statistiques de buffers et d’événements perdus. Les files produit peuvent publier des compteurs de santé, logs de diagnostic ou heartbeats. Un résultat négatif reste ininterprétable sans les indicateurs de santé et de perte. « Aucun événement trouvé » peut signifier une absence d’instrumentation, un filtrage, une perte, un retard d’ingestion, l’expiration de la rétention, une erreur de requête ou un contournement effectif.
Frontières entre observation et application
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.
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 pilote signé.
Protected Process Light restreint l’accès aux processus protégés selon le niveau de signature et l’opération demandée. Ce mécanisme 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.
Virtualization-Based Security (VBS, sécurité fondée sur la virtualisation) et Hypervisor-Protected Code Integrity (HVCI, intégrité du code protégée par l’hyperviseur) déplacent ou appliquent certaines décisions d’intégrité depuis un environnement isolé. La blocklist des pilotes vulnérables et App Control ajoutent une politique sur les images kernel autorisées au chargement. Ces mécanismes réduisent l’exposition aux pilotes 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 surveille certains codes et structures kernel critiques 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 : listes de contrôle d’accès (Access Control List, ACL) des services et des devices de pilotes, services protégés, watchdogs, contrôles de santé des callbacks, intégrité des modules, signature de configuration et état de santé backend peuvent tous contribuer. Anti-tamper et détection comportementale restent distincts. Empêcher l’arrêt d’un service ne prouve pas la visibilité d’une attaque, et détecter une modification mémoire ne prouve pas la collecte du comportement initial.
De la télémétrie à la détection
La collecte ne constitue que la première étape d’un pipeline EDR. La qualité de détection dépend 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.
Limites de l’évasion
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é.
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.
Altération d’ETW
Le patch de EtwEventWrite dans un processus affecte les appels traversant cette copie et ce chemin de code. Il 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.
Manipulation des callbacks kernel
La suppression d’un callback par des structures internes non documentées exige une primitive d’écriture kernel et une découverte propre au build. L’opération ne correspond pas à un code de contrôle d’entrée/sortie (Input/Output Control, IOCTL) générique « remove callback ». Un pilote vulnérable expose son propre protocole de device et ses propres primitives ; l’exploitation doit implémenter la sémantique de lecture/écriture du pilote 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.
Pilotes 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 Hypervisor-Protected Code Integrity, la blocklist Microsoft des pilotes vulnérables, les politiques App Control et la règle Attack Surface Reduction contre l’abus de pilotes signés vulnérables. Leur couverture n’est pas absolue et l’état de la blocklist doit être vérifié sur l’endpoint testé.
Internals propres au build et reverse engineering
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.
Le manuel de lab fourni énumère les routines de notification de processus, threads et images à l’aide de symboles kernel privés, examine les enregistrements de callbacks registre et objets, parcourt les callback nodes des minifilters et résout l’état du provider ETW Threat Intelligence (ETW-TI). Il masque également les entrées encodées de callbacks et utilise une primitive issue d’un pilote vulnérable pour modifier la mémoire kernel. Ces techniques démontrent que le build du lab peut être inspecté et 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 pilotes ;
- la source du symbol server, l’identité du fichier Program Database (PDB), l’état de chargement des symboles et la présence éventuelle d’informations de types privées ;
- 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 pilote 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 pilote 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, références de rundown, files de workers, watchdogs, callbacks alternatifs et preuves backend. Une écriture réussie n’équivaut pas à un contournement propre ou durable du capteur.
Erreurs d’analyse fréquentes
- Confondre enregistrement et flux d’événements. Une API de setup appelée lors de l’initialisation du pilote 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 comparable et une opération bénigne de même forme API.
- Ignorer la récupération. Watchdogs, réenregistrement, reboot, refresh de politique et mises à jour peuvent annuler une modification transitoire.
- Supposer un numéro de syscall fixe. L’architecture et le build Windows déterminent le stub ; les exemples hardcodés vieillissent immédiatement.
- Assimiler pilote signé et comportement fiable. Signature, état de vulnérabilité, couverture de blocklist et politique runtime sont des propriétés distinctes.
Matrice de couverture expérimentale
Un plan de test exhaustif doit faire varier indépendamment le comportement et le chemin de collecte. 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 pilote | Code Integrity et politique pilotes | 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.
Méthodologie de recherche
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, pilotes 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") -NoTypeInformationCe snapshot 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.
Un modèle mental durable
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.
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 démontre pas la manière dont un produit la configure ou la consomme.
Les sections du manuel Evasion Lab consacrées à l’énumération de callbacks, à la manipulation des minifilters et à ETW Threat Intelligence démontrent une méthode de lab fondée sur les symboles WinDbg et une primitive de lecture/écriture issue d’un pilote vulnérable. Ces exercices constituent une preuve de méthode sur le build fourni, pas une preuve que les offsets, layouts internes, propriétaires de callbacks ou états de providers sont identiques sur un autre système.
Références
- Microsoft, PsSetCreateProcessNotifyRoutineEx et PS_CREATE_NOTIFY_INFO.
- Microsoft, PsSetCreateThreadNotifyRoutine et PCREATE_THREAD_NOTIFY_ROUTINE.
- Microsoft, PsSetLoadImageNotifyRoutine et PLOAD_IMAGE_NOTIFY_ROUTINE.
- Microsoft, Enregistrement des notifications du registre et REG_NOTIFY_CLASS.
- Microsoft, ObRegisterCallbacks, OB_OPERATION_REGISTRATION et OB_PRE_CREATE_HANDLE_INFORMATION.
- Microsoft, Concepts du Filter Manager, ordre des callbacks pre/post et groupes de chargement et altitudes.
- Microsoft, Présentation d’Event Tracing.
- 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.
- Microsoft, règles recommandées de blocage des pilotes.
- 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.