
POC pour frustrer/déjouer les Analystes de Malwares
Le travail qui suit est une POC permettant à un logiciel malveillant de se « verrouiller » sur une victime particulière afin de contrecarrer les efforts des analystes de malware.
Je décline toute responsabilité en cas d'utilisation malveillante des idées ou du code contenus dans ce projet. Je fournis cette recherche pour continuer à former les professionnels de la sécurité informatique et offrir une formation supplémentaire/une matière à réflexion aux analystes de malware, ingénieurs en rétro-ingénierie et équipes bleues en général.
La première fois que le malware s'exécute sur une victime, il chiffre AES la charge utile réelle (un RDLL) en utilisant des données environnementales de cette victime. Chaque fois que le malware est exécuté par la suite, il collecte ces mêmes informations environnementales, déchiffre AES la charge utile stockée sous forme de tableau d'octets dans le malware, et l'exécute. Si le déchiffrement échoue ou si la charge utile ne s'exécute pas, le malware se supprime. Protection contre les ingénieurs en rétro-ingénierie et les analystes de malware.


Je n'avais pas l'impression d'avoir terminé ce projet, alors j'y suis retourné et j'ai effectué une réécriture assez conséquente. La recherche originale et le savoir-faire technique peuvent être trouvés ici.
Les changements majeurs sont les suivants :
Il y a pas mal de choses différentes qui pourraient être extraites du code source de ce projet pour être utilisées ailleurs. Espérons que cela sera utile à quelqu'un.
Il y avait quelques lacunes dans la version originale de BeatRev que j'ai décidé d'essayer de corriger.
Stage2 était auparavant un exécutable autonome stocké sous forme de flux de données alternatif (ADS) de Stage1. Pour réaliser le chiffrement AES par victime puis le déchiffrement et l'exécution, chaque fois que Stage1 était exécuté, il lisait l'ADS, le déchiffrait, le réécrivait dans l'ADS, appelait CreateProcess, puis rechiffrait Stage2 et le réécrivait sur le disque dans l'ADS. Cela représentait beaucoup d'opérations d'E/S et l'appel CreateProcess n'était bien sûr pas idéal.
Je suis tombé sur les recherches de Steven Fewer concernant les DLL réflexives et cela semblait bien convenir. Stage2 est maintenant un RDLL ; notre malware/lanceur de shellcode/ce que nous voulons protéger peut être porté au format RDLL et stocké sous forme de tableau d'octets dans Stage1, qui est ensuite déchiffré à l'exécution et exécuté par Stage1. Cela supprime toutes les opérations d'E/S et l'appel CreateProcess de la version 1 et constitue un changement bienvenu.
Stage1 n'avait aucune véritable mesure d'évasion antivirale programmée ; c'était intentionnel, car c'est un travail supplémentaire et ce n'était pas vraiment le but de cette recherche. Lors de la réécriture, j'ai relevé le défi supplémentaire et ajouté du hachage d'API pour supprimer les fonctions de la table d'adresses d'importation de Stage1. Cela a aidé pour la détection et Stage1 a un taux de détection de 4/66 sur VirusTotal. J'étais à l'aise pour télécharger Stage1 étant donné qu'il est déjà lié à la machine d'origine sur laquelle il a été exécuté et que la signature du fichier change constamment en raison du chiffrement AES qui se produit.
J'ai récemment commencé à prêter attention à l'entropie comme moyen de détecter les malwares ; pour essayer de réduire l'entropie très élevée qu'un gros blob binaire chiffré AES donne à un exécutable, j'ai cherché à intégrer du shellcode stocké sous forme d'UUID. Comme le binaire est stocké sous forme de chaîne, l'entropie globale de l'exécutable est plus faible. Grâce à cette technique, l'entropie de Stage0 est maintenant d'environ 6,8 et celle de Stage1 d'environ 4,5 (sur une échelle maximale de 8).
Enfin, c'est une tâche énorme d'intégrer et de produire un Stage0 complet en raison de toutes les pièces qui doivent être manipulées. Pour faciliter cela, j'ai créé une application de construction qui prend un fichier modèle Stage0.c, un stub Stage1, un stub Stage2 et un fichier shellcode brut (cela a été construit autour de Stage2 étant un lanceur de shellcode contenant du shellcode CobaltStrike) et produit une charge utile Stage0 compilée pour être utilisée sur la cible.
Le code DLL réflexive de Stephen Fewer contient des instructions spécifiques au compilateur Visual Studio ; je suis sûr qu'il est possible de porter la technique sur MingW mais je n'ai pas les compétences pour le faire. Le principal problème ici est que le shellcode CobaltStrike (sans état fait ~265K) doit être placé à l'intérieur du RDLL et être compilé. Pour contourner cela et l'intégrer harmonieusement au reste du processus, j'ai écrit mon RDLL Stage2 pour contenir un bloc de mémoire variable globale de la taille du shellcode CS ; ce bloc de mémoire d'environ 265K contient un petit espace réservé qui peut être localisé dans le binaire compilé. Le code dans src/Stage2 a déjà cela ajouté.
Une fois compilé, ce Stage2stub est transféré sur kali où un patch binaire peut être effectué pour insérer le vrai shellcode CS à l'endroit de la mémoire qui lui appartient. Cela produit le Stage2 complet.
Pour éviter le fiasco des E/S et de CreateProcess décrit précédemment, le Stage2 complet doit également être patché dans le Stage1 compilé par Stage0 ; cela est nécessaire pour permettre à Stage2 d'être chiffré une fois sur la cible en plus d'empêcher Stage2 d'être stocké séparément sur le disque. Le même concept décrit précédemment pour Stage2 est réalisé par Stage0 sur la cible afin d'assembler la charge utile finale de Stage1. Il est à noter que la fonction memmem est utilisée pour localiser l'espace réservé dans chaque stub ; cette fonction n'est pas disponible sur Windows, donc une implémentation personnalisée a été utilisée. Merci à Foxik384 pour son code.
Pour effectuer un patch binaire, nous devons allouer la mémoire nécessaire à l'avance ; cela a un effet cumulatif, car Stage1 doit maintenant être suffisamment grand pour contenir également Stage2. Avec l'étape supplémentaire de conversion de Stage2 en chaîne UUID, Stage2 gonfle en taille tout comme Stage1 pour le contenir. Un RDLL Stage2 avec une taille compilée d'environ 290K donne une charge utile Stage0 d'environ 1,38M et une charge utile Stage1 d'environ 700K.
L'application de construction ne supporte que la création d'exécutables x64. Cependant, avec un peu plus de travail, en théorie, vous pourriez faire de Stage0 une DLL, ainsi que Stage1, et faire exister tout le cycle de vie comme un détournement de DLL au lieu d'un exécutable autonome.
Ces instructions vous permettront d'utiliser cette POC.