Démantèlement de KillDisk : rétro-ingénierie du composant destructeur de BlackEnergy

[post-views]
février 29, 2016 · 12 min de lecture
Démantèlement de KillDisk : rétro-ingénierie du composant destructeur de BlackEnergy

Passons l’introduction longue sur la menace BlackEnergy et allons directement à l’étude du composant malveillant appelé « ololo.exe », également connu du public sous le nom de KillDisk. KillDisk est un module du cadre BlackEnergy visant à la destruction de données et à créer le chaos/distraction pendant les opérations APT.

https://www.virustotal.com/en/file/11b7b8a7965b52ebb213b023b6772dd2c76c66893fc96a18a9a33c8cf125af80/analysis/Les principaux outils utilisés dans notre analyse aujourd’hui sont Process Monitor qui fait partie des utilitaires SysInternals de Mark Russinovich et IDA Pro Disassembler. Toutes les manipulations seront effectuées dans un environnement virtuel basé sur le système d’exploitation Windows XP. Nous commençons par une configuration initiale rapide de la VM de test, nous allumons la machine et créons un instantané appelé « Avant l’infection ». Commençons !

Pour suivre tous les événements liés à notre objet d’étude nous lançons Process Monitor et nous assurons qu’il reste visible :BlackEnergy-killdisk-1Et qu’il suit le processus nécessaire :

BlackEnergy-killdisk-2Ensuite, nous chargeons le virus dans IDA Pro Disassembler et voyons l’image suivante.C’est la fonction WinMain, c’est-à-dire la fonction principale, donc nous commençons son analyse et voyons que la première (et seule) chose qu’elle fait est un appel de procédure nommé sub_40E070:

 BlackEnergy-killdisk-3
Allons donc directement à cette procédure. Ici, nous pouvons trouver des procédures responsables de l’extraction des extensions de fichiers en mémoire et la première procédure juste après celles-ci, qui appelle une chose intéressante appelée sub_40E080:

BlackEnergy-killdisk-4Examinons de plus près son contenu :BlackEnergy-killdisk-5Son intérieur contient un appel système intéressant CreateFile et quelques procédures sub_40C390 and sub_40C400. Il est donc temps de prendre un instantané de notre laboratoire virtuel et de continuer notre voyage !

Examinons l’appel de fonction CreateFile, définissez le Point d’arrêt sur celui-ci et exécutez notre échantillon d’étude :

BlackEnergy-killdisk-6Un petit indice concernant cette fonction :

CreateFile la fonction crée ou ouvre un fichier ou un périphérique d’I/O. Les périphériques d’I/O les plus couramment utilisés sont les suivants : fichier, flux de fichiers, répertoire, disque physique, volume, tampon de console (CONIN$ or CONOUT$), bande magnétique, ressource de communication, mailslot et canal nommé. La fonction retourne un handle qui peut être utilisé pour accéder au fichier ou au périphérique pour divers types d’I/O selon le fichier ou le périphérique et les flags et attributs spécifiés.

Comme nous le savons, la plupart des compilateurs passent les arguments à une fonction via une pile et puisque nous traitons d’un échantillon initialement écrit en C, et selon les directives C, les arguments sont poussés sur la pile de droite à gauche de manière à ce que le premier argument de la fonction soit poussé en dernier sur la pile et devienne le premier élément en haut. [1].

There is a special register for working with the stack – ESP (Extended Stack Pointer). The ESP, by definition, always points to the top of the stack:

BlackEnergy-killdisk-41Un aperçu de la pile :BlackEnergy-killdisk-7Le dernier argument qui a été poussé dans la pile (celui en haut) est le premier argument accepté par la fonction (selon la syntaxe de CreateFile fonction) et il contient le nom de fichier dans notre cas \.PHYSICALDRIVE0, ainsi la cible est le premier disque physique.BlackEnergy-killdisk-8Nous analysons maintenant d’autres arguments envoyés à la fonction en prenant leurs valeurs de la pile :BlackEnergy-killdisk-9Quelques conclusions :

  • lpFileName – pointeur vers une chaîne ASCIIZ contenant le nom (chemin) du fichier ouvert ou créé (comme nous l’avons déjà découvert) ;
  • dwDesiredAccess – type d’accès au fichier : dans notre cas, la valeur est 0C0000000h ce qui signifie GENERIC_READ+GENERIC_WRITE or accès en lecture-écriture;
  • DwShareMode – un mode qui permet le partage des fichiers avec différents processus et ce paramètre peut avoir différentes valeurs, dans notre cas la valeur de 00000003b se traduit parFILE_SHARE_READ+FILE_SHARE_WRITE oud'autres processus peuvent ouvrir le fichier en lecture/écriture;
  • IpSecurityAttributes – pointeur vers une structure SecurityAttributes (fichier winbase.h) qui définit les paramètres de sécurité liés à l’objet fichier noyau, si la sécurité n’est pas définie une valeur NULL est utilisée ;
  • dwCreationDistribution – est responsable de décider des actions lorsque le fichier existe ou non, dans notre cas une valeur de 3 signifie OPEN_EXISTING or ouvrir le fichier s'il existe, et retourner une erreur s'il n'existe pas;
  • DwFlagsAndAttributes – flags et attributs; ce paramètre est utilisé pour définir les caractéristiques du fichier créé et est ignoré en mode lecture 0.
  • hTemplateFile – le paramètre n’est utilisé que lors de la création d’un nouveau fichier 0.
  • En cas de succès, la fonction retourne le nouveau gestionnaire de fichier à ЕАХ Et si la fonction échoue un NULL est écrit dans le ЕАХ registre.

Bien, donc après avoir appelé CreateFile nous obtenons une valeur non nulle pour EAX ce qui signifie qu’il contient le gestionnaire du fichier demandé par ex. \.PHYSICALDRIVE0:

BlackEnergy-killdisk-26Nous avons notre gestionnaire alors passons à l’appel suivant, sub_40C390:

BlackEnergy-killdisk-10Comme précédemment, nous regardons à l’intérieur de la procédure et voyons un autre appel intéressant :BlackEnergy-killdisk-11Indice de la fonction :

DeviceIoControl fonction envoie un code de contrôle directement à un pilote de périphérique spécifié, entraînant l’exécution de l’opération correspondante par le périphérique.

Un coup d’œil à la pile juste avant d’appeler cette fonction :BlackEnergy-killdisk-11Conclusions :

  • hDevice – descripteur de périphérique (nous reconnaissons un vieil ami ici \.PHYSICALDRIVE0);
  • dwIoControlCode – code d’opération. Dans notre cas, la valeur est 0x70000h ce qui signifie IOCTL_DISK_GET_DRIVE_GEOMETRY ou récupération des informations concernant la géométrie des disques (nombre de cylindres, type de média, pistes sur cylindre, secteurs par piste, octets par secteur) ;
  • lpInBuffer – tampon avec des données d’entrée. Nous n’avons pas besoin de données d’entrée donc NULL il est ;
  • nInBufferSize – taille du tampon d’entrée en octets égale à 0 dans notre cas ;
  • lpOutBuffer – pointeur vers le tampon de sortie. Son type est défini par le paramètre dwIoControlCode 0x0011FCA8h;
  • nOutBufferSize – taille du tampon de sortie en octets 0x18h (24) ;
  • lpBytesReturned – un pointeur vers une variable qui contient la quantité d’octets écrits dans la sortie 0x0011FCA4h;
  • lpOverlapped – pointeur vers une structure OVERLAPPED ;
  • Lorsque l’appel est traité nous regardons à l’intérieur du tampon (à l’adresse qui a été envoyée comme argument à lpOutBuffer précisément 0x0011FCA8h):

BlackEnergy-killdisk-12Interprétons la réponse :

  • À l’adresse de 0x0011FCA4h nous avons un retour de la quantité d’octets écrits dans le tampon de sortie comme prévu par la fonction (marqué en vert) nous avons autant que demandé 24 symboles.
  • À l’adresse de 0x0011FCA8h nous avons des informations sur la géométrie du premier disque physique (\.PHYSICALDRIVE0):
    • nombre de cylindres – 0x519h (1305)
    • type de média – 0x0Ch (12) signifiant disque dur fixe.
    • pistes par cylindre – 0x0FFh (255)
    • secteurs par piste – 0x3Fh (63)
    • octets par secteur – 0x200 (512)

Nous nous référons maintenant à la procédure trois, sub_40C400:

BlackEnergy-killdisk-13À l’intérieur de la procédure deux appels ont immédiatement attiré notre attention, en particulierSetFilePointerExetWriteFile:

BlackEnergy-killdisk-14Indice de fonction :

SetFilePointerEx fonction déplace le pointeur de fichier du fichier ouvert spécifié.

Comme d’habitude, nous regardons dans la pile d’appels de la fonction :BlackEnergy-killdisk-15

  • hfile – notre descripteur de périphérique familier \.PHYSICALDRIVE0;
  • liDustanceToMove – position initiale pour commencer une écriture 0;
  • lpNewFilePointer – pointeur vers une variable qui récupère un nouveau pointeur de position depuis le fichier 0x0011FCA8h. Dans le cas où le paramètre est VIDE (NULL) un nouveau pointeur n’est pas renvoyé dans le fichier ;
  • dwMoveMethod – taille du tampon d’entrée en octets. Dans notre cas 0x200h (512) – octets dans le secteur ;

Maintenant notre « bête » utilise la WriteFile fonction et pour remplir le fichier de zéros auquel il a déjà établi un accès direct et fiable.

Indice de fonction :

Une WriteFile fonction écrit des données dans le fichier ou périphérique d’entrée/sortie spécifié à partir de la position spécifiée dans le fichier. Cette fonction est conçue pour une opération à la fois synchrone et asynchrone.

Liste des arguments de la fonction :BlackEnergy-killdisk-16Bien, pour nous assurer de ne rien manquer d’intéressant, nous regardons le tableau de bord et parmi toutes les choses nous prêtons attention à la pile :BlackEnergy-killdisk-17Tout est prêt pour l’action :

  • 1 – resté sur l’appel de fonction WriteFile
  • 2 – Process Monitor anticipe anxieusement le moindre mouvement de notre « bête »
  • 3 – arguments poussés des registres vers la pile
  • 4 – et bien, la pile elle-même
  • Un carré dédié dans la fenêtre Hex View-EBP nous avons marqué une zone qui est adressée par le deuxième argument de la fonction (tampon de données) et vous pouvez me croire sur parole – elle contient exactement 0x200h (512) de zéros.

Nous exécutons la commande en pressant la touche F8 et observons le changement :BlackEnergy-killdisk-18Comme prévu, l’œil attentif de Process Monitor est en plein dans le mille et fixe l’écriture de 512 octets à partir de la position 0.

Ensuite, la procédure se termine et est appelée à nouveau mais maintenant avec des valeurs différentes :

Maintenant nous prenons les 512 octets suivants ce qui est particulièrement visible à 0x200 (contrairement aux arguments de pile précédents que nous avons marqués par un cadre bleu)BlackEnergy-killdisk-19Et ils subissent le même sort que les précédents :BlackEnergy-killdisk-20Ce manège continue jusqu’à ce que la vérification de 0x40C865h adresse renvoie la valeur de EBX registre égale à 255 fois (Pour s'assurer que cela s'est produit, nous plaçons un point d'arrêt sur l'instruction qui suit notre cycle vicieux de « destruction » : ):

BlackEnergy-killdisk-21En tout nous recevons un

BlackEnergy-killdisk-22d’opérations de réécriture : 256 C’est à peu près tout, notre « bête » a fait son sale travail et ferme le gestionnaire de fichier en appelant laBlackEnergy-killdisk-23

That’s basically it, our “beast” has done its dirty work and closes the file handler by calling the CloseHandle fonction.

Indice de fonction :

Une CloseHandle fonction ferme un handle d’objet d’objet ouvert.

Arguments de la fonction :

BlackEnergy-killdisk-24Notre vue traditionnelle de la préparation de la pile :

BlackEnergy-killdisk-25Et qu’avons-nous dans le registre ESI ?BlackEnergy-killdisk-26Comme prévu notre descripteur familier 0x44h est passé à la pile comme argument de la fonction.

Nous voyons les valeurs retournées par la fonction dans le registre EAX : BlackEnergy-killdisk-27Si la fonction se termine avec succès la valeur retournée non nulle.

Si la fonction existe avec une erreur la valeur retournée est nulle.

Cette action n’a pas échappé à l’œil vigilant de notre Process Monitor:

BlackEnergy-killdisk-28Tout a fonctionné normalement (si vous pouvez appeler la destruction de la partie la plus importante du lecteur système normale) et le fichier est fermé.

Continuons :BlackEnergy-killdisk-29Pour une perception plus confortable, j’ai renommé la fonction en Effaceur. Après avoir détruit avec succès les données sur PhysicalDrive0, le compteur situé dans ESI registre est augmenté de 1, vérifié avec la valeur de 0x0Ah (10) et lance une opération de réécriture avec une nouvelle valeur :BlackEnergy-killdisk-30et ainsi de suite jusqu’à ce que 10 soit atteint.

De cette façon, la bête continue de parcourir tous les disques physiques et d’effacer les premiers 512 * 255 = 128ko (cela dépend du nombre d’octets dans un secteur que la « bête » a appris en connaissant la géométrie du disque).

Cette fois cependant, le résultat de la fonction CreateFileW sera une valeur suivante : BlackEnergy-killdisk-31Ce qui signifie que dans notre « laboratoire improvisé », il n’y a pas de deuxième disque physique (ou bien qu’il n’existe pas de disque avec un numéro de séquence supérieur à 0) et que le cycle n’a rien à réécrire.part1_op_enMaintenant que nous sommes armés de connaissances fraîchement acquises, essayons de désactiver la fonction destructrice que nous venons d’apprendre en modifiant le code de manière à ce que notre modification puisse permettre de contourner la réécriture des données :

BlackEnergy-killdisk-33Souvenons-nous de la procédure sub_40E080 qui contient l’appel de fonction CreateFileW. Puisque nous savons que la valeur de 0xFFFFFFFFh apparaîtrait après l’achèvement de la fonction CreateFileW uniquement si le disque physique n’est pas présent et dans un tel scénario, la fonction « destruction de données » effectue une sortie, changeons une instruction de branchement conditionnel située à l’adresse 0x0040C7E4h à une instruction inconditionnelle :

 BlackEnergy-killdisk-34
IDA Pro est un outil intéressant, alors nous devons juste écrire une commande.

La seule chose que nous devons considérer est la taille de la commande avant et après, donc vérifions combien d’octets sont utilisés par la séquence conditionnelle :

BlackEnergy-killdisk-35Comme nous pouvons le voir, une instruction de branchement conditionnelle équivaut à 6 octets, tandis que la séquence inconditionnelle ne prendra que 5 et donc nous devons ajouter une autre commande – NOP, afin de garder l’équilibre :BlackEnergy-killdisk-36Nous faisons un NOP sur la commande suivante, peu importe ce qu’elle contenait avant (même si nous voyons un 0 là) :

BlackEnergy-killdisk-37Nous vérifions deux fois que les adresses des commandes sont maintenant égalisées en fonction de l’adresse de la commande suivante :

BlackEnergy-killdisk-38et appuyons sur annuler. Terminé.

Après ces manipulations, la procédure semblera simple : entrer et sortir, en faisant une boucle vide (puisque nous ne savons pas quelles autres parties du programme peuvent s’y accrocher, nous la laissons telle quelle).BlackEnergy-killdisk-39La preuve finale trouvée par Process Monitor indique qu’aucun disque physique n’a été endommagé suite à l’exécution disséquée de KillDisk :

BlackEnergy-killdisk-40Le malware a lu les informations sur le disque, les a étudiées et a continué ses affaires que nous analyserons en profondeur dans nos prochains articles.

Sources :

[1] – Hacker Disassembling Uncovered. Kris Kaspersky, 2009.

Traduit de l’original par Andrii Bezverkhyi | CEO SOC Prime

Cet article vous a-t-il été utile ?

Aimez-le et partagez-le avec vos collègues.
Rejoignez la plateforme Detection as Code de SOC Prime pour améliorer la visibilité des menaces les plus pertinentes pour votre entreprise. Pour vous aider à démarrer et générer une valeur immédiate, réservez dès maintenant une réunion avec les experts de SOC Prime.