Définir le chargeur réflexif cobalt strike

Homme portant un sweat à capuche noir utilisant un ordinateur portable dans la rue, représentant un hacker

Si les composants d’IA et de machine learning de nouvelle génération des solutions de sécurité continuent d’améliorer les capacités de détection comportementales, beaucoup reposent encore sur la détection basée sur la signature. Cobalt Strike étant un cadre de commandement et de contrôle (C2) très utilisé par les acteurs de la menace et les équipes rouges depuis ses débuts, il continue à être fortement signé par les solutions de sécurité.

Pour poursuivre l’utilisation opérationnelle de Cobalt Strike, nous, membres de l’équipe IBM X-Force Red Adversary Simulation, avons déployé d’importants efforts de recherche et développement pour personnaliser Cobalt Strike avec des outils internes. Certains de nos outils internes spécifiques à Cobalt Strike ont de versions publiques telles que « InlineExecute-Assembly », « CredBandit » et « BokuLoader ». Ces deux dernières années, étant donné le nombre excessif de signatures Cobalt Strike, nous limitons son utilisation à la simulation d’acteurs de la menace moins sophistiqués, et nous privilégions d’autres C2 tiers et internes pour effectuer des exercices d’équipe rouge plus avancés.

Grâce à nos efforts de recherche et développement, nous avons obtenu de meilleurs résultats opérationnels lors des exercices avancés en équipe rouge avec :

  • Outils internes personnalisés.
  • Chargeurs internes personnalisés.
  • Cadre C2 interne personnalisé.
  • Nous continuons à investir dans l’expansion des capacités et de la discrétion des cadres C2 tiers alternatifs.

Cependant, de nombreux acteurs malveillants exploitent toujours des copies piratées de Cobalt Strike, et il reste important de pouvoir simuler leur activité. Les équipes rouges prêtes à déployer des efforts de recherche et de développement peuvent encore réussir leurs opérations avec Cobalt Strike, tout en simulant ces adversaires. En outre, Cobalt Strike est un excellent outil d’apprentissage, qui peut être utilisé par les novices pour acquérir de l’expérience avec un cadre C2 par le biais de cours de formation en équipe rouge.

Alors que nous continuons à développer nos capacités C2, nous partageons quelques informations sur la manière dont nous nous sommes appuyés sur le cadre Cobalt Strike par le passé, notamment en développant des chargeurs réflexifs sur mesure. Il souhaitons également aider les défenseurs de comprendre le fonctionnement de Cobalt Strike afin de créer des détections plus robustes.

S’appuyer sur le cadre avec des chargeurs réflexifs

Cet article de blog est le premier d’une série qui sert d’introduction, abordant les bases du développement de chargeurs réflexifs Cobalt Strike. Au fil de cette série, nous nous appuierons sur ces bases et ferons référence à cet article.

À l’issue de cette série, nous visons à créer un chargeur réflexif qui s’intègre aux fonctionnalités d’évitement existantes de Cobalt Strike, voire qui les améliore à l’aide de techniques avancées actuellement absentes de l’outil. Les prochains articles aborderont en détail le développement de différentes fonctionnalités d’évitement et la manière de les intégrer dans notre chargeur réflexif Cobalt Strike.

Pour commencer, cet article abordera les points suivants :

  • Les problèmes liés au chargement d’un implant C2 à partir du disque avec le chargeur DLL de Windows.
  • Les concepts et les mécanismes du processus de chargement réflexif de Cobalt Strike.
  • Les exigences de conception à respecter pour construire un chargeur réflexif efficace.
  • Les phases du processus de chargement réflexif.

En explorant le chargement réflexif de Cobalt Strike du point de vue d’un développeur d’outils de sécurité offensifs, nous soulignerons les opportunités de détection et d’évitement. Certains aspects du développement seront omis ou simplifiés, et nous vous encourageons à combler les lacunes en déboguant les projets de chargeurs réflexifs existants, en les reconstruisant à partir de zéro ou en suivant une formation.

Charger la DLL Beacon

L’implant Cobalt Strike C2, connu sous le nom de Beacon, est une bibliothèque DLL Windows, et la capacité modulaire d’utiliser notre propre chargeur DLL dans Cobalt Strike est connue sous le nom de User-Defined Reflective Loader (UDRL).

Le chargeur DLL Windows intégré

Le chargeur DLL intégré de Windows est généralement responsable du chargement DLL dans l’espace mémoire virtuel d’un processus. Le chargeur DLL Windows se trouve principalement dans l’espace utilisateur, bien qu’il passe dans l’espace du noyau lors du mappage de DLL à partir d’un disque.

L’utilisation du chargeur DLL Windows présente quelques inconvénients lorsqu’il est utilisé pour simuler les adversaires :

  • La DLL brute doit être présente sur le système de fichiers.
  • La DLL brute doit être exempte de brouillage.
  • Les événements de chargement des images du noyau sont déclenchés par le chargeur DLL Windows.

Par conséquent, utiliser le chargeur DLL Windows pour charger notre DLL Beacon n’est pas une solution idéale. Pour surmonter ces défis, nous chargeons la DLL Beacon à partir de la mémoire à l’aide d’un chargeur réflexif.

Les trois principaux points de détection évités par le chargement réflexif sont les suivants :

  1. Évite les logiciels malveillants signés sur le système de fichiers.
  2. Évite les événements de chargement des images du noyau, qui peuvent être surveillés par des solutions de sécurité.
  3. Évite que notre DLL d’implant C2 ne soit répertoriée dans le Process Environment Block (PEB).

Chargeur réflexif et chargeur DLL Windows

Le chargement réflexif consiste à tout simplement charger une DLL brute directement à partir de la mémoire, au lieu de la charger à partir du système de fichiers.

Le chargeur réflexif et le chargeur DLL intégré à Windows permettent tous deux de charger une DLL à partir d’un format de fichier brut dans l’espace mémoire virtuelle d’un processus. Cependant, le chargement réflexif présente un avantage majeur par rapport au chargeur DLL Windows? En effet, il ne requiert pas que le fichier DLL existe sur le système de fichiers. Ce chargement en mémoire permet un nombre illimité de phases de chargement en chaîne, car la DLL de l’implant C2 peut être cachée dans les couches de chiffrement et d’encodage de la mémoire du processus.

Format de fichier brut et format d’adresse virtuelle

Un aspect clé à connaître lors du chargement d’une DLL est celui que la DLL sera formatée différemment sur disque et en mémoire. Les principales différences entre la DLL au format de fichier brut et au format d’adresse virtuelle sont les suivantes :

Format de fichier brut :

  • Le format de la DLL telle qu’elle existe sur un système de fichiers.
  • Les sections de la DLL sont étroitement liées les unes aux autres.
  • Les offsets sont basés sur le début du fichier DLL brut, tel qu’il existerait sur le disque.
  • Ce format occupe moins d’espace mémoire.

Format d’adresse virtuelle :

  • Le format de la DLL tel qu’elle existe dans l’espace mémoire virtuel d’un processus.
  • Les sections sont espacées.
  • Les offsets sont des adresses virtuelles relatives (RVA).
  • Lors de l’exécution d’un processus, la DLL et autres modules déterminent les emplacements via RVA.
  • Ce format prend plus d’espace mémoire.

Beacon brut et beacon virtuel

En examinant une DLL beacon HTTP dans l’outil PE-Bear d’Aleksandra Doniec, nous constatons des différences entre l’adressage brut et virtuel pour chaque section de la DLL :

Tableau listant les adresses brutes et virtuelles de chaque section de la DLL beacon.

Tableau listant les adresses brutes et virtuelles de chaque section de la DLL beacon.

Cette DLL beacon HTTP/S est 0x52000  octets (327KB ) en taille lorsqu’il est chargé dans l’espace mémoire virtuel d’un processus, par rapport à 0x44000  octets (272KB ) en fonction de sa taille sur le système de fichiers. Cette différence de taille s’explique par le fait que les sections sont espacées dans le format d’adresse virtuelle, alors qu’elles sont serrées les unes contre les autres dans le format de fichier brut.

PE-Bear fournit une représentation visuelle de notre DLL beacon telle qu’elle existe au format de fichier brut et au format d’espace d’adresse virtuelle :

Représentation visuelle de la DLL Beacon au format brut et au format virtuel

Représentation visuelle de la DLL beacon au format brut (à gauche) et au format virtuel (à droite)

Charger Beacon avec le chargeur DLL Windows

Bien que ce ne soit pas le choix le plus judicieux lors d’une simulation d’adversaire, déposer sur le disque une DLL beacon brute sans brouillage et la charger à l’aide du chargeur DLL Windows permet de démystifier le chargement beacon et DLL. En fait, Beacon n’est rien d’autre qu’une DLL. Le chargeur DLL Windows et le chargeur réflexif chargent tout simplement une DLL dans un processus.

Pour charger la DLL Beacon avec le chargeur DLL Windows, nous procédons comme suit :

  1. Générer une DLL Beacon brute sans brouillage.
  2. Créer un programme qui :
    1. Utilise le LoadLibrary API pour charger notre DLL Beacon à partir du disque.
    2. Exécute notre beacon en appelant le point d’entrée de la DLL beacon virtuelle.
  3. Placer notre programme exécutable et notre DLL beacon dans le même dossier.
  4. Exécuter notre programme.

Générer une DLL beacon brute sans brouillage

Tout d’abord, nous désactivons toutes les options Malleable PE qui font que notre DLL beacon ne peut pas être chargée par le chargeur DLL Windows. Pour ce faire, nous modifions notre profil Malleable C2 et désactivons les options d’évitement de Malleable PE situées dans le bloc stage :

Bloc stage de profil Malleable C2 modifié pour désactiver les fonctionnalités d’évitement de Cobalt Strike.

Bloc stage de profil Malleable C2 modifié pour désactiver les fonctionnalités d’évitement de Cobalt Strike.

Après avoir modifié le profil, nous redémarrons le serveur Cobalt Strike Team, en fournissant notre no_evasion.profile  profil comme argument.

Capture d'écran réalisée pour l'article de blog

Nous nous connectons au serveur Team via le client Cobalt Strike. Ensuite, nous créons notreWindows Stageless Payload  avec l’option de sortie définie sur Raw et le listener défini sur https . Nous enregistrons la charge utile comme beacon.dll .

Capture d’écran illustrant la création d’une DLL beacon « brute sans mise en scène » à partir du client Cobalt Strike

Capture d’écran illustrant la création d’une DLL beacon « brute sans mise en scène » à partir du client Cobalt Strike

Créer le programme de notre chargeur DLL Beacon

En utilisant le code ci-dessous, nous créons un programme en C nommé loadBeaconDLL.c  et nous compilons :

Code C Windows pour charger la DLL beacon à partir du disque à l’aide du chargeur DLL Windows.

Code C Windows pour charger la DLL beacon à partir du disque à l’aide du chargeur DLL Windows.

Nous utilisons Kernel32.LoadLibraryA  l’API pour charger notre DLL beacon brute à partir du disque. Cette API appellera le chargeur DLL intégré à Windows, qui chargera notre DLL beacon à partir du disque vers l’espace mémoire virtuel de notre processus hôte.

Durant le processus de chargement, le chargeur DLL Windows initialisera notre DLL beacon en appelant son point d’entrée avec DLL_PROCESS_ATTACH (1)  comme argument.

Une fois que le chargeur DLL Windows a chargé et initialisé notre DLL beacon dans l’espace mémoire virtuel de notre processus, nous devons à nouveau appeler le point d’entrée de la DLL beacon virtuelle avec l’argument 0x4.

Notre programme doit connaître le point d’entrée de notre DLL beacon virtuelle pour l’exécuter. Cela peut être fait de manière dynamique dans le programme en analysant les en-têtes de la DLL beacon virtuelle pour identifier l’adresse virtuelle relative (RVA) du point d’entrée, ou nous pouvons rapidement l’examiner et coder la valeur en dur.

Pour notre preuve de concept, nous découvrirons manuellement et coderons en dur la RVA du point d’entrée de notre DLL beacon dans notre programme. En utilisant PE-Bear, nous découvrons que la RVA vers le point d’entrée du beacon est 0x1D840 :

Capture d’écran illustrant la recherche de la RVA du point d’entrée de la DLL beacon avec PE-Bear

Capture d’écran illustrant la recherche de la RVA du point d’entrée de la DLL beacon avec PE-Bear

Le LoadLibraryA  L’API renvoie l’adresse de base de notre DLL beacon virtuelle. Il suffit de l’ajouter à la RVA du point d’entrée pour déterminer le point d’entrée.

Notre code étant prêt, nous compilons notre programme C en exécutable Windows :

Commande utilisée pour compiler notre programme.

Commande utilisée pour compiler notre programme.

Positionner notre programme et la DLL Beacon sur le système de fichiers

En plaçant notre DLL beacon et notre programme de chargement beacon exécutable dans le même répertoire, nous permettons au chargeur DLL Windows de découvrir notre DLL au fur et à mesure qu’elle effectue sa routine de chargement.

Nous plaçons les deux beacon.dll  et loadBeaconDLL.exe  sur le système de fichiers dans le même répertoire :

La DLL Beacon et le programme de chargement sont placés dans le même répertoire.

La DLL Beacon et le programme de chargement sont placés dans le même répertoire.

Exécuter notre programme

Dans notre bureau Windows, nous double-cliquons sur le programme loadBeaconDLL.exe et établissons une connexion beacon active à notre Team Server.

Connexion réussie à C2 Team Server à partir de la DLL beacon chargée à l’aide du chargeur DLL Windows.

Connexion réussie à C2 Team Server à partir de la DLL beacon chargée à l’aide du chargeur DLL Windows.

Chargement réflexif Cobalt Strike

Cobalt Strike utilise une version modifiée du projet Reflective Loader de Stephen Fewer. Ce légendaire chargeur de DLL en mémoire, qui a plus de dix ans, a été utilisé dans Metasploit et d’autres outils de sécurité offensifs notables.

Considérations relatives à l’utilisation de l’UDRL

Au fil des années, le chargeur réflexif Cobalt Strike a été amélioré pour prendre en charge toutes les fonctionnalités d’évitement Malleable PE qu’offre Cobalt Strike. Le principal inconvénient lié à l’utilisation d’un chargeur réflexif défini par l’utilisateur (UDRL) est que les fonctionnalités d’évitement Malleable PE peuvent ou non être prises en charge telles quelles.

Certaines fonctions d’évitement sont entièrement implémentées lors de l’utilisation d’un UDRL, étant intégrées à la DLL Beacon par le moteur Malleable PE de Cobalt Strike lors de la création de la charge utile Beacon. Actuellement, des fonctionnalités comme obfuscate doivent être gérées par l’UDRL, tandis que d’autres comme sleepmask et cleanup peuvent être traitées par Beacon avec une intégration UDRL appropriée.

Méthodes de chargement réflexif

Méthode originale de chargement réflectif

Le projet d’origine Reflective Loader exige de compiler ReflectiveLoader  dans notre projet DLL et de l’exporter au sein de notre DLL d’implant C2.

Ensuite, un autre projet est chargé de :

  1. Découvrir l’adresse virtuelle de ReflectiveLoader  l’exportation.
  2. Exécuter ReflectiveLoader  l’exportation, qui renvoie le point d’entrée de notre DLL chargée.
  3. Appel du point d’entrée de la DLL chargé de manière réflexive.
Diagramme du chargeur réflexif d’origine chargeant une DLL dans la mémoire virtuelle.

Diagramme du chargeur réflexif d’origine chargeant une DLL dans la mémoire virtuelle.

Méthode du chargeur réflectif en préfixe

Une autre méthode consiste à ajouter le chargeur réflexif à la DLL. Cela permet de charger n’importe quelle DLL non gérée et n’exige pas de compiler la DLL à partir du code source. Il s’agit d’une méthode de chargement réflexif robuste, capable de charger n’importe quel fichier PE (EXE ou DLL).

Diagramme d’un chargeur réflexif ajouté à une DLL, chargeant une DLL dans la mémoire virtuelle.

Diagramme d’un chargeur réflexif ajouté à une DLL, chargeant une DLL dans la mémoire virtuelle.

La méthode de chargement réflexif de Cobalt Strike

La mise en œuvre du chargement réflexif par Cobalt Strike associe les deux méthodes susmentionnées. Cette méthode de chargement réflexif peut être familière à ceux qui savent comment le Meterpreter de Metasploit réalise le chargement réflexif.

Comme avec la méthode du chargeur réflexif d’origine, ReflectiveLoader  la fonction est compilée et exportée dans la DLL beacon d’origine. Lorsqu’un opérateur génère une charge utile beacon à partir du client Cobalt Strike, le moteur Malleable PE de ce dernier corrige la DLL beacon brute pour informer le chargeur réflexif des options Malleable PE à utiliser. L’en-tête DOS de Beacon est corrigé pour appeler ReflectiveLoader l’exportation avec un offset codé en dur. Les octets initiaux corrigés de l’en-tête DOS du beacon, qui appellent ReflectiveLoader  l’exportation, seront appelés « stub d’appel du chargeur réflexif » dans cet article.

Lorsqu’un UDRL est chargé dans Cobalt Strike et qu’un opérateur génère une charge utile beacon à partir du client Cobalt Strike, le moteur Malleable PE de Cobalt Strike corrige le shellcode du chargeur réflexif à l’offset du fichier brut de ReflectiveLoader  l’exportation.

Lorsque le moteur Malleable PE corrige la DLL beacon brute, cette dernière est donnée à l’opérateur dans un format exécutable similaire à un shellcode.

Diagramme du chargeur réflexif Cobalt Strike chargeant la DLL beacon dans la mémoire virtuelle.

Diagramme du chargeur réflexif Cobalt Strike chargeant la DLL beacon dans la mémoire virtuelle.

Stub de chargement réflectif de Beacon

En examinant les octets initiaux dans le désassembleur PE-Bear, nous constatons que la DLL beacon est elle-même exécutable :

Le stub d’appel du chargeur réfléchissant montré sous forme de codes d’opération d’assemblage exécutables.

Le stub d’appel du chargeur réfléchissant montré sous forme de codes d’opération d’assemblage exécutables.

Les octets initiaux MZAR  sont personnalisables grâce aux options Malleable PE du profil C2 de Cobalt Strikes. Ces octets doivent être exécutables et entraîner une non-opération (nop ).

Après l’exécution des octets éventuellement préfixés nops  et magiques, le stub d’appel du chargeur réflexif :

  • Crée un cadre de pile.
  • Utilise l'adressage relatif RIP pour déterminer l'adresse de base de la DLL beacon brute.
  • Appelle le ReflectiveLoader  exporter vers 0x16E3C  offset du fichier brut.
  • Appelle le point d’entrée de la DLL beacon chargée.

Nous confirmons que l’offset du fichier brut pour ReflectiveLoader  l’exportation est 0x16E3C  en consultant le répertoire d’exportation des DLL beacon :

Capture d’écran illustrant l’utilisation de PE-Bear pour déterminer l’offset du fichier brut de l’exportation ReflectiveLoader.

Capture d’écran illustrant l’utilisation de PE-Bear pour déterminer l’offset du fichier brut de l’exportation ReflectiveLoader.

Telle qu’elle existe dans le répertoire d’exportation, l’adresse pour ReflectiveLoader l’exportation est au format RVA, faisant référence à la DLL Beacon dans son état virtuel. Étant donné que l’exportation ReflectiveLoader  est exécutable, nous savons qu’elle existe dans.text section de la DLL beacon.

Pour connaître l’offset du fichier brut de ReflectiveLoader  l’exportation, nous devons d’abord connaître la différence entre .text  les sections adresse virtuelle et adresse brute. Une fois la différence connue, il suffit de la soustraire de la ReflectiveLoader  RVA de l’exportation pour découvrir ReflectiveLoader  l’offset du fichier brut de l’exportation.

Les adresses virtuelle et brute .text  de la section sont répertoriées dans les en-têtes de section de la DLL Beacon :

Adresses brutes et virtuelles de la section .text de la DLL beacon.

Adresses brutes et virtuelles de la section .text de la DLL beacon.

La différence entre les deux est 0xC00  octets. En soustrayant ReflectiveLoader  le RVA de l’exportation de 0x17A3C  par la différence, nous découvrons que l’offset du fichier brut est 0x16E3C .

Nous le confirmons dans PE-Bear en faisant un clic droit sur ReflectiveLoader  la fonction RVA de l’exportation, puis en cliquant sur Follow RVA:17A3C . Le visualiseur hex dans le widget ci-dessus permet d’afficher ReflectiveLoader  l’exportation au niveau de son offset de fichier brut.

En résumé, le processus de chargement réflexif de Cobalt Strike est le suivant :

  • Un thread exécute la DLL beacon brute.
  • Le stub d’appel du chargeur réflexif appelle ReflectiveLoader  l’exportation à un offset de fichier brut connu.
  • Le chargeur réflexif charge la DLL beacon brute dans la mémoire virtuelle du processus hôte.
  • Après le chargement, le chargeur réflexif renvoie le point d’entrée de la DLL beacon virtuelle au stub d’appel du chargeur réflexif.
  • Le stub du chargeur réflexif appelle le point d’entrée de la DLL beacon virtuelle.
Diagramme illustrant les principales phases du chargement réflexif de DLL beacon par Cobalt Strike.

Diagramme illustrant les principales phases du chargement réflexif de DLL beacon par Cobalt Strike.

Exigences de conception des chargeurs réflexifs

Code indépendant de la position

Étant donné que notre chargeur réflexif est exécuté avant le chargement de la DLL beacon, le code du chargeur réflexif doit être un shellcode pur.

La manière la plus simple de créer un shellcode complexe est de l’écrire en C, sans aucune dépendance externe. Le fichier C est ensuite compilé en fichier objet. Tout doit être inclus dans la section text du fichier objet. Enfin, nous retirons les.text la section pour obtenir le shellcode du chargeur réflexif.

Comment Cobalt Strike insère notre UDRL

Le moteur Malleable PE de Cobalt Strike se chargera de récupérer le shellcode du fichier objet de notre chargeur réflexif et de l’ajouter à la DLL beacon brute à l’offset du fichier brut de ReflectiveLoader  l’exportation. Cette opération est effectuée dans le script UDRL Aggressor, comme indiqué ci-dessous :

Script Aggressor pour écrire le shellcode du chargeur réflexif dans la DLL beacon brute en s’appuyant sur Cobalt Strike.

Script Aggressor pour écrire le shellcode du chargeur réflexif dans la DLL beacon brute en s’appuyant sur Cobalt Strike.

Notre script UDRL Aggressor demande à Cobalt Strike d’écrire le shellcode de notre chargeur réflexif en effectuant les étapes suivantes :

  • Nous ouvrons$handle à notre fichier d’objets UDRL avecopenf commerciales.
  • Avec le fichier$handle nous lisons le flux d’octets et l’enregistrons dans la$data variable du tableau d’octets.
  • Ensuite, nous fermons le fichier$handle avecclosef commerciales.
  • La fonction
    <a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#extract_reflective_loader">extract_reflective_loader</a>
    
    Cobalt Strike Aggressor intégrée analysera notre fichier d’objets UDRL à partir du$data tableau d’octets, localisera.text la section de notre fichier d’objets UDRL, extraira la section .text l’enregistrera dans$loader variable du tableau d’octets.
  • La fonction
    <a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#setup_reflective_loader">setup_reflective_loader</a>
    
    La fonction Cobalt Strike Aggressor utilisera le moteur Malleable PE pour découvrir l’offset du fichier brut de notreReflectiveLoader exportation, et ajoutera notre shellcode UDRL à partir$loader variable du tableau d’octets.
  • Enfin, nous renvoyons la DLL beacon modifiée à Cobalt Strike et enregistrons notre fichier à partir du client.

Phases de chargement réflexif

Cobalt Strike a réalisé pour nous l’extraction de la section .text du fichier d’objets de notre chargeur réflexif, l’ajout du shellcode de notre chargeur réflexif et l’appel de notre chargeur réflexif avec le stub d’appel du chargeur réflexif situé dans l’en-tête de la DLL beacon.

Voici les phases que nous devons développer pour charger le beacon de manière réflexive :

  1. Trouver la DLL Beacon brute
  2. Analyser les en-têtes DLL Beacon
  3. Allouer de la mémoire à la DLL Beacon virtuelle
  4. Charger les sections dans l’espace mémoire virtuel
  5. Charger les dépendances DLL
  6. Résoudre la table d’adresses d’importation
  7. Résoudre les relocalisations
  8. Exécuter Beacon

Phase 1 : trouver l’adresse de base de la DLL Beacon brute

Il existe plusieurs méthodes pour découvrir l’adresse de la DLL beacon brute en mémoire. En voici quelques-unes :

  • Rechercher à rebours les en-têtes MZ et PE
  • Rechercher à rebours un œuf
  • Obtenir l’adresse de base de la DLL beacon brute à partir du stub d’appel du chargeur réflexif

Trouver notre position en mémoire

Lorsque nous utilisons une méthode de chasse à rebours, nous devons d’abord obtenir l’adresse actuelle du pointeur d’instruction de notre thread (RIP ). Nous pouvons utiliser cette astuce simple pour getRip :

  1. Dans notre UDRL, nous créons une fonction appelée getRip .
  2. Nous appelons getRip  qui poussera l’adresse succédant à « call getRip  » en haut de la pile. C’est l’adresse de retour.
  3. Puis dans notre getRip  fonction, nous copions simplement l’adresse de retour de l’appelant figurant en haut de la pile.
  4. Dans le codage C Windows x64, les fonctions peuvent retourner une valeur. Cette valeur est renvoyée à l’appelant par l’intermédiaire du RAX  registre. En déplaçant l’adresse de retour de l’appelant dans RAX  le registre, nous retournons l’adresse de retour de l’appelant à ce dernier.
Code d’assemblage Intel x64 pour obtenir l’adresse de base de la DLL beacon brute à partir du registre RDI.

Code d’assemblage Intel x64 pour obtenir l’adresse de base de la DLL beacon brute à partir du registre RDI.

Recherche à rebours des en-têtes MZ et PE

Le projet de chargeur réflexif d’origine recherche les en-têtes MZ et PE. Ces en-têtes sont devenus des points de détection. Pour surmonter ce problème, Cobalt Strike a ajouté magic_mz  et magic_pe  les fonctions d’évitement Malleable PE.

La documentation Cobalt Strike indique que magic_mz  option :

  • « Remplacer les premiers octets (en-tête MZ inclus) de la DLL réflexive de Beacon. Des instructions valides sont requises. Suivre les instructions qui modifient l’état du processeur et celles qui annulent le changement. »

Une fois configurés, MZ--  octets à l’offset du fichier brut 0x00  et le PE00  les octets à l’offset du fichier brut 0x80  sont connus du chargeur réflexif. Ils sont intégrés à la DLL beacon par le moteur Malleable PE.

Ces octets doivent être uniques, sans quoi le chargeur réflexif ne pourra pas les trouver. En outre, les octets de l’en-tête MZ doivent être de type non-opération et exécutables. Ils ne peuvent pas être des valeurs comme 0x00  ou Beacon peut tomber en panne. Il peut s’agir d’un point de détection.

Chercher un œuf à rebours

Après avoir découvert ce point de détection potentiel, j’ai mis au point une autre méthode pour trouver l’adresse de base de la DLL beacon brute. Cette méthode utilise un chasseur d’œufs capable d’effectuer une recherche à rebours à partir de RIP , qui recherche deux instances répétées d’un œuf 64 bits unique au niveau beacon.dll+0x50  offset du fichier brut.

L’adresse beacon.dll+0x50  a été choisi car il s’agit de l’emplacement de la bannière « Ce programme ne peut pas être exécuté en mode DOS », qui n’est pas nécessaire lors du chargement réflexif de Beacon.

Comme nous n’avons pas un accès facile au moteur Java Malleable PE, le BokuLoader.cna  script UDRL Aggressor peut être utilisé pour écrire 0xB0C0ACDC  l’œuf dans Beacon. Le code ci-dessous montre comment modifier la DLL beacon brute pour contenir l’œuf :

Script Aggressor pour écrire un œuf dans la DLL beacon brute et afficher les modifications dans la console de script Cobalt Strike.

Script Aggressor pour écrire un œuf dans la DLL beacon brute et afficher les modifications dans la console de script Cobalt Strike.

Le code UDRL doit connaître la valeur de l’œuf écrite dans la DLL beacon brute par le script UDRL. Une fois l’œuf connu, le chasseur d’œufs en recherche deux instances, comme indiqué dans le code ci-dessous :

Code d’assemblage Intel x64 pour un chasseur d’œufs qui recherche à rebours deux instances d’un œuf 64 bits.

Code d’assemblage Intel x64 pour un chasseur d’œufs qui recherche à rebours deux instances d’un œuf 64 bits.

  • Le script UDRL Aggressor et le code UDRL C peuvent tous deux être modifiés pour utiliser différents œufs.

Maintenant que les en-têtes MZ et PE ne sont plus utilisés, nous pouvons les supprimer dans le script UDRL Aggressor :

Script Aggressor pour masquer les MZ, PE et les octets non utilisés de la bannière DOS située dans les en-têtes de la DLL beacon brute.

Script Aggressor pour masquer les MZ, PE et les octets non utilisés de la bannière DOS située dans les en-têtes de la DLL beacon brute.

Obtenir l’adresse de base de la DLL Beacon brute à partir du stub Call Reflective Loader

Il existe également un autre moyen, spécifique à Cobalt Strike, de découvrir l’adresse de base de la DLL beacon brute. Comme nous l’avons vu ci-dessus, les octets initiaux dans le stub d’appel du chargeur réflexif stockent l’adresse de base de la DLL beacon brute dans le RDI  registre avant d’appeler le chargeur réflexif. Au lieu de chasser à rebours RIP un œuf, nous pouvons simplement obtenir la valeur à partir du RDI  registre au début du code de notre chargeur réflexif.

Pour examiner tout cela plus en détail dans le débogueur, nous générons un beacon, y ajoutons un point d’arrêt (0xCC ), et ouvrons le beacon en x64dbg. Comme le point d’arrêt est ajouté au début, l’adresse de base du beacon brut est +1  de la mémoire allouée. Comme nous l’avons vu ci-dessus, le stub d’appel du chargeur réflexif utiliseRIP l’adressage relatif pour obtenir l’adresse de base de la DLL beacon brute :

Capture d’écran X64dbg de l’exécution du stub du chargeur réflectif pour voir que l’adresse de base de la DLL Beacon brute est sauvegardée dans le registre RDI avant d’appeler le chargeur réflectif.

Capture d’écran X64dbg de l’exécution du stub du chargeur réflectif pour voir que l’adresse de base de la DLL Beacon brute est sauvegardée dans le registre RDI avant d’appeler le chargeur réflectif.

Voici un exemple pratique montrant comment obtenir l’adresse de base brute de la DLL Beacon à partir du stub du chargeur réflectif :

Code C d’assemblage en ligne pour obtenir l’adresse de base de la DLL beacon brute à partir du registre RDI.

Code C d’assemblage en ligne pour obtenir l’adresse de base de la DLL beacon brute à partir du registre RDI.

Phase 2 : analyser les en-têtes de la DLL Beacon

Avec l'adresse de base de la DLL beacon brute, nous pouvons maintenant obtenir les valeurs dont nous avons besoin pour charger beacon dans l’espace d’adressage virtuel du processus.

Le tableau ci-dessous répertorie les valeurs dont nous avons besoin à partir des en-têtes de la DLL beacon brute, les emplacements où nous les trouverons et leur type :.

Tableau répertoriant les valeurs provenant de l’en-tête de la DLL beacon brute, utiles pour charger la DLL beacon.

Tableau répertoriant les valeurs provenant de l’en-tête de la DLL beacon brute, utiles pour charger la DLL beacon.

Évitements

Tous les contenus des en-têtes ne sont pas nécessaires au chargement de la DLL beacon. Les valeurs requises peuvent être reconditionnées ou brouillées. Les valeurs non obligatoires peuvent être supprimées ou randomisées.

Phase 3 : allouer de la mémoire à Virtual Beacon

Une fois que nous connaissonsSizeOfImagef à partir de l’en-tête de la DLL beacon brute, nous devons allouer une mémoire de cette taille. Cet espace mémoire contiendra notre DLL beacon virtuelle.

Différentes méthodes peuvent être utilisées pour allouer de la mémoire à la DLL beacon virtuelle. Le type de mémoire varie selon la méthode utilisée. Les différentes méthodes prises en charge par le chargeur réflexif par défaut de Cobalt Strike sont les suivantes :

Tableau présentant les options d’allocation de mémoire de Cobalt Strike pour la DLL beacon virtuelle.

Tableau présentant les options d’allocation de mémoire de Cobalt Strike pour la DLL beacon virtuelle.

Évitements

On peut aller encore plus loin avec UDRL. On peut utiliser la version NTAPI de ces fonctions. En outre, les fonctions NTAPI peuvent être appelées via des appels système directs ou indirects, ce qui peut contribuer ou non à renforcer les capacités d’évitement.

Lorsque la méthode d’allocation est définie sur VirtualAlloc  dans le profil Cobalt Strike Malleable C2, actuellement le projet BokuLoader utilisera un appel système direct à NtAllocateVirtualMemory  pour allouer de la mémoire à la DLL beacon virtuelle :

Exemple de code du projet BokuLoader montrant qu’un appel système direct est utilisé pour allouer de la mémoire à la DLL beacon virtuelle.

Exemple de code du projet BokuLoader montrant qu’un appel système direct est utilisé pour allouer de la mémoire à la DLL beacon virtuelle.

  • Le numéro d’appel système est découvert à l’aide de la méthode HellsGate.
  • Si un hook userland existe au niveau du stub d’appel système, la méthode HalosGate est utilisée.

L’image ci-dessous montre un exemple de code utilisant les méthodes HellsGate et HalosGate pour déterminer les numéros d’appel système :

Exemple de code du projet BokuLoader montrant comment les appels système sont détectés à partir du processus.

Exemple de code du projet BokuLoader montrant comment les appels système sont détectés à partir du processus.

Phase 4 : charger les sections dans l’espace mémoire virtuel

Maintenant que nous avons alloué de la mémoire à notre DLL beacon virtuelle, nous devons copier les sections du beacon à partir de leurs offsets de fichiers bruts, tels qu’ils existent dans la DLL beacon brute, vers la mémoire allouée au niveau de leurs offsets virtuels correspondants.

Si nous avons alloué notre mémoire avecREADWRITE nous devrons suivre l’adresse de .text la section et sa taille. Avant d’appeler le point d’entrée de la DLL Beacon virtuelle, nous devrons modifier les protections de mémoire de .text la section exécutable.

Allouer notre mémoire avec READWRITE_EXECUTE facilite le processus de chargement réflexif, mais augmente les risques de détection par les solutions de sécurité.

Vous trouverez ci-dessous un exemple de code simplifié, tiré du projet BokuLoader, qui le démontre :

Exemple de code du projet BokuLoader montrant les sections copiées de la DLL beacon brute vers la DLL beacon virtuelle.

Exemple de code du projet BokuLoader montrant les sections copiées de la DLL beacon brute vers la DLL beacon virtuelle.

Évitements

Voici quelques fonctionnalités d’évitement concernant les sections de chargement :

  • Ne pas copier les en-têtes beacon dans la DLL beacon virtuelle.
  • Désaffecter l’espace mémoire dans le DLL beacon virtuelle où se trouveraient les en-têtes.

Dans le projet public BokuLoader, les en-têtes de la DLL beacon ne sont pas copiés de la DLL beacon brute vers la DLL beacon virtuelle. Actuellement, les premiers 0x1000  octets de la DLL beacon virtuelle sont nuls (0x00‘s ). D'après mes tests, beacon ne dépend pas de ses en-têtes une fois qu’il a été correctement chargé dans la mémoire virtuelle. Éviter de copier les en-têtes permet de contourner les scanners en mémoire, mais ces octets nuls peuvent également constituer un point de détection.

Une autre possibilité d’évitement consiste à faire chiffrer les sections par le script UDRL Aggressor. Les sections peuvent être déchiffrées en mémoire par l’UDRL, à l’aide d’une clé partagée entre l’UDRL et le script Aggressor de l’UDRL.

Phase 5 : Charger les dépendances DLL

Le beacon HTTP/S x64 repose sur quatre DLL pour fonctionner correctement. Si ces DLL ne sont pas encore chargées dans le processus, notre chargeur réflexif devra le faire.

Les quatre DLL sont répertoriées dans le répertoire d’importation de la DLL beacon HTTP/S :

Capture d’écran de PE-Bear listant les DLL du répertoire d’importation de la DLL beacon.

Capture d’écran de PE-Bear listant les DLL du répertoire d’importation de la DLL beacon.

Le chargeur réflexif intégré à Cobalt Strike utilise l’API kernel32.LoadLibraryA pour le chargement des DLL.

Évitements

Le chargement des DLL peut être réalisé de différentes manières, avec différentes considérations de sécurité opérationnelle. Voici quelques méthodes :

Si la DLL existe déjà dans le processus, alors les API Windows ci-dessus peuvent toujours être utilisées pour obtenir les adresses de base, bien que cela puisse déclencher des alertes de détection indésirables.

Sinon, le PEB contient également un pointeur vers 

<a title="https://learn.microsoft.com/fr-fr/windows/win32/api/winternl/ns-winternl-peb_ldr_data" href="https://learn.microsoft.com/fr-fr/windows/win32/api/winternl/ns-winternl-peb_ldr_data">_PEB_LDR_DATA</a>

struct. Vous y trouverez une liste liée des DLL chargées au cours du processus et de leurs informations relatives (

InMemoryOrderModuleList

). BokuLoader en tire parti pour découvrir les informations relatives aux DLL en évitant les appels d’API inutiles.

Si la DLL n’existe pas dans le InMemoryOrderModuleList , actuellement BokuLoader utilise NTDLL.LdrLoadDll  l’API pour charger la dépendance DLL en mémoire grâce au chargeur DLL intégré à Windows.

Le chargement réflexif imbriqué n’est pas facile à utiliser pour charger les dépendances DLL, car les chargeurs réflexifs n’enregistrent généralement pas la DLL dans le processus. Le code externe à la DLL ne peut pas utiliser correctement une DLL chargée de manière réflexive. Le projet DarkLoadLibrary est peut-être capable de charger correctement une DLL en mémoire sans déclencher un événement de chargement d’image du noyau.

Exemple de code du projet BokuLoader montrant comment les adresses de base des DLL chargées peuvent être résolues en parcourant InMemoryOrderModuleList.

Exemple de code du projet BokuLoader montrant comment les adresses de base des DLL chargées peuvent être résolues en parcourant InMemoryOrderModuleList.

Phase 6 : résoudre la table d’adresses d’importation

Une fois les DLL nécessaires chargées dans le processus, les API listées dans le répertoire d’importation doivent être résolues. Les adresses API devront ensuite être écrites dans la table d’adresses d’importation (IAT) de la DLL beacon virtuelle. Ainsi, beacon sait à quelle adresse accéder lorsqu’il a besoin d’appeler des API comme WININET.HttpSendRequest

L’entrée d’importation devra être résolue soit par l’ordinal, soit par la chaîne de noms.

Sur l’image ci-dessous, on voit que la DLL beacon Cobalt Strike utilise une combinaison d’ordinaux et de chaînes de noms pour les entrées d’importation :

Capture d’écran de PE-Bear montrant certaines entrées d’importation pour la DLL beacon devant être résolues par l’ordinal.

Capture d’écran de PE-Bear montrant certaines entrées d’importation pour la DLL beacon devant être résolues par l’ordinal.

Le chargeur réflexif Cobalt Strike intégré utilise Kernel32.GetProcAddress  l’API pour résoudre les adresses virtuelles des entrées d’importation.

Évitements

Voici quelques méthodes d’évitement pour résoudre les adresses d’API :

  • Implémentations de code personnalisées de GetProcAddress
  • NTDLL.LdrGetProcedureAddress

BokuLoader utilise une implémentation de code personnalisée deGetProcAddress pour résoudre l’adresse de l’entrée d’importation, en gérant tant les chaînes de noms que les ordinaux.

La fonction NTDLL.LdrGetProcedureAddress est capable de traiter aussi bien les chaînes de noms que les ordinaux. Si l’adresse retournée pour l’entrée d’importation est une redirection vers une autre DLL, BokuLoader utilise par défaut NTDLL.LdrGetProcedureAddress pour résoudre le transfert.

Lors de l’écriture de l’IAT, le hooking peut être mis en œuvre en écrivant les adresses virtuelles des fonctions hook que nous avons implémentées, et non l’adresse virtuelle des API prévues. Tant que la sortie attendue est renvoyée à beacon lorsque l’adresse de l’IAT est appelée, nous pouvons exécuter du code supplémentaire avant de revenir à beacon. Les prochains articles et les versions publiques de BokuLoader montreront comment tirer parti du hooking IAT pour des fonctionnalités d’évitement avancées.

Avec une version récente, le projet public BokuLoader prend en charge obfuscate la fonctionnalité PE malléable issue du profil Cobalt Strike C2 avec une implémentation personnalisée. En modifiant la clé de masquage dans laBokuLoader.cna script UDRL Aggressor, le brouillage peut être amélioré en choisissant sa propre clé XOR à un seul octet.

En ce qui concerne la sécurité opérationnelle, il est important de savoir que les moteurs de mise en correspondance des schémas sont capables de forcer les masques XOR à un seul octet. Dans les prochains articles, nous verrons comment créer notre propre moteur Malleable PE en utilisant les fonctionnalités de script de Cobalt Strikes Aggressor pour brouiller Beacon et ainsi contourner la mise en correspondance des schémas.

Phase 7 : résoudre les relocalisations

La DLL beacon comporte de nombreuses relocalisations qui doivent être résolues et écrites dans le tableau de relocalisation de base de la DLL beacon virtuelle avant son exécution.

Dans PE-Bear, nous voyons que la DLL beacon a par défaut l’adresse de base d’image de 0x180000000 :

Capture d’écran de PE-Bear montrant l’adresse de base d’image de la DLL beacon.

Capture d’écran de PE-Bear montrant l’adresse de base d’image de la DLL beacon.

Avant de commencer à écrire des relocalisations, nous devons calculer le delta entre l’adresse de base de notre DLL beacon virtuelle et l’adresse de base codée en dur.

Par exemple, supposons que l’adresse de base de notre DLL beacon virtuelle soit 0x7FFC44FE0000 . Nous soustrayons l’adresse de base codée en dur de l’adresse de base de la DLL beacon virtuelle pour obtenir le delta de l’adresse de base :

Capture d’écran obtenant le delta de l’adresse de base

Ensuite, pour déterminer l’adresse virtuelle de chaque entrée de relocalisation dans le tableau de relocalisation de base, nous ajoutons le delta de l’adresse de base à l’adresse de l’entrée de relocalisation codée en dur pour déterminer la relocalisation dans notre DLL beacon virtuelle.

L’image ci-dessous montre que les entrées de relocalisation du beacon sont écrites à l’envers au format little endian :

Capture d’écran PE-Bear montrant plusieurs entrées de relocalisation au format little endian.

Capture d’écran PE-Bear montrant plusieurs entrées de relocalisation au format little endian.

L’adresse codée en dur pour cette entrée de relocalisation est 0x1800341C8 .

Nous ajoutons cette adresse au delta de l’adresse de base pour obtenir l’adresse virtuelle de la relocalisation telle qu’elle figure dans la DLL beacon virtuelle :

Capture d’écran illustrant l’ajout de l’adresse au delta de l’adresse de base pour obtenir l’adresse virtuelle de la relocalisation telle qu’elle figure dans la DLL beacon virtuelle :

Pour chaque entrée de relocalisation, nous devrons vérifier que le type est

<a title="https://learn.microsoft.com/fr-fr/windows/win32/debug/pe-format" href="https://learn.microsoft.com/fr-fr/windows/win32/debug/pe-format">IMAGE_REL_BASED_DIR64 (0xA)</a>

. Si cela est faux, nous n’écrirons pas la relocalisation.

Une fois que nous avons déterminé l’adresse virtuelle de relocalisation telle qu’elle figure dans la DLL beacon virtuelle, nous l’écrivons dans l’espace mémoire qui contient l’adresse de l’entrée de relocalisation codée en dur.

Si vous souhaitez en savoir plus sur les relocalisations PE, consultez le code de la fonction doRelocations dans le projet public BokuLoader. Avant de publier cet article de blog, j’ai remplacé le code de relocalisation d’assemblage par un code C lisible par l’humain, afin d’aider ceux qui souhaitent connaître les détails techniques de la procédure.

Phase 8 : Exécuter Beacon

L’exécution de Beacon peut être décomposée en trois étapes :

  • S’assurer que les sections de la DLL beacon virtuelle disposent des autorisations de mémoire appropriées.
  • Initialisation de la DLL beacon virtuelle.
  • Appel du point d’entrée de la DLL beacon virtuelle.

Rendre Virtual Beacon exécutable

Si la mémoire que nous avons allouée à notre DLL beacon virtuelle est READWRITE_EXECUTE , nous n’avons pas besoin de modifier les protections de la mémoire pour que beacon fonctionne correctement, sans tomber en panne.

Si nous avons alloué notre mémoire beacon virtuelle comme étant non exécutable (READWRITE ), nous devrons définir .text  la section de la DLL beacon virtuelle sur exécutable. L’emplacement et la taille virtuelle de .text  la section aurait dû être préalablement enregistrée dans notre fonction principale UDRL sous forme de variable.

Dans le projet public BokuLoader, la modification des protections de mémoire est effectuée par des appels système directs à NTProtectVirtualMemory , comme on le voit dans l’exemple de code ci-dessous :

Exemple de code du projet BokuLoader illustrant la modification de la section .text de la DLL beacon virtuelle à exécuter.

Exemple de code du projet BokuLoader illustrant la modification de la section .text de la DLL beacon virtuelle à exécuter.

Les .data  section de notre DLL beacon virtuelle doit avoir les autorisations READWRITE . Si la section n’est pas accessible en écriture, notre DLL beacon peut tomber en panne pendant l’exécution.

Initialiser la DLL Virtual Beacon

Pour que la DLL beacon virtuelle s’exécute correctement, elle doit d’abord être initialisée en appelant le point d’entrée de la DLL beacon virtuelle. Le premier argument est l’adresse de base de la DLL beacon virtuelle. Le deuxième argument est fwdReason  et doit être défini sur DLL_PROCESS_ATTACH (1) .

Exemple de code du projet BokuLoader initialisant la DLL beacon virtuelle.

Exemple de code du projet BokuLoader initialisant la DLL beacon virtuelle.

Exécuter notre DLL Virtual Beacon

Après avoir initialisé la DLL beacon virtuelle, nous pouvons soit renvoyer le point d’entrée du beacon virtuel au stub du chargeur réflexif d’appel, soit appeler le point d’entrée de la DLL beacon virtuelle dans notre UDRL avec fwdReason  défini sur 0x4 .

Contrairement à une DLL classique, dont le premier argument hinstDLL  à 

<a href="https://learn.microsoft.com/fr-fr/windows/win32/dlls/dllmain">DLLMAIN</a>

serait l’adresse de base de la DLL virtuelle, beacon attend l’adresse de base de la DLL beacon brute. Si cette dernière n’est pas fournie, certaines fonctionnalités d’évitement de Malleable PE peuvent échouer.

Exemple de code du projet BokuLoader montrant deux façons différentes d’exécuter la DLL beacon virtuelle.

Exemple de code du projet BokuLoader montrant deux façons différentes d’exécuter la DLL beacon virtuelle.

Réflexions finales

J’espère que cet article de blog aidera les équipes rouges et bleues à mieux comprendre Cobalt Strike et le processus de chargement réflectif. Il existe encore de nombreuses opportunités d’évasion qui peuvent être mises en œuvre grâce à un chargement réflectif. Avec une compréhension plus approfondie de ces concepts, les organisations peuvent mieux se préparer à une défense efficace contre les menaces cybernétiques.

Les prochains articles de cette série porteront sur l’intégration de l’UDRL avec les fonctions d’évitement actuelles de Cobalt Strike, sur les fonctions d’évitement non documentées déjà présentes dans le BokuLoader public, ainsi que sur les fonctions avancées qui n’ont pas encore été mises à la disposition du public. Ne manquez pas ces informations détaillées et techniques pour tirer pleinement profit de Cobalt Strike grâce au développement d’UDRL !

Mixture of Experts | 12 décembre, épisode 85

Décryptage de l’IA : Tour d’horizon hebdomadaire

Rejoignez notre panel d’ingénieurs, de chercheurs, de chefs de produits et autres spécialistes de premier plan pour connaître l’essentiel de l’actualité et des dernières tendances dans le domaine de l’IA.