En tant que chercheur de logiciels malveillants , vous êtes souvent confronté à des échantillons complexes et fortement obscurcis. Et GuLoader, le malware auquel nous avons affaire aujourd'hui, en est un exemple classique.
Regardez ce pseudo-code, produit en décompilant son code assembleur, il est laid et illisible.
Face à un casse-tête comme celui-ci, vous pourriez vous retrouver désemparé. Par où commencer ? Comment abordez-vous l'analyse de cet échantillon ? Décomposons-le.
Dans cet article, nous explorerons donc des stratégies pour désobscurcir ce code, en utilisant GuLoader comme référence. Vous en apprendrez plus sur :
L'article est basé sur l' analyse des logiciels malveillants GuLoader précédemment publiée par ANY.RUN. Visitez notre blog pour trouver l'échantillon que nous allons analyser, ainsi que des instructions de déballage et un script Ghidra qui automatise partiellement une grande partie de ce que nous allons couvrir à partir de maintenant.
Cet article se concentre sur l'analyse statique. Mais si vous souhaitez analyser dynamiquement un échantillon de GuLoader, vous pouvez utiliser le bac à sable de logiciels malveillants cloud ANY.RUN .
Demandez un essai gratuit de 14 jours du plan Entreprise en utilisant votre adresse e-mail professionnelle. Profitez de la durée de vie prolongée des instances de VM, des tâches illimitées et des versions 7 à 11 de Windows.
L'analyse dynamique vous permet de voir comment les logiciels malveillants fonctionnent dans le monde réel en liant son comportement à sa configuration technique. C'est un moyen de vérifier ses actions dans différentes configurations système et de collecter les IOC.
Sans plus tarder, plongeons dedans.
Après avoir déballé l'échantillon, nous commençons par une analyse manuelle du shellcode et réalisons rapidement qu'il est obscurci. L'étude du code nous permet de regrouper les techniques d'obfuscation employées par GuLoader en catégories :
C'est le bon moment pour s'arrêter, analyser les méthodes d'obfuscation utilisées et développer une stratégie de débofuscation.
Par exemple, dans notre cas, la plupart de ces techniques introduisent du code qui ne modifie pas le résultat final de l'exécution. Par conséquent, nous pouvons souvent les « NOP » en toute sécurité pour améliorer la lisibilité. Mais procédez avec prudence - tout le code obscurci n'est pas sans rapport avec le fonctionnement du programme, comme nous le découvrirons bientôt.
Maintenant, examinons ces techniques d'obscurcissement individuellement et voyons comment les vaincre.
Le code est jonché de nombreuses instructions XMM. Ceux-ci semblent désordonnés et ajoutent de la complexité au processus d'analyse. Nous les rencontrons dès le premier octet du shellcode décompressé.
Notez que de nombreux moteurs d'émulation trébuchent dessus en raison du manque de prise en charge par défaut. Nous avons mis à l'épreuve les moteurs embarqués d'Angr, Triton et Ghidra - tous ont échoué.
Dans le cas de GuLoader, les instructions XMM n'ont pas réellement d'impact sur le comportement prévu du code. Vous rencontrerez des méthodes d'obscurcissement similaires dans de nombreux logiciels malveillants. Par conséquent, nous pouvons remplacer en toute sécurité toutes les instructions XMM par "NOP", comme indiqué dans le tableau suivant :
Voici à quoi ressemble le résultat dans le désassembleur Ghidra :
Les instructions JMP inconditionnelles segmentent le code en plus petits morceaux. Cette méthode est fréquemment utilisée pour éviter la détection par un logiciel antivirus et d'autres outils de sécurité. De plus, cela peut rendre le travail des analystes plus long et frustrant, car ils doivent sauter entre ces blocs, en particulier lorsqu'ils traitent une grande quantité de code. GuLoader et d'autres logiciels malveillants utilisent couramment cette technique.
Cette méthode d'obscurcissement est assez facile à vaincre. Le désassembleur dans le code décompilé concatène souvent avec succès ces blocs, améliorant la lisibilité du code, même avec ces sauts inconditionnels présents. Ainsi, nous pouvons laisser les petits blocs tels quels sans avoir besoin de les fusionner.
Les instructions d'assemblage indésirables entrent souvent en jeu comme une couche supplémentaire d'obscurcissement. Ces instructions n'exécutent aucune fonction tangible, laissant généralement les valeurs de registre, le flux d'exécution ou la mémoire inchangés.
Vous les rencontrerez également dans GuLoader.
Faites attention aux instructions qui n'effectuent aucune action ("NOP", "FNOP") et celles qui décalent ou tournent de zéro bit ("SHL reg, 0" ; "ROL reg, 0"). D'autres instructions sans impact comme "OR reg, 0", "XOR reg, 0", "CLD", "WAIT" sont également présentes.
Traiter les fausses instructions de comparaison est plus difficile que de simplement remplacer les instructions inutiles par "NOP". Nous ne pouvons pas supprimer toutes les instructions de comparaison car certaines sont nécessaires pour que le code fonctionne correctement. Une façon de résoudre ce problème consiste à « marquer » toutes les instructions de comparaison que nous rencontrons. Si aucune instruction utilisant le résultat de la comparaison n'est trouvée, vous pouvez la désactiver en toute sécurité. Si nous trouvons un saut conditionnel ou similaire, nous décochons la comparaison pour éviter la suppression.
Le tableau suivant montre un exemple où toutes les instructions de comparaison sauf "CMP EDX,0x0" ont été sélectivement remplacées par NOP :
GuLoader utilise également la tactique d'obscurcissement consistant à utiliser de fausses instructions "PUSHAD", couplées à une instruction "POPAD" correspondante. Ils peuvent temporairement modifier les valeurs des registres mais sont annulés par le "POPAD" qui restaure les valeurs des registres d'origine.
Nos recherches ont montré que toutes les instructions "PUSHAD" dans GuLoader sont superflues. Donc, nous abordons cela en remplaçant "PUSHAD", "POPAD" et les instructions intermédiaires par NOP :
Cependant, toutes les instructions "POPAD" dans GuLoader ne sont pas inutiles. Nous laissons ceux sans "PUSHAD" correspondant intacts.
Une autre technique d'obscurcissement similaire à la précédente est l'utilisation de fausses instructions "PUSH". Ces instructions poussent une valeur sur la pile uniquement pour la supprimer immédiatement.
Un exemple est l'inclusion d'une instruction "PUSH SS", éventuellement suivie d'instructions modifiant un registre ou un emplacement mémoire. Cependant, le "POP SS" suivant restaure le pointeur de pile à sa valeur initiale.
Vaincre les fausses instructions PUSH ressemble au processus pour les faux PUSHAD, mais il est crucial de ne pas modifier les registres non poussés.
Les prédicats opaques sont des instructions conditionnelles qui renvoient toujours vrai ou faux, mais ils sont difficiles à analyser ou à prévoir. Ceux-ci se trouvent dans le code de GuLoader et compliquent la compréhension de la logique.
Par exemple, une paire d'instructions comme "MOV BL, 0xB6" et "CMP BL, 0xB6" pourrait être suivie d'un saut conditionnel comme "JNZ ADDR". La comparaison renvoie toujours faux car la valeur comparée est égale à la valeur déplacée, ce qui rend le saut inutile et déconcertant.
Surmonter les prédicats opaques peut sembler difficile en raison de l'exigence de "prédire" la condition de saut. Cependant, notre situation est plus simple car tous les prédicats opaques relèvent des blocs "PUSHAD" et "POPAD". Par conséquent, nous remplaçons simplement tous les prédicats entre ces instructions par NOP.
Les expressions arithmétiques obscurcies font partie des techniques les plus intéressantes utilisées par GuLoader. Ils compliquent la compréhension des opérations réelles effectuées. Ces expressions incorporent des actions arithmétiques comme l'addition ou la soustraction. Parfois, ils sont mélangés à d'autres obscurcissements comme de fausses comparaisons, des prédicats opaques et des instructions indésirables.
Un exemple consiste à déplacer une valeur constante dans un registre et à exécuter des opérations arithmétiques :
Une autre instance consiste à pousser une valeur constante sur la pile et à effectuer des calculs sur la mémoire :
Pour désobscurcir les expressions arithmétiques de GuLoader, nous adoptons une approche similaire à la gestion de fausses comparaisons. Nous marquons toutes les instructions "MOV" où le deuxième argument est une valeur scalaire, et toutes les instructions "PUSH" où l'argument est scalaire. En rencontrant une opération arithmétique, nous mettons à jour la valeur constante dans la première instruction et remplaçons la valeur actuelle par NOP. De cette façon, la première instruction a le résultat et les instructions arithmétiques suivantes sont remplacées par NOP.
Vous trouverez ci-dessous des exemples avec des opérations « MOV » et « PUSH » optimisées :
Soyez prudent avec les tailles d'opérandes. Préserver la bonne taille pendant les opérations est crucial.
Dans cet article d'experts ANY.RUN , nous avons utilisé GuLoader comme exemple concret pour mettre en évidence les stratégies de désobscurcissement typiques. Nous avons commencé par analyser le code, puis avons regroupé des tactiques d'obscurcissement similaires, et enfin, nous avons pensé à des façons uniques de les aborder.
Mais il est crucial de comprendre que ces techniques ne sont pas une solution unique. Chaque échantillon de logiciel malveillant peut présenter ses stratégies d'obscurcissement uniques qui nécessitent des contre-mesures uniques.
Notre discussion souligne l'importance de décomposer une tâche complexe comme la désobfuscation en étapes claires et gérables. N'oubliez pas qu'une désobfuscation réussie de tout logiciel malveillant implique une analyse détaillée, la détection de schémas d'obscurcissement et la création de stratégies optimisées.