Détection de Cobalt Strike à l'aide de signatures en mémoire
Avec Elastic Security, nous envisageons la détection des menaces sous plusieurs angles. Traditionnellement parlant, nous avons privilégié l'utilisation de modèles de Machine Learning et l'analyse des comportements. Ces deux méthodes sont intéressantes car elles permettent de détecter des malwares jusque-là inconnus. Historiquement parlant, nous avons toujours eu l'impression que l'évasion des signatures se faisait trop facilement, même si nous sommes bien conscients qu'il ne s'agit là que d'une problématique parmi d'autres. Les taux de performances et de faux positifs sont également deux aspects essentiels pour mesurer l'efficacité d'une technique de détection.
Même si les signatures ne permettent pas de détecter des malwares inconnus, elles renvoient un taux de faux positifs quasi-nul. Elles disposent par ailleurs de balises qui aident à hiérarchiser les alertes. Par exemple, une alerte concernant un ransomware comme TrickBot ou REvil sera plus urgente à traiter que la variante d'un adware potentiellement indésirable. Même si nous avions la possibilité de détecter ne serait-ce que la moitié des malwares connus avec les signatures, cela resterait une grande victoire, qui viendrait se cumuler à d'autres protections, offrant d'autres atouts. Ceci étant dit, nous pouvons faire beaucoup mieux.
La création de signatures offrant une valeur à long terme n'est pas tâche aisée. L'un des plus gros obstacles en la matière est l'usage répandu de packers et de modules de chargement de malwares à usage unique. Ces composants évoluent rapidement pour échapper à la détection de signature. Mais la charge utile du malware final est déchiffrée et exécutée en mémoire.
Et c'est là que cela devient intéressant. Pour contourner le problème des packers et des modules de chargement, nous pouvons axer les stratégies de détection de signature sur le contenu en mémoire. Cela permettrait ainsi d'étendre la durée de vie de la signature jusqu'à plusieurs mois, contre quelques jours actuellement. Pour illustrer notre propos, nous allons nous servir de Cobalt Strike.
Création de signatures Cobalt Strike
Cobalt Strike est un framework populaire qui permet de réaliser des attaques et de simuler des adversaires. Sa simplicité d'utilisation, sa stabilité et sa discrétion en font aussi un outil de choix pour les utilisateurs malveillants ayant des intentions particulièrement retorses. Pour détecter Beacon, c.-à-d. la charge utile au point de terminaison de Cobalt Strike, plusieurs techniques ont été exploitées, notamment la recherche de threads non sauvegardés, ou plus récemment, les tubes nommés intégrés. Toutefois, les possibilités de configuration dans Beacon sont multiples. Il existe donc généralement des méthodes qui permettent d'échapper aux stratégies de détection publiques. Dans cet article, nous allons nous servir de signatures en mémoire comme stratégie de détection alternative.
Beacon est généralement chargé de manière réflexive en mémoire. Il n'entre jamais en contact avec un disque sous une forme ayant une signature directement identifiable. Par ailleurs, il peut être configuré avec différentes options de brouillage en mémoire pour cacher sa charge utile. Par exemple, l'option obfuscate-and-sleep masque des parties de la charge utile de Beacon entre les rappels pour échapper spécifiquement aux analyses de mémoire basées sur les signatures. Nous devrons tenir compte de cette option lorsque nous développerons des signatures. Il n'est reste pas moins simple de créer une signature de Beacon avec ces fonctionnalités discrètes de pointe.
Procédure détaillée
Nous allons commencer par obtenir des charges utiles Beacon à l'aide de l'option sleep_mask, que nous activerons et désactiverons avec les versions les plus récentes (voir les hachages dans la section des références). Démarrons avec un premier échantillon pour lequel l'option sleep_mask est désactivée. Après la détonation, nous pouvons localiser Beacon en mémoire avec l'outil Process Hacker en recherchant un thread qui appelle SleepEx à partir d'une région non sauvegardée :
De là, nous pouvons enregistrer la région de mémoire associée sur le disque afin de l'analyser :
La méthode la plus simple consisterait à sélectionner quelques chaînes uniques de cette région et de les utiliser pour notre signature. Pour cela, nous écrirons des signatures avec yara, un outil standard commercial qui a été conçu à cet effet :
rule cobaltstrike_beacon_strings { meta: author = "Elastic" description = "Identifies strings used in Cobalt Strike Beacon DLL." strings: $a = "%02d/%02d/%02d %02d:%02d:%02d" $b = "Started service %s on %s" $c = "%s as %s\\%s: %d" condition: 2 of them }
Cela nous donnerait un bon point de départ. Mais nous pouvons aller encore plus loin en étudiant des échantillons avec l'option sleep_mask activée. Si nous regardons en mémoire là où l'en-tête MZ/PE devrait se trouver, nous constatons qu'il est brouillé :
Si nous parcourons rapidement l'échantillon, nous remarquons que certains octets sont répétés de nombreuses fois (en l'occurrence, 0x80), alors qu'il devrait y avoir des octets nuls. Cela peut indiquer que Beacon utilise un brouillage simple à un seul octet de type XOR. Pour confirmer notre hypothèse, nous allons utiliser CyberChef :
Comme vous pouvez le voir, la chaîne "This program cannot be run in DOS mode" (Ce programme ne peut pas être exécuté en mode DOS) s'affiche après le décodage, ce qui confirme notre théorie. Étant donné que le brouillage XOR à un seul octet fait partie des astuces ultra-connues, yara prend en charge la détection native à l'aide du modificateur xor :
rule cobaltstrike_beacon_xor_strings { meta: author = "Elastic" description = "Identifies XOR'd strings used in Cobalt Strike Beacon DLL." strings: $a = "%02d/%02d/%02d %02d:%02d:%02d" xor(0x01-0xff) $b = "Started service %s on %s" xor(0x01-0xff) $c = "%s as %s\\%s: %d" xor(0x01-0xff) condition: 2 of them }
Nous pouvons aller encore plus loin dans la confirmation de la détection de nos règles yara en fournissant un PID lors de l'analyse :
Mais nous n'en avons pas encore tout à fait terminé. Après que nous avons testé cette signature sur un échantillon avec la dernière version de Beacon (la 4.2 au moment de l'écriture de cet article), la routine de brouillage a été améliorée. Nous pouvons l'identifier en nous servant de la pile des appels ("call stack") comme nous l'avons vu précédemment. Elle utilise désormais une clé XOR à 13 octets, comme dans l'extrait IDA Pro suivant :
Heureusement, l'option obfuscate-and-sleep de Beacon ne brouille que les chaînes et les données, ce qui nous laisse une section de code entière à disposition pour développer une signature. La question qui se pose est de savoir quelle fonction de la section de code nous allons utiliser pour y parvenir, mais cela mériterait que nous y consacrions tout un article de blog. Pour l'instant, nous nous contenterons de créer une signature en nous basant sur la routine d'anti-brouillage, ce qui devrait amplement suffire :
rule cobaltstrike_beacon_4_2_decrypt { meta: author = "Elastic" description = "Identifies deobfuscation routine used in Cobalt Strike Beacon DLL version 4.2." strings: $a_x64 = {4C 8B 53 08 45 8B 0A 45 8B 5A 04 4D 8D 52 08 45 85 C9 75 05 45 85 DB 74 33 45 3B CB 73 E6 49 8B F9 4C 8B 03} $a_x86 = {8B 46 04 8B 08 8B 50 04 83 C0 08 89 55 08 89 45 0C 85 C9 75 04 85 D2 74 23 3B CA 73 E6 8B 06 8D 3C 08 33 D2} condition: any of them }
Nous pouvons confirmer que nous pouvons détecter Beacon même s'il est en mode dormant (aussi bien en 32 bits qu'en 64 bits) :
Pour que la détection soit encore plus robuste, nous pourrions examiner régulièrement l'ensemble des processus du système (ou de l'entreprise). Par exemple, à l'aide de la ligne PowerShell suivante :
powershell -command "Get-Process | ForEach-Object {c:\yara64.exe my_rules.yar $_.ID}"
Conclusion
Même si elle est souvent délaissée, la détection basée sur la signature peut être un atout véritablement stratégique, en particulier lors d'une analyse en mémoire. Avec seulement quelques signatures, nous pouvons détecter Cobalt Strike, quels que soient la configuration ou les fonctions activées, avec un taux de faux positifs nul.
Hachages de référence
7d2c09a06d731a56bca7af2f5d3badef53624f025d77ababe6a14be28540a17a 277c2a0a18d7dc04993b6dc7ce873a086ab267391a9acbbc4a140e9c4658372a A0788b85266fedd64dab834cb605a31b81fd11a3439dc3a6370bb34e512220e2 2db56e74f43b1a826beff9b577933135791ee44d8e66fa111b9b2af32948235c 3d65d80b1eb8626cf327c046db0c20ba4ed1b588b8c2f1286bc09b8f4da204f2
Envie d'en savoir plus sur Elastic Security ?
Familiarisez-vous avec les fonctions puissantes de protection, de détection et de réponse d'Elastic Agent. Lancez-vous avec un essai gratuit de 14 jours (sans carte bancaire) ou téléchargez nos produits gratuitement pour votre déploiement sur site. Et profitez de nos formations rapides pour être opérationnel rapidement.