Analyse du malware Buer Loader écrit en Rust

L’analyse de malwares fait partie du quotidien de l’équipe CTI. Cet article présente l’analyse d’une souche Rust de Buer Loader depuis la réception des échantillons jusqu’à l’écriture d’un script d’extraction de stage2*. Malgré plusieurs mécanismes de protection, il a été possible d’extraire tous les échantillons de manière différentes. TEHTRIS fournit le code permettant une telle extraction.

Sommaire

Introduction

Le langage Rust [1] est de plus en plus utilisé [2] chez les développeurs. En effet, la philosophie de ce langage est de maximiser la sécurité tout en offrant des performances proches du C++ dans une syntaxe agréable. De nombreux projets ambitieux comme Kerla [3] visent à offrir un système d’exploitation compatible avec n’importe quel binaire Linux, sans modification ! Ceci offre à Rust beaucoup de crédibilité et explique son adoption chez les développeurs. D’un point de vue bas niveau, ce langage génère un code binaire plus complexe à signer et analyser par rapport à du C en proposant davantage d’instructions et en ajoutant de l’offuscation à peu de frais, ce qui intéresse les auteurs de malware.

Encore peu nombreux à l’adopter, il n’est pourtant pas étonnant de noter que ce langage commence à se faire une place dans le bestiaire des malwares, comme le montre Buer Loader [3] qui comporte des variants développés dans ce langage.

Si ce malware a déjà été analysé [3], il reste intéressant d’en étudier le « stage1* » pour identifier les mécanismes de protection et les évolutions concernant son offuscation au cours du temps. En effet, des techniques de contournement de sandbox [5] et de chargement en mémoire de binaires offusqués [6] par un mécanisme inconnu ont été observés. Cela a suffi à susciter l’intérêt d’une rétro-analyse du loader en question.

L’objectif est de caractériser et d’extraire la charge principale du malware qui a plus particulièrement été repéré dans les environnements informatiques entre juillet et aout 2021.

Une méthode d’extraction par émulation à été présentée à la conférence Hack-It-N de décembre 2021. Une diffusion en différé de la présentation est disponible sur Youtube [16].

Analyse

Les échantillons récupérés fonctionnent sur le même modèle, à savoir une base en Rust chargeant en mémoire un stage2, lui-même écrit en Rust. Voici la liste des échantillons récupérés :

Sha256

Compile time

Size

001405ded84e227092bafe165117888d423719d7d75554025ec410d1d6558925

2021-07-28 17:59:19

3.4 MB

4421dbc01ddc5ed959419fe2a3a0f1c7b48f92b880273b481eb249cd17d59b91

2021-08-11 15:35:55

6.6 MB

52d8316b0765c147558aecbda686d076783f3a08b2741b8c9e3e717cc56e8a92

2021-07-19 13:57:22

7.3 MB

580d55f1e51465b697d46e67561f3161d4534a73e8aa47e18b9bae344d46bcf4

2021-07-19 13:57:22

7.3 MB

578dc62dfa0203080da262676f28c679114d6b1c90a4ab6c07b736d9ce64e43e

2021-07-15 16:28:54

7.1 MB

5ac6766680c8c06a4b0b4e6a929ec4f5404fca75aa774f3eb986f81b1b30622b

2021-08-05 14:47:25

4.9 MB

64dd547546394e1d431a25a671892c7aca9cf57ed0733a7435028792ad42f4a7

2021-08-16 18:54:29

4.1 MB

88689636f4b2287701b63f42c12e7e2387bf4c3ecc45eeb8a61ea707126bad9b

2021-08-03 15:46:42

3.3 MB

afb5cbe324865253c7a9dcadbe66c66746ea360f0cd184a2f4e1bbf104533ccd

2021-06-25 17:49:48

7.1 MB

c425264f34fa8574c7e4321020eb374b9364a094cda9647e557b97d5e2b8c17b

2021-08-16 18:54:29

4.1 MB

d3a486d3b032834b1203adefd25d0bf0b36fae7f9e72071c21ccc266e1e1f893

2021-07-19 14:14:54

4.3 MB

Les dates de compilation semblent cohérentes entre elles. En effet, elles correspondent approximativement aux dates de soumission sur Virustotal [15] (avec un petit délai, naturellement). Les binaires sont strippés (c’est-à-dire que les symboles non nécessaires au fonctionnement du binaire ont été supprimés, et plus particulièrement les noms de fonctions et symboles de débogage) mais contiennent tout de même des informations sur le code source utilisé :

Malheureusement, BinDiff [7] n’est pas en mesure de comparer les binaires. Le flux de contrôle est trop complexe pour lui. Les comparaisons de code ont donc été effectuées manuellement par nos experts.

Figure 1: Erreur BinDiff

Notre analyse a permis de déterminer qu’il ne semble n’y avoir que 2 fichiers sources en dehors des librairies open source (principalement cryptographiques) utilisées. Une première technique anti-sandbox consiste à perdre du temps en effectuant une boucle inutile. L’ordre de grandeur du temps nécessaire (et perdu) est de l’ordre d’une minute avec un CPU occupé à 100%.

Figure 2: Anti sandbox par perte de temps

Après un passage par cette boucle, nous arrivons à la fonction de déchiffrement du stage2. Celle-ci comporte un important nombre d’appels successifs à des fonctions inutiles, parmi lesquelles nous retrouvons :

  • GetCommandLineA;
  • GetCurrentProcess;
  • GetEnvironmentStrings;
  • GetLastError;
  • GetProcessHeap;
  • GetTickCount;

Il s’agit probablement d’un mécanisme de saturation utilisant des calls (appel à une bibliothèque Windows) et ne prenant pas d’argument pour surcharger d’éventuelles sandboxes, ce qui complique l’émulation de code. Il semblerait qu’un script génère tous les appels. Cette technique a également l’avantage d’ajouter un ratio call/instruction crédible par rapport à un programme légitime. Compter les calls permet parfois de retrouver des routines de déchiffrement ou d’unpacking :

Figure 3: Decoys variant 1
Figure 4: Decoys variant 2

Ces appels sont répétés tout au long de la routine de désoffuscation. Les données sont empilées successivement sur la pile, puis dans le tas à l’aide avec des kernel32 !HeapAlloc, sous la forme de DWORD :

Figure 5: Stage2 buffer reconstruction

Le graphique d’appel est très différent suivant les échantillons, ce qui renforce notre hypothèse selon laquelle le code est généré à partir d’un script. Le mix des appels et de la désoffuscation implique un effort particulier pour se fondre dans un comportement semblant être légitime.

Figure 6: Control flow Variant1
Figure 7: Control flow Variant2
Figure 8: Control flow Variant3

Cette technique permet d’inclure des données binaires en limitant l’entropie entre 0.5 et 0.8, très proches de ce que l’on aurait pu attendre d’instructions légitimes.

Figure 9: Entropie du malware

Ces données, une fois reconstituées sont placées dans un buffer qui est déchiffré. On reconnait alors un mécanisme de type Key Schedule, suivi d’un générateur faisant penser à du RC4 :

Figure 10: KSA block
Figure 11: RC4 Random generator

Le paradigme Rust rend ces routines de déchiffrement difficiles à identifier. Les outils tels que findcrypt [8] ne les détectent pas. La vérification suivante permet d’identifier toute la chaîne de désoffuscation :

Figure 12: Decryption in python

La décompression est effectuée par la librairie lzma-rs [9] version 0.1.3, et les clés de déchiffrement sont facilement visibles dans le code. Le script suivant permet son extraction :

Figure 13: Key extraction script

La liste des clés RC4 des échantillons est la suivante :

  • YDVHHCYTCH ;
  • DQOQHMLGYU ;
  • BWSCVQZXOB ;
  • SIMHIDVSCR ;
  • RGZPMAAQRP ;
  • YVMOOVSIOF ;
  • KSQKGUUTXZ ;
  • EUWPUQYDTT ;
  • KHBXNNHKNN ;

Malheureusement, le nombre de clés est insuffisant pour pouvoir évaluer la qualité du PRNG [10]. A noter que le charset est très réduit, ce qui n’a pas d’importance vu que la clé est en clair dans la section « .rdata ». Au vu du graphe d’appel extrêmement variable et très complexe, l’écriture d’un script d’extraction de données générique fonctionnant pour tous les échantillons est fastidieux. Maintenant que l’on connait la clé (cf. script d’extraction précédent), il convient de trouver les données chiffrées. Celles-ci, une fois désoffusquées, comportent un entête avec des données invariantes :

Figure 14: 3 blocks of encrypted buffer header

Le script suivant permet ensuite de trouver les données extraites en mémoire, à condition que le programme ait bien réalisé la phase de déchiffrement en effectuant une recherche de type Egg-hunting :

Figure 15: Script de récupération des données chiffrées

Il ne manque plus, pour automatiser l’extraction, que de retrouver une adresse ou poser un breakpoint, ce qui est fait de manière assez logique en retrouvant les références vers la clé RC4 :

Figure 16: Script de lancement du malware

Et le script fonctionne… Du premier coup pour tous les échantillons ! Le code source de l’outil est accessible sur le github TEHTRIS [19].

Une méthode alternative a été présentée à la conférence Hack it N [16]. Elle consiste à extraire le stage2 par émulation en utilisant la lib unicorn [17]. Le code source est disponible sur le github TEHTRIS [18].

L’extraction des stage2 des échantillons étudiés donne la liste suivante :

Sha256

Stage1 compile time

Stage 2

compile time

edc3b5f8d45d7a1cceee144e57fc5ddfaf8c0c7407a1514d2f3bab4f3c9f18b8

2021-07-28 17:59:19

2021-07-28 17:39:31

d7ec38c0e89a749a7727e5644328835b50e19302e9f3a4688809403ebcbd03d2

2021-08-11 15:35:55

2021-08-11 15:30:38

6578db32dc78ef7f41213557cf894d03b97ed6974ae7a72bec9b7c7ac08c4ba9

2021-07-19 13:57:22

2021-07-19 13:44:11

d48d91451b9594eadc0d1ef6e379bbce9a6033bd337e06d46613a70187c9c5ef

2021-07-15 16:28:54

2021-07-15 16:20:18

54109b12cbbd223f5ad79a9f87bfe50ef05a80e5551a3c1931748c3698900496

2021-08-05 14:47:25

2021-08-05 14:38:43

2d8a2bcc45daedd343eadb4222885d12a221bebbf7f1d98f92cb233df0a4c1d4

2021-08-16 18:54:29

2021-08-16 18:45:22

16feaed6222ce4a1941ae0c32eabaf0ecf68c33c49544f71d431d1b70c4247fd

2021-08-03 15:46:42

2021-08-03 15:38:18

7af554fb260817350d33b801d9f0b8a638b831992f4b1b31c2bbdab875b211df

2021-06-25 17:49:48

2021-06-25 17:43:23

2d8a2bcc45daedd343eadb4222885d12a221bebbf7f1d98f92cb233df0a4c1d4

2021-08-16 18:54:29

2021-08-16 18:45:22

039d63a07372e6e17f9779ccffbafbf9a06a9402ade58fbec3b0b2f8d2038175

2021-07-19 13:57:22

2021-07-19 13:46:46

Les horodatages semblent cohérents entre eux et l’on note que les dates de compilation correspondent avec un décalage de 5 à 10 min laissant à penser à une étape de packing manuelle.

On notera que le stage2 vérifie la présence d’une machine virtuelle en testant la présence des exécutables suivants :

> vboxservice.exe ;
> vboxtray.exe ;
> vmtoolsd.exe ;
> vmwaretray.exe ;
> vmwareuser.exe ;
> vmacthlp.exe ;
> vmsrvc.exe ;
> vmusrvc.exe ;
> prl_cc.exe ;
> prl_tools.exe ;
> xenservice.exe ;
> qemu-ga.exe ;
> windanr.exe.

L’étape de désoffuscation faite, le binaire n’est plus offusqué et livre facilement ses secrets. Par exemple les C2 [11] :

Figure 17: List of C2 URLs

Ou encode la liste des fichiers constituant le code source :

Figure 18: Source code metadata

L’analyse du stage2 a cependant déjà été faite, et nous ne la décrirons pas dans le présent article.

Conclusion

L’évasion de système de détection automatisée de malware est un jeu d’équilibriste entre l’entropie du binaire, le camouflage des fonctions de chiffrement, le ralentissement du délai d’analyse, l’obtention d’un ratio calls/instructions cohérent…

L’utilisation du Rust ajoute une surcouche de code machine qui est cependant bien inférieure à ce que ferait un compilateur Go [12], delphi [13], cython [14], etc. rendant une analyse manuelle tout à fait raisonnable.

 

Cependant, ces techniques ne tiennent pas face à un reverser, ou à une sandbox bien configurée. L’analyse manuelle des menaces les plus connues et laissant le moins de traces constituent un plus dans l’amélioration continue de nos produits sandbox et EDR. Il est très probable qu’à l’avenir, de plus en plus de malwares soient développés en Rust. L’avenir nous le dira.

 

* L’utilisation de stageN consiste à séparer le malware en plusieurs sous parties spécialisées afin d’échapper à une détection antivirale. Dans ce cas, le stage1 est le malware tel qu’envoyé aux victimes et le stage2 est la charge utile encapsulée dans le stage1.

BIBLIOGRAPHIE

Cyber or not Cyber ?

Abonnez-vous à la newsletter TEHTRIS.

Une fois par mois, soyez au courant de l’actualité cyber en vous abonnant à la newsletter TEHTRIS.

Pour pousser le sujet

Publications similaires

Cyber or not cyber ?

Une fois par mois, soyez au courant de l’actualité cyber en vous abonnant à la newsletter TEHTRIS.