Charges utiles des logiciels malveillants & Beacons : Types de charges utiles malveillantes
Dans le billet précédent, nous avons expliqué comment les charges utiles facilitent les communications malveillantes et comment un attaquant peut prendre le contrôle d'un système après avoir exécuté avec succès les charges utiles et les balises. Nous avons utilisé un canal de communication reverse_tcp facilité par la charge utile Meterpreter (du Metasploit Framework).
Ce billet s'attachera à mieux comprendre les différents types de charges utiles et des exemples de techniques de manipulation de la mémoire qu'elles peuvent employer.
Les logiciels malveillants sont devenus plus sophistiqués dans leur composition et leur exécution, surtout par rapport à l'époque des virus plus simples, comme le programme Creeper au début des années soixante-dix. Pour les acteurs de la menace motivés principalement par des objectifs clandestins, rester sous le radar pour persister dans un réseau sans être détecté pendant une période prolongée est généralement une priorité. Ils emploieront donc les techniques correspondantes, telles que les leurres, le codage, l'obscurcissement, le cryptage et l'imitation, pour atteindre le niveau souhaité de sécurité opérationnelle.
Un acteur de la menace dispose de plusieurs formats d'exécutables. Le choix de l'attaquant dépend des vecteurs d'attaque initiaux et des actions post-exploitation ultérieures. Voici quelques-uns des formats exécutables et de transformation du Metasploit Framework.

Comme nous l'avons vu dans la première partie, la balise ou la charge utile est l'implant sur la machine ou le réseau de la victime qui permet à l'attaquant d'entrer sur le territoire et de s'y implanter. Il s'agit d'un élément important de l'arsenal des logiciels malveillants et du cycle de vie global de l'attaque, qui permet à l'auteur de la menace d'avoir un accès direct pour poursuivre ses activités malveillantes.
En ce qui concerne la catégorisation générale, une charge utile peut être soit "mise en scène", soit "sans mise en scène". Un acteur de la menace peut choisir l'un plutôt que l'autre en fonction de plusieurs facteurs, dont les principaux peuvent être des considérations de sécurité opérationnelle.
Que sont les charges utiles échelonnées ?
Les charges utiles échelonnées décomposent les différentes phases d'une attaque, en utilisant souvent plusieurs charges utiles pour des phases qu'une seule charge utile aurait autrement exécutées. Ces charges utiles sont généralement décomposées en un exécutable "stager" (charge utile initiale ou balise) et un exécutable "stage" (charge utile principale).
Un stager est un petit exécutable qui constitue une charge utile initiale. Il s'agit d'un morceau de code relativement petit qui est exécuté pour préparer une charge utile beaucoup plus importante et plus performante, connue sous le nom de charge utile d'étape. Cela signifie que "le stager prépare le terrain". Un stager fait généralement partie d'un code d'exploitation lorsque l'entrée initiale exploite une vulnérabilité. Ici, le code d'exploitation exploitera avec succès la vulnérabilité cible et exécutera ensuite le code de mise en scène (charge utile). Le stager entre alors en action.
La tâche principale de l'organisateur est de réussir à s'exécuter sans être détecté, de rejoindre l'infrastructure de l'attaquant pour télécharger la charge utile principale souhaitée, puis de configurer le système pour exécuter cette charge utile. L'étape téléchargée ou la charge utile principale plus importante peut être une ou plusieurs charges utiles en fonction de la capacité requise par l'attaquant. Une fois la scène téléchargée, l'organisateur transmet le contrôle d'exécution pour poursuivre l'activité malveillante.
Dans le cas de l'exploitation d'une vulnérabilité, le programme d'exploitation initial fait quelque chose de similaire pour le stager que le stager fait pour la charge utile de l'étape principale, en termes d'allocation de ressources sur le système compromis. Il suivra un schéma similaire à celui-ci :

L'exemple ci-dessous montre une charge utile mise en scène sous la forme d'un exécutable Windows.

Les charges utiles échelonnées s'adaptent aux scénarios dans lesquels il peut y avoir des contraintes liées au système, telles que l'espace disque et l'espace mémoire, en ce qui concerne la livraison et l'exécution de la charge utile - comme dans le cas d'un shellcode utilisé pour exploiter une vulnérabilité de débordement de mémoire tampon.
Qu'est-ce qu'une charge utile sans étape ?
Le contraire des charges utiles échelonnées est la charge utile sans échelonnement. Les charges utiles sans étages sont autonomes et généralement beaucoup plus grandes que les charges utiles avec étages. Ils combinent généralement toutes les capacités requises d'un attaquant en un seul exécutable.
Dans ce cas, il n'est généralement pas nécessaire d'avoir une charge utile initiale (stager) qui télécharge la charge utile principale (stager). Une fois que la charge utile sans étape est exécutée, elle dispose de toutes les capacités nécessaires pour effectuer des actions malveillantes, telles que l'injection de mémoire, le rappel de l'infrastructure de l'attaquant et la fourniture d'un shell à l'attaquant.
L'exemple ci-dessous montre une charge utile tcp inversée de Meterpreter sans étape sous la forme d'un exécutable Windows.

La décision concernant le type de charge utile à utiliser dans le cadre d'une campagne malveillante est connue sous le nom de "considérations de sécurité opérationnelle" pour cet acteur de la menace. Le type d'infrastructure correspondant à l'attaquant qui soutient la campagne malveillante est en partie influencé par ces considérations.
Charges utiles échelonnées ou non échelonnées
Les charges utiles mises en scène par Metasploit comportent le symbole de la barre oblique ( / ) après le mot Meterpreter. La capture d'écran ci-dessous montre des exemples de charges utiles Meterpreter mises en scène sous Windows.

Les charges utiles sans étape utilisent le symbole de soulignement ( _ ) après le mot Meterpreter. La capture d'écran ci-dessous montre des exemples de charges utiles sans étape de Windows Meterpreter.

L'exemple ci-dessous montre les deux catégories de charges utiles.

Les charges utiles sans étape sont autonomes et ne nécessitent pas l'étape supplémentaire d'envoi d'une étape (charge utile principale) à la machine de la victime une fois que le logiciel malveillant a effectué un rappel à l'infrastructure de l'attaquant. Remarquez dans la capture d'écran ci-dessous qu'après le démarrage du gestionnaire TCP inverse, l'étape suivante consiste à ouvrir immédiatement une session shell à distance Meterpreter sur l'ordinateur de la victime, sans qu'il soit nécessaire d'envoyer d'autres charges utiles telles qu'une scène.

Dans la capture d'écran ci-dessous, nous pouvons voir qu'il y a une étape supplémentaire d'envoi de l'étape après que le stager ait fait un rappel à l'infrastructure de l'attaquant : "sending stage (175174) to 203.0.113.1".

La taille des charges utiles constitue une autre différence entre les deux types d'opérations. Dans la capture d'écran ci-dessous, la charge utile sans étape (meeting_update_stageless.exe) est beaucoup plus volumineuse (245 Ko) que la charge utile initiale avec étape (web1_meeting_update.exe) (73 Ko).

Qu'est-ce qu'un shellcode ?
Le shellcode est un code malveillant qui tente de détourner le flux normal d'un programme en cours d'exécution dans la mémoire de l'ordinateur. Il redirige ensuite le flux de manière à ce que le code malveillant soit exécuté, au lieu du programme normal, ce qui donne à l'attaquant un shell ou un accès direct. Il s'agit souvent de balises ou de charges utiles sous la forme d'un code de programmation de bas niveau ou d'un code machine combiné à un exploit. Les exploits sont des morceaux de code natif ou de bas niveau qui exploitent avec succès une vulnérabilité.
Les vulnérabilités exploitées impliquent souvent un dépassement de mémoire tampon dans la mémoire d'une application où l'attaquant a dépassé la mémoire allouée pour rediriger le flux normal du programme. Un exploit réussi conduira alors à l'exécution d'une charge utile, qui est le logiciel malveillant.
Dans sa forme la plus pure, le shellcode est un code natif ou un code assembleur couramment utilisé dans les exploits liés à la mémoire.
L'exemple ci-dessous montre un shellcode Powershell (ps1).

Cet exemple particulier utilise une bibliothèque de liens dynamiques(DLL) Windows injectée dans la mémoire par l'intermédiaire d'un chargeur réfléchi. Le shellcode est généré sous forme alphanumérique. Une fois exécuté avec succès, il peut se connecter à l'attaquant via une session TCP DNS inversée générée par le Metasploit Framework.
Le choix du mécanisme de diffusion, le type d'exploit et le système cible vulnérable déterminent le choix de la balise ou de la charge utile liée à l'attaque. L'exploit est utilisé pour tirer parti d'une application vulnérable avant d'accéder au système d'exploitation sous-jacent. Dans ce cas, le code spécifique de l'application correspondante peut être utilisé (par exemple, PHP ou ASP pour les applications frontales du serveur web).
Caractéristiques du shellcode
Certaines considérations et caractéristiques importantes doivent être prises en compte pour garantir la réussite de l'exécution du Shellcode et maintenir un niveau élevé de sécurité opérationnelle.
Le code doit :
- Disposer de toutes les instructions nécessaires à l'exécution de l'enveloppe souhaitée tout en étant de taille relativement réduite.
- Être "indépendant de la position" dans la mémoire - ce point est crucial car il n'est souvent pas possible de savoir à l'avance où il sera chargé dans la mémoire du processus vulnérable cible.
- Ne pas contenir d'éléments susceptibles de provoquer des erreurs potentielles ou de faire échouer l'ensemble du processus, par exemple en raison de caractères nuls (0x00).
- Être capable de s'appuyer sur une allocation de mémoire existante à l'aide de techniques d'injection - injection de code ou de réflexion.
À partir de là, l'attaquant devra sélectionner le type d'exécutable post-exploit approprié à exécuter sur le système cible, tel que EXE ou DLL pour Windows, ELF pour Linux et APK pour Android. Là encore, il est préférable d'utiliser des techniques de post-exploitation basées uniquement sur la mémoire afin d'accroître la sécurité opérationnelle.
Qu'est-ce que l'injection de code et l'injection de DLL ?
L'injection de DLL est le processus d'exécution d'un code (DLL) dans le contexte d'un autre processus. Les charges utiles Meterpreter utilisent des techniques d'injection de DLL pour les mécanismes de furtivité et d'évasion.
Sous Windows, une bibliothèque de liens dynamiques ou DLL ("Shared Library" sous Linux) est un morceau de code stocké dans un fichier de bibliothèque partagée. Cela signifie qu'il peut être utilisé par différents programmes informatiques lorsqu'ils en ont besoin. Le système d'exploitation (dans ce cas, Windows) se charge de l'écriture et du chargement de la bibliothèque, ce qui se fait au moment de l'exécution. Un programme peut simplement appeler ou référencer le fichier DLL requis pour utiliser le code qu'il contient.
C'est utile pour les programmeurs car ils n'écrivent le code qu'une seule fois, le compilent et le stockent sous forme de bibliothèque partagée ou DLL, puis l'utilisent à chaque fois que cela est nécessaire et pour plusieurs programmes.
La principale différence entre une DLL et un fichier EXE est qu'une DLL ne peut pas être exécutée indépendamment. Il a besoin d'un programme tel qu'un EXE pour l'appeler ou le référencer, puis l'exécuter. L'exemple suivant montre les fichiers DLL d'un système d'exploitation Windows, qui sont généralement stockés dans le dossier C:\NWindows\NWinSxS (WinSxS est l'abréviation de Windows Side-by-Side).

Les capacités des DLL les rendent également très utiles pour les acteurs de la menace. L'injection de code au niveau de base implique une tentative par un processus (malveillant) d'attacher (ou d'obtenir une poignée) à un processus distant (processus victime). Il alloue ensuite suffisamment de mémoire ou modifie les autorisations de page dans le processus victime pour exécuter un nouveau code tel qu'une DLL, après quoi il copie (injecte) le code malveillant de la DLL dans l'espace mémoire du nouveau processus victime ou du processus déjà en cours d'exécution.
Un nouveau thread, qui permet aux processus d'exécuter des tâches spécifiques, est alors lancé dans le processus victime pour exécuter les instructions contenues dans le code injecté ou la DLL.
Un thread partage le même espace mémoire que le processus qui l'a lancé, alors que les différents processus disposent d'espaces mémoire distincts, en particulier lorsqu'ils ne partagent aucune variable. Cette règle est appliquée par le système d'exploitation. Toutefois, les systèmes d'exploitation fournissent des mécanismes permettant aux processus de communiquer si nécessaire par le biais de la communication interprocessus (IPC), tels que les tuyaux (nommés ou anonymes), les sockets, les sémaphores, la mémoire partagée et les files d'attente de messages.
Dans les systèmes d'exploitation Windows, l'injection de code consiste à utiliser des API et des fonctions Windows légitimes à des fins malveillantes. Par exemple :
- OpenProcess est utilisé pour accéder à un processus,
- VirtualAllocEx facilite alors l'allocation d'une quantité suffisante de mémoire dans ce processus distant ou,
- VirtualProtectEx peut être utilisé pour outrepasser les autorisations de mémoire (page), puis
- WriteProcessMemory écrit le code malveillant, tel qu'une DLL, dans le processus victime.
- CreateRemoteThread, RtlCreateUserThread ou NtCreateThreadEx est utilisé pour créer un nouveau thread (qui est la façon dont les processus exécutent des tâches spécifiques) et exécuter la capacité malveillante, telle que le vol d'informations d'identification ou l'exécution d'un ransomware.
Le chargement d'une DLL sous Windows nécessite l'appel des fonctions LoadLibraryA ou LoadLibraryExA, qui font partie de libloaderapi.h. Ces fonctions, comme le dit Microsoft, "chargent le module spécifié dans l'espace d'adressage du processus appelant". L'utilisation de LoadLibrary pour charger une DLL signifie que les DLL doivent être chargées à partir du disque.
Toutefois, en utilisant la technique d'injection de DLL par réflexion, une DLL peut être chargée directement à partir de la mémoire, une possibilité qui n'est pas offerte actuellement par LoadLibrary. Un acteur de la menace peut utiliser l'injection de DLL réflexive pour charger lui-même son code malveillant entièrement dans la mémoire, sans avoir à invoquer le chargeur natif de Windows sur le disque. Ici, ils utilisent un chargeur personnalisé au lieu de la bibliothèque de chargement de Windows.
Les attaquants peuvent également utiliser d'autres techniques d'injection et de manipulation de processus, telles que :
- Il vide ensuite la mémoire pour faire de la place à un nouveau code, modifie les autorisations de la page, injecte un code malveillant et reprend le processus pour exécuter le code malveillant injecté.
- Chargement latéral de DLL - Lorsqu'un programme Windows légitime et souvent plus ancien (processus victime) est forcé de charger une DLL malveillante, qui porte délibérément le même nom qu'une DLL légitime attendue par la victime et qui est placée dans le même répertoire (côte à côte) que le programme vulnérable. Le processus du programme victime cherchera d'abord dans son dossier immédiat la DLL malveillante renommée (usurpée). Cette technique exploite l'ordre de recherche des DLL utilisé par le chargeur Windows pour réussir.
Ces techniques tentent de faire passer une activité malveillante pour une activité légitime, ce qui permet d'échapper à la détection et de persister sur un système compromis.
À partir des techniques de manipulation de processus présentées dans la première partie de cette série, nous pouvons voir ci-dessous que la migration du fichier malveillant original web1_meeting_update.exe a impliqué une injection de code.
Dans cet exemple, la charge utile malveillante a démarré (engendré) un tout nouveau processus notepad.exe en tant que processus victime souhaité pour injecter du code. Le processus malveillant d'origine (PID 2472) est ensuite injecté ou migré dans le nouveau processus victime notepad.exe (PID 1768).

Notepad.exe est un processus Microsoft de confiance et, en tant que tel, il permet à l'attaquant de déguiser le processus original malveillant en un processus plus fiable. Notepad.exe est un éditeur de texte Windows de base qui n'a pas besoin d'appeler (de faire référence à) une API, une fonction ou une DLL Windows requise pour la connexion au réseau - ce que, dans notre exemple, le processus malveillant a exécuté.
L'analyse dynamique est nécessaire pour détecter l'injection qui se produit dans la mémoire. La cartographie des dépendances applicatives est également nécessaire pour détecter les communications réseau suspectes de l'éditeur de texte, en l'occurrence le processus notepad.exe, qui établit une connexion réseau.
Conclusion
Suite à la première partie de cette série de blogs, nous avons examiné certaines catégories et certains types de charges utiles qu'un acteur de la menace peut utiliser et les raisons pour lesquelles il peut décider d'utiliser un type plutôt qu'un autre.
La plupart des auteurs de menaces se donnent beaucoup de mal pour réussir leur entrée initiale, maintenir leur persistance et éviter d'être détectés. Nous avons vu jusqu'à présent que la combinaison d'outils, de techniques et de procédures signifie qu'une combinaison tout aussi performante d'outils et de procédures de sécurité est nécessaire pour empêcher une attaque réussie - ou plus probablement pour assumer la violation et empêcher qu'un incident cybernétique initial ne devienne une violation majeure.
Dans la dernière partie de cette série, nous aborderons d'autres techniques et capacités et, surtout, nous explorerons l'analyse et l'atténuation.