Mouvement latéral sans fichier avec des objets COM piégés
Le modèle d'objet composant (COM) est une pierre angulaire du développement de Microsoft Windows depuis le début des années 1990 et il est encore très répandu dans les systèmes d'exploitation et applications Windows modernes. La dépendance aux composants COM et le développement intensif de fonctionnalités au fil des ans ont créé une surface d'attaque importante. En février 2025, James Forshaw (@tiraniddo) de Google Project Zero a publié un article de blog détaillant une nouvelle approche pour exploiter la technologie de communication à distance Distributed COM (DCOM), dans laquelle des objets COM piégés peuvent être utilisés pour exécuter du code géré .NET dans le contexte d'un processus DCOM côté serveur. M. Forshaw met en évidence plusieurs cas d'utilisation pour l'escalade des privilèges et le contournement du Protected Process Light (PPL).
Sur la base des recherches de M. Forshaw, Mohamed Fakroud (@T3nb3w) a publié une implémentation de la technique permettant de contourner les protections PPL début mars 2025. Jimmy Bayne (@bohops) et moi-même avons mené des recherches similaires en février 2025, ce qui nous a conduits à développer une technique de mouvement latéral sans fichier en abusant d’objets COM piégés.
COM est une norme d'interface binaire et une couche de services middleware qui permet l'exposition de composants distincts et modulaires afin qu'ils interagissent entre eux et avec des applications, quel que soit le langage de programmation sous-jacent. Par exemple, les objets COM développés en C++ peuvent facilement s'interfacer avec une application .NET, ce qui permet aux développeurs d'intégrer efficacement divers modules logiciels. DCOM est une technologie de communication à distance qui permet aux clients COM de communiquer avec les serveurs COM via la communication inter-processus (IPC) ou les appels de procédure à distance (RPC). De nombreux services Windows implémentent des composants DCOM accessibles localement ou à distance.
Les classes COM sont généralement enregistrées et contenues dans le registre Windows. Un programme client interagit avec un serveur COM en créant une instance de la classe COM, appelée objet COM. Cet objet fournit un pointeur vers une interface standardisée. Le client utilise ce pointeur pour accéder aux méthodes et propriétés de l'objet, facilitant ainsi la communication et les fonctionnalités entre le client et le serveur.
Les objets COM sont fréquemment étudiés afin d'évaluer l'exposition aux vulnérabilités et de découvrir les fonctionnalités susceptibles d'être exploitées. Un objet COM piégé est une classe de bogues dans laquelle un client COM instancie une classe COM dans un serveur DCOM hors processus, où le client contrôle l'objet COM via un pointeur d'objet marshalé par référence. Selon la condition, ce vecteur de contrôle peut présenter des failles logiques liées à la sécurité.
Le blog de M. Forshaw décrit un cas d’utilisation de contournement PPL où l’interface IDispatch, telle qu’exposée dans la classe COM WaaSRemediation, est manipulée pour permettre l'utilisation abusive d'objets COM piégés et l'exécution de code .NET. WaaSRemediation est implémenté dans le service WaaSMedicSvc, qui s'exécute en tant que processus protégé svchost.exe dans le contexte NT AUTHORITY\SYSTEM. L’excellente présentation de M. Forshaw a servi de base à nos recherches appliquées et au développement d’une technique de mouvement latéral sans fichier de preuve de concept.
Notre parcours de recherche a commencé par la découverte de la classe COM WaaSRemediation qui prend en charge l’interface IDispatch. Cette interface permet aux clients d'effectuer une liaison tardive. En règle générale, les clients COM disposent des définitions d'interface et de type pour les objets qu'ils utilisent, définies au moment de la compilation. La liaison tardive permet au client de découvrir et d'appeler des méthodes sur l'objet au moment de l'exécution. IDispatch comprend la méthode GetTypeInfo, qui renvoie une interface ITypeInfo. ITypeInfo dispose de méthodes qui peuvent être utilisées pour découvrir les informations de type pour l'objet qui l'implémente.
Si une classe COM utilise une bibliothèque de types, elle peut être interrogée par le client via ITypeLib (via ITypeInfo-> GetContainingTypeLib) afin de récupérer des informations sur les types. De plus, les bibliothèques de types peuvent également référencer d’autres bibliothèques de types pour obtenir des informations supplémentaires sur les types.
Selon l’article de blog de M. Forshaw, WaaSRemediation fait référence à la bibliothèque de types WaaSRemediationLib, qui à son tour fait référence à stdole (OLE Automation). WaaSRemediationLib utilise deux classes COM de cette bibliothèque, StdFont et StdPicture. En effectuant un détournement COM sur l'objet StdFont via la modification de sa clé de registre TreatAs, la classe pointera vers une autre classe COM de notre choix, telle que System.Object dans le cadre .NET. Il convient de noter que M. Forshaw souligne que StdPicture n'est pas viable, car cet objet effectue une vérification pour l'instanciation hors processus. Nous avons donc continué à nous concentrer sur l'utilisation de StdFont.
Les objets .NET nous intéressent grâce à la méthode GetType de System.Object . Grâce à GetType, nous pouvons effectuer une réflexion .NET pour accéder finalement à Assembly.Load. Bien que System.Object ait été choisi, ce type est la racine de la hiérarchie de types dans .NET. Par conséquent, n'importe quel objet COM .NET peut être utilisé.
Une fois l'étape initiale franchie, deux autres valeurs DWORD sous la clé HKLM\Software\Microsoft\.NetFramework étaient nécessaires pour concrétiser notre cas d'utilisation envisagé :
Après avoir confirmé que la dernière version du CLR et de .NET pouvait être chargée lors de nos premiers tests, nous avons compris que nous étions sur la bonne voie.
En nous concentrant sur les aspects programmatiques à distance, nous avons d'abord utilisé le registre à distance pour manipuler les valeurs de la clé de registre .NetFramework et détourner l'objet StdFont sur la machine cible. Ensuite, nous avons remplacé CoCreateInstance par CoCreateInstanceEx pour instancier l'objet COM WaaSRemediation sur la cible à distance et obtenir un pointeur sur l'interface IDispatch .
Avec un pointeur sur IDispatch, nous appelons la méthode membre GetTypeInfo pour obtenir un pointeur sur l'interface ITypeInfo, qui est piégée dans le serveur. Les méthodes membres appelées ensuite se produisent côté serveur. Après avoir identifié la référence de bibliothèque de type contenue d'intérêt (stdole) et dérivé la référence d'objet de classe qui nous intéressait (StdFont), nous avons finalement utilisé la méthode « remotable » CreateInstance sur l'interface ITypeInfo pour rediriger le flux de lien d'objet StdFont (via la manipulation préalable de TreatAs ) afin d'instancier System.Object.
Comme AllowDCOMReflection est correctement défini, nous pouvons alors effectuer une réflexion .NET sur DCOM pour accéder à Assembly.Load afin de charger un assemblage .NET dans le serveur COM. Puisque nous utilisons Assembly.Load via DCOM, cette technique de mouvement latéral est entièrement sans fichier, car le transfert des octets de l'assembly est géré par la technologie DCOM. Pour une explication détaillée de ce flux technique, de l'instanciation d'objet à la réflexion, veuillez vous référer au diagramme suivant :
Notre premier et principal problème était d'appeler Assembly.Load_3, via IDispatch->Invoke. Invoke transmet un tableau d'objets à la fonction cible, et Load_3 est la surcharge de Assembly.Load qui prend un seul tableau d'octets. Nous devions donc encapsuler le SafeARRAY d’octets dans un autre SafeARRAY de VARIANTs. au départ, nous avons tenté à plusieurs reprises de transmettre un seul SAFEARRAY d’octets.
Un autre problème consistait à trouver la surcharge Assembly.Load appropriée. Les fonctions d’assistance étaient tirées du code CVE-2014-0257 de M. Forshaw, qui incluait la fonction GetStaticMethod . Cette fonction utilisait la réflexion .NET sur DCOM pour trouver une méthode statique à partir d’un pointeur de type, du nom de la méthode et de son nombre de paramètres. Assembly.Load possède deux surcharges statiques qui nécessitent un seul argument ; nous avons donc fini par utiliser une solution improvisée. Nous avons constaté que la troisième instance de Load avec un seul argument était le bon choix.
L'un des principaux inconvénients que nous avons observés avec cette technique était que la durée de vie de la balise générée était limitée au client COM ; dans ce cas, la durée de vie de l'application de notre binaire d'armement « ForsHops.exe » (élégamment nommé, bien sûr). Ainsi, si ForsHops.exe nettoyait ses références COM ou se fermait, il en allait de même pour la balise qui s'exécutait sous svchost.exe de la machine à distance. Nous avons essayé différentes solutions, telles que demander à notre assembly .NET de suspendre indéfiniment son thread principal, exécuter le shellcode dans un autre thread et demander à ForsHops.exe de laisser le thread d'exploitation en suspens, mais aucune n'était satisfaisante.
Dans son état actuel, ForsHops.exe s'exécute jusqu'à la fermeture de la balise, puis ses opérations de registre sont supprimées. Il existe des possibilités d'amélioration, mais nous laisserons au lecteur le soin de les identifier.
Les recommandations de détection proposées par Samir Bousseaden (@SBousseaden) après la publication de leur implémentation par Mohamed Fakroud s'appliquent également à cette technique de mouvement latéral :
De plus, nous recommandons la mise en œuvre des contrôles supplémentaires suivants :
De plus, tirez parti de la règle YARA de validation de concept suivante pour détecter l'exécutable standard ForsHops.exe :
règle Detect_Standard_ForsHops_PE_By_Hash
Notre implémentation étend légèrement le détournement de COM expliqué dans le blog de M. Forshaw en exploitant les objets COM piégés pour le mouvement latéral plutôt que l'exécution locale pour contourner le PPL. ar conséquent, elle reste sensible aux mêmes détections que les implémentations effectuant une exécution locale.
Vous trouverez ici le code de démonstration de mouvement latéral ForsHops.exe.
Nous remercions tout particulièrement Dwight Hohnstein(@djhohnstein) et Sanjiv Kawa(@sanjivkawa) pour leurs commentaires sur cette recherche et la révision du contenu des articles de blog.