Un bypass EDR universel découvert dans Windows 10 

En étudiant les mécanismes internes d’un système utilisé par tous les EDR pour obtenir de l’information sur les activités des processus sous Windows, nous avons découvert un moyen pour un processus malveillant de désactiver la génération de certains événements de sécurité liés aux interactions entre processus. Cette technique pourrait être utilisée pour contourner la surveillance effectuée par un EDR et ainsi réaliser des opérations malveillantes telles que dump mémoire d’un processus, l’injection de code, ou le process hollowing.

 

Rappel sur les capacités de surveillance des EDR

Méthodes basées sur l’espace utilisateur vs. l’espace noyau

Sous Windows, les logiciels EDR utilisent principalement deux catégories de techniques pour surveiller les actions effectuées par les processus : les mécanismes côté espace utilisateur (« userland », ou « userspace »), tel que le hooking de fonctions, qui ciblent chaque processus individuellement, et ceux côté noyau (« kernel land », « kernel space »), qui s’appuient sur les fonctions fournies par le système d’exploitation pour collecter des données de télémétrie sur l’activité des processus à l’échelle du système.

Les techniques de la première catégorie peuvent souvent être techniquement contournées par un processus malveillant, à condition qu’il connaisse les moyens exacts employés par l’EDR. En effet, le code de surveillance et le code surveillé s’exécutent souvent dans le même « espace », la mémoire du processus, ce qui revient à un jeu du chat et de la souris entre le logiciel malveillant et l’EDR, étant donné que chacun peut interagir ou modifier le code de la « partie adverse ».

Pour la deuxième catégorie, le code de surveillance s’exécute dans l’espace du noyau Windows, qui n’est directement accessible par aucun processus, quel que soit son niveau de privilège. Cependant, ces capacités de surveillance sont fournies par Windows lui-même aux produits de sécurité installés, et tous les logiciels EDR sont obligés de les utiliser de manière presque identique pour obtenir une télémétrie des activités des processus (la manière de détecter une activité malveillante à partir de ces informations dépend évidemment de chaque logiciel EDR).

Pour approfondir le sujet, ces deux types de mécanismes ont notamment été décrits dans notre article de la 116ème édition du magazine MISC. Aussi, pour mieux comprendre les enjeux de ce qui suit dans le présent article, nous recommandons aux lecteurs de consulter notre autre article sur les contournements de surveillance EDR dans la 118ème édition du magazine MISC, ainsi que le README de notre outil, EDRSandblast.

 

Event Tracing for Windows – Threat Intelligence

Parmi les mécanismes susmentionnés, Event Tracing for Windows – Threat Intelligence (abrégé en ETW-Ti dans cet article) permet de générer des événements lors d’opérations du noyau critiques pour la sécurité, telles que la création de processus, la lecture/écriture de mémoire entre processus, la création de mémoire exécutable, etc. (voir notre article dans MISC 116 pour plus de détails).

Le flux d’événements produit par le mécanisme n’est normalement « consommable » que par les produits de sécurité, qui doivent être des processus protégés (PROTECTED_ANTIMALWARE_LIGHT), signés cryptographiquement comme tels par Microsoft.

La création de ces événements de sécurité est gérée par le noyau Windows et est mise en œuvre par de simples appels à des fonctions EtwTi* dédiées, intégrées à chaque fonction du noyau présentant un intérêt. L’image suivante montre l’appel à EtwTiLogReadWriteVm à l’intérieur de la fonction MiReadWriteVirtualMemory, cette dernière étant responsable des lectures et écritures de mémoire entre les processus.

A call to EtwTiLogReadWriteVm highlighted in a control-flow graph
 Figure 1: Appel à EtwTiLogReadWriteVm au sein de MiReadWriteVirtualMemory

 

Nos constats

Une exception bien commode

En examinant le graphique du flot de contrôle de la fonction ci-dessus, nous constatons que l’appel à la fonction de journalisation de l’ETW-Ti est toujours effectué lors d’un appel réussi à MiReadWriteVirtualMemory, à moins que PsIsProcessLoggingEnabled ne renvoie la valeur FALSE.

Cette dernière fonction, qui n’est mentionnée nulle part dans la littérature consacrée au reverse engineering de Windows, permet d’effectuer les opérations suivantes (note : les commentaires, les noms et les types de variables ont été obtenus par reverse engineering et/ou déduits des symboles de débogage publics) :

decompiled source code of PsIsProcessLoggingEnabled

Figure 2: Code source de PsIsProcessLoggingEnabled obtenu par reverse-engineering

Comme nous pouvons le voir, la fonction vérifie l’état d’un flag parmi EnableReadVmLogging, EnableWriteVmLogging, EnableThreadSuspendResumeLogging et EnableProcessSuspendResumeLogging, indiquant si l’action en cours (parmi une lecture de mémoire interprocessus, une écriture de mémoire, une suspension/reprise de thread ou une suspension/reprise de processus, respectivement) doit être effectivement enregistrée par ETW-Ti. Ces flags se trouvent dans différents bitfields de la structure _EPROCESS du processus ciblé.

 

Accès aux flags de journalisation

En recoupant l’utilisation des flags susmentionnés dans le noyau, nous avons constaté que les fonctions NtQueryInformationProcess et NtSetInformationProcess étaient utilisés pour obtenir ou définir les bits spécifiques correspondant à ces flags de journalisation (logging).

Bien que la plupart du temps non documentées, ces fonctions ont été examinées de près par les reverse-engineers s’intéressant aux mécanismes internes de Windows (et par les développeurs de logiciels malveillants) depuis longtemps, car elles gèrent les appels système éponymes accessibles à partir de l’espace utilisateur. Le projet System Informer (anciennement connu sous le nom de Process Hacker) abrite une base de données impressionnante de prototypes de fonctions, de structures et d’enums liés aux mécanismes internes de Windows.

Le prototype de la fonction NtSetInformationProcess est le suivant :

Reversed source code of NtSetInformationProcess

Figure 3: Prototype de la fonction NtSetInformationProcess

Cette fonction gère une centaine de cas d’utilisation, selon la valeur du paramètre ProcessInformationClass. La fonction est implémentée à l’aide d’une énorme instruction switch, et le code spécifique concernant les flags de journalisation est situé dans les cases ProcessEnableReadWriteVmLogging et ProcessEnableLogging (constantes non documentées, nommées ainsi par les développeurs de System Informer).

Reverse-engineered source code of NtSetInformationProcess

Figure 4: Code source de NtSetInformationProcess obtenu par reverse-engineering

Le comportement du code ci-dessus peut être réduit aux points suivants :

  • La cohérence de l’argument ProcessInformationLength est vérifiée par rapport à la structure ProcessInformation attendue (c’est-à-dire que les flags sont stockés dans un BYTE ou dans un DWORD, voir les structures attendues pour ProcessEnableReadWriteVmLogging et ProcessEnableLogging) ;
  • Les privilèges du processus sont vérifiés : l’appel n’est accepté que si au moins l’un des privilèges SeDebugPrivilege ou SeTcbPrivilege est détenu par le processus appelant ;
  • L’objet noyau (_EPROCESS) du processus cible est récupéré, tout en vérifiant que son handle possède bien le droit d’accès PROCESS_SET_LIMITED_INFORMATION ;
  • Différents flags (provenant des champs Flags2 et Flags3 de la structure _EPROCESS) sont mis à jour, sur la base de la structure ProcessInformation

Les flags qui peuvent être mis à jour par cette méthode sont les suivants :

  • EnableProcessSuspendResumeLogging (ou EnableThreadSuspendResumeLogging) : détermine si un événement ETW-Ti est généré lors de la suspension ou de la reprise d’un processus (ou d’un thread). Ces opérations sont utilisées dans les techniques de process hollowing, par exemple ;
  • EnableReadVmLogging : détermine si un événement ETW-Ti est généré lors de la lecture de la mémoire d’un processus par un autre. Ces opérations sont généralement utilisées dans les techniques de dump de processus (LSASS, notamment) ;
  • EnableWriteVmLogging : idem, pour les écritures en mémoire entre processus. Ces opérations sont utilisées dans la plupart des techniques d’injection de processus.

Du point de vue offensif

En résumé, si le mécanisme ETW-Ti ne peut pas être désactivé globalement sur le système depuis l’espace utilisateur (c’est-à-dire par un processus), certaines de ses fonctionnalités peuvent être désactivées par un processus disposant du privilège SeDebugPrivilege ou SeTcbPrivilege, ce qui peut être le cas de n’importe quel processus élevé.

Comme indiqué précédemment, le flux d’événements de l’ETW-Ti n’est normalement accessible qu’aux produits de sécurité tels que les EDR. Cependant, dans la fonction ci-dessus, nous voyons que n’importe quel processus non protégé peut désactiver certaines fonctions de journalisation d’un autre processus sans prouver au système qu’il est un consommateur légitime du flux ETW-Ti (ex. un EDR).

Il est important de noter que les EDR mettent souvent en corrélation plusieurs événements pour créer des alertes, afin de ne pas générer de résultats faussement positifs. Par exemple, un dump du processus LSASS est souvent divisé en plusieurs étapes :

  • L’ouverture d’un handle au processus exe ayant un accès PROCESS_VM_READ ;
  • La lecture effective de toutes les plages de mémoire concernées ;
  • La création d’un fichier minidump.

Si seul l’événement de création d’un handle existe, mais que les événements de lecture ne sont pas enregistrés par ETW-Ti et que le fichier minidump est chiffré ou n’a jamais été écrit sur le disque, l’EDR peut ne pas émettre d’alertes concernant le dump du processus LSASS, faute de preuves.

Versions de Windows affectées

Différences entre Windows 10 et Windows 11

La même analyse a été effectuée sur le code NtSetInformationProcess du noyau de Windows 11.

Reverse-engineered source code of NtSetInformationProcess on Windows 11

Figure 5: Code source de NtSetInformationProcess obtenu par reverse-engineering sous Windows 11

Le code présente deux différences principales. La première, et la plus importante : le niveau de protection du processus appelant NtSetInformationProcess est vérifié comme étant « dominant » par rapport au niveau ANTIMALWARE_LIGHT, à l’aide de l’appel à EtwCheckSecurityLoggerAccess. Pour information, on dit qu’un niveau de protection en domine un autre si les deux affirmations suivantes sont vraies :

  • Le type de protection (Protected, Protected light ou non protégé) est identique ou plus fort que l’autre niveau de protection (Protected est « plus fort » que Protected light, qui est évidemment plus fort que non protégé).
  • Le signataire (signer) domine celui de l’autre niveau de protection, selon des règles codées en dur dans le noyau Windows (analysées de la structure RtlProtectedAccess). Le graphique suivant décrit ces règles :

Protected processes "domination" between different protection levels

Figure 6: « Domination » des processus protégés entre les différents signataires

Cela signifie que seul un processus protected ou protected light dont le signataire est WinSystem, WinTcb, Windows ou Antimalware (c’est-à-dire un composant système ou un produit de sécurité signé cryptographiquement par Microsoft en tant que tel) est autorisé à utiliser l’API NtSetInformationProcess pour désactiver les fonctions de journalisation ETW-Ti sous Windows 11. Il s’agit d’une amélioration importante, car elle établit une frontière cohérente entre les produits et fonctions de sécurité d’une part, et les autres processus d’autre part.

La deuxième différence entre les implémentations de NtSetInformationProcess sous Windows 10 et Windows 11 est que de nouveaux flags de journalisation ETW-Ti semblent être modifiables avec l’API : EnableProcessLocalExecProtectVmLogging et EnableProcessRemoteExecProtectVmLogging, apparemment utilisés pour activer/désactiver la surveillance des opérations rendant la mémoire exécutable.

À titre d’information, cette fonctionnalité semble toutefois être boguée ou n’est pas encore complètement implémentée, puisque dans le code présenté plus haut, les bits correspondant ne sont pas mis à zéro par l’opération AND bit-à-bit (InterlockedAnd), les fonctionnalités correspondantes ne peuvent donc pas être désactivées à l’aide de cette API.

 

Périmètre exact des versions affectées

L’analyse de diverses versions du noyau dans différentes versions de Windows a montré que la première version disponible de Windows 11 (21H2, version 10.0.22000.194) met déjà en œuvre le contrôle de sécurité effectué par EtwCheckSecurityLoggerAccess décrit plus haut.

En revanche, dans la dernière version disponible de Windows 10 au moment de la rédaction (22H2, version 10.0.19041.3393), la vérification de sécurité est toujours absente, alors que cette version est plus récente de 2 ans. Cela indique selon toute vraisemblance que Microsoft est bien conscient du problème et ne corrige pas la faiblesse volontairement, probablement pour des raisons de rétrocompatibilité.

Les différents flags de journalisation et leur traitement associé par NtSetInformationProcess sont apparus progressivement au cours de la vie du produit Windows 10. Le tableau suivant résume les versions concernées :

 

Win10

1507 -> 1703

Win10

1709 -> 1803

Win10

1809 -> 22H2

Win11

21H2 -> 22H2

Opération de lecture mémoire

Opération d’écriture mémoire

Opérations d’arrêt/reprise d’un processus

Opérations d’arrêt/reprise d’un thread

⚠ : La fonction de journalisation ETWTi n’existe pas encore
❌ : La journalisation ETWTi peut être désactivée
✅ : La journalisation ETWTi ne peut pas être désactivée

Le mot de la fin

En conclusion, le mécanisme décrit dans cet article permet de fait à un programme malveillant s’exécutant avec des privilèges élevés et souhaitant effectuer des actions néfastes (injection de processus, dump LSASS, process hollowing, etc.) de désactiver soigneusement la télémétrie associée avant de le faire, supprimant ainsi des preuves critiques de la surveillance EDR, ce qui améliore grandement ses chances de ne pas être détecté.

De nombreux éléments montrent que Microsoft est conscient de la faiblesse, mais ne modifie pas le comportement de l’API rétroactivement sur Windows 10, probablement en raison de problèmes de rétro-compatibilité.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Back to top