Résumé
Le ransomware Play, également connu sous le nom de PlayCrypt, est un ransomware apparu pour la première fois en juin 2022. Le ransomware a ciblé des secteurs tels que les soins de santé et les télécommunications, ainsi qu'un large éventail de régions telles que l'Amérique latine, l'Europe et l'Amérique du Nord. Le ransomware Play est connu pour accéder aux réseaux par le biais de comptes valides compromis ou en exploitant des vulnérabilités spécifiques. Une fois à l'intérieur du réseau, il utilise un grand nombre d'outils de post-exploitation connus pour poursuivre son attaque. Des outils tels que Bloodhound, PsExec, Mimikatz et AdFind sont des exemples d'outils précédemment utilisés dans des attaques impliquant ce ransomware.
Un autre aspect du logiciel malveillant qui le rend célèbre est la quantité de techniques anti-analyse qu'il utilise dans ses charges utiles, telles que l'utilisation abusive de SEH et l'utilisation de ROP pour rediriger le flux d'exécution. En employant des techniques pour ralentir le processus de rétro-ingénierie, les acteurs de la menace rendent la détection et la prévention des logiciels malveillants plus difficiles.
En 2022, d'autres chercheurs ont publié un excellent article de blog analysant le logiciel malveillant lui-même et certaines des techniques anti-analyse qu'il utilise. Dans cet article de blog, nous reviendrons sur les techniques anti-analyse employées par les récentes variantes du ransomware Play, en expliquant comment elles fonctionnent et comment nous pouvons vaincre certaines d'entre elles à l'aide de scripts d'automatisation.
Programmation orientée retour (ROP)
Lors de la rétro-ingénierie d'un logiciel malveillant, s'assurer que le flux de contrôle n'est pas obscurci est l'une des premières choses à faire pour comprendre correctement le logiciel malveillant.
Pour tenter d'obscurcir son flux de contrôle, le ransomware Play utilise souvent une technique ROP dans sa charge utile. Pour ce faire, il appelle plus d'une centaine de fonctions qui corrigent la valeur située au sommet de la pile et redirigent ensuite le flux d'exécution vers cette valeur. Avant d'expliquer comment le logiciel malveillant procède exactement, examinons le fonctionnement général des instructions d'assemblage CALL et RET.
Lorsqu'un CALL se produit (un near call dans ce cas pour être plus précis), le processeur pousse la valeur du registre du pointeur d'instruction (EIP dans ce cas) sur la pile et passe ensuite à l'adresse spécifiée par l'opérande de la cible de l'appel, qui dans ce cas est un décalage par rapport au pointeur d'instruction. L'adresse du pointeur d'instruction, plus ce décalage, donne l'adresse de la fonction à appeler.
L'instruction RET, quant à elle, indique la fin d'un appel de fonction. Cette instruction est chargée de transférer le flux de contrôle du programme à l'adresse située au sommet de la pile. Et oui, c'est exactement l'adresse initialement poussée par l'instruction call !
Compte tenu de ce qui a été mentionné, dans un scénario idéal, l'adresse mise en évidence dans l'image ci-dessous serait la prochaine instruction à exécuter après un appel à la fonction cible (sub_42a4b9) :
L'image suivante montre comment le logiciel malveillant abuse du fonctionnement des instructions CALL et RET :
Une fois la fonction appelée, l'adresse 0x42a4b4 est poussée sur la pile, et c'est donc elle que pointera ESP. La fonction appelée ajoute alors la valeur 0xA à l'adresse pointée par ESP, puis retourne à l'aide de l'instruction RET. Ces opérations ont pour effet de rediriger le flux de contrôle vers 0x42a4be (0x42a4b4 + 0xa) au lieu de 0x42a4b4.
En appliquant cette technique, le logiciel malveillant rend non seulement l'analyse statique plus complexe, car le flux du programme n'est pas trivial, mais il peut également rendre le débogage plus difficile, car si vous passez sur ce type de fonction, beaucoup de choses peuvent se produire avant que l'instruction suivante ne soit exécutée.
Une autre façon pour le logiciel malveillant de mettre en œuvre cette technique ROP est d'utiliser l'approche illustrée dans le code ci-dessous, qui est très courante dans les shellcodes. Le décalage spécifié par l'opérande cible de l'instruction d'appel est nul, ce qui fait que l'adresse de la fonction à appeler correspond exactement à l'adresse de l'instruction suivante. Cette adresse est ensuite poussée au sommet de la pile et le reste des opérations est exactement le même que dans l'exemple précédent :
Afin de faciliter l'analyse du ransomware Play, Netskope Threat Labs a développé un script, basé sur les travaux antérieurs d'autres chercheurs, pour corriger l'obscurcissement ROP utilisé.
Le script recherche les candidats ROP possibles, collecte le décalage à ajouter au sommet de la pile et corrige les adresses effectuant les appels ROP avec un saut absolu, où la cible est l'adresse de transfert modifiée calculée au moment de l'exécution.
Voici un exemple de l'aspect de la charge utile du logiciel malveillant avant et après l'exécution du script :
Avant :
Après :
Anti-démontage
Une technique d'anti-désassemblage utilisée pour tromper les analystes et les désassembleurs consiste à transférer le flux d'exécution vers des cibles situées au milieu d'autres instructions valides.
Prenons l'exemple de l'appel de fonction à 0x42a4af utilisé dans la section ROP ci-dessus. Les opcodes pour cette instruction CALL sont "E8 05 00 00 00". L'octet 0xE8 est l'opcode de l'instruction CALL elle-même et les 4 autres octets représentent l'opérande cible (le décalage par rapport à EIP).
Comme nous l'avons vu précédemment, l'adresse de la fonction à appeler est la valeur de EIP (0x42a4b4) + le décalage (0x5), ce qui donne l'adresse 0x42a4b9. Cependant, cette valeur tombe dans le dernier octet d'une autre instruction valide à 0x42a4b5 :
En termes d'exécution, ce type de comportement ne change rien car le processeur comprendra les instructions correctement. Cependant, un désassembleur peut ne pas présenter les instructions correctement en fonction de l'approche qu'il utilise (par ex. ), ce qui rend l'analyse statique quelque peu délicate.
Le script que nous avons fourni pour corriger les appels ROP gère également ce scénario pour les cibles ROP. Étant donné que nous utilisons une instruction JMP pour patcher les appels, nous finissons par forcer le désassembleur à comprendre le flux correct à suivre.
Code de la poubelle
Bien qu'il s'agisse d'une technique très simple, elle mérite d'être mentionnée car elle peut définitivement ralentir l'analyse du logiciel malveillant. La technique d'insertion de code junk/garbage est exactement ce que son nom suggère : l'insertion d'instructions inutiles dans le code binaire.
Contrairement aux techniques présentées jusqu'à présent, l'insertion de code indésirable ne tromperait pas le désassembleur, mais elle pourrait faire perdre du temps à l'analyse de code inutile et le ransomware Play l'utilise assez souvent, en particulier dans les cibles des appels ROP.
Traitement structuré des exceptions (SEH)
Une autre technique anti-analyse utilisée par le logiciel malveillant consiste à abuser d'un mécanisme Windows appelé Structured Exception Handling (gestion structurée des exceptions ), utilisé pour gérer les exceptions.
Dans le système d'exploitation Windows, chaque thread possède une structure appelée Thread Environment Block (TEB). Dans un environnement x86, l'adresse du TEB est située dans le registre FS et contient des informations utiles pour le thread lui-même et réside dans l'espace d'adressage du processus. Le premier champ de cette structure est une autre structure appelée Thread Information Block (TIB) et le premier élément de cette structure contient une liste de ce que l'on appelle des "enregistrements d'exception".
Chacun de ces enregistrements est composé d'une structure qui contient deux valeurs. Le premier est un pointeur sur l'enregistrement "suivant" de la liste et le second est un pointeur sur le gestionnaire chargé de traiter l'exception.
En termes simples, lorsqu'une exception se produit, le dernier enregistrement ajouté à la liste est appelé et décide de traiter ou non l'exception déclenchée. Si ce n'est pas le cas, le gestionnaire suivant de la liste est appelé et ainsi de suite jusqu'à la fin de la liste. Dans ce cas, le gestionnaire Windows par défaut est appelé.
Play abuse de cette fonctionnalité en insérant son propre gestionnaire d'exception dans la liste de gestion des exceptions, puis en forçant une exception. Dans l'exemple ci-dessous, le registre EBX contient l'adresse du gestionnaire d'exception du logiciel malveillant et, une fois qu'il est inséré comme premier élément de la liste (les quatre instructions surlignées en haut), le logiciel malveillant met EAX à zéro et le divise par zéro, ce qui provoque une exception (les deux instructions surlignées en bas) :
Une fois l'exception déclenchée, le dernier gestionnaire enregistré est appelé. Le logiciel malveillant utilise ce premier gestionnaire pour s'enregistrer et en appeler un second en forçant une deuxième exception, mais maintenant via l'instruction INT1 pour générer une exception de débogage. Le deuxième gestionnaire enregistré est celui qui est responsable de la poursuite de l'exécution "normale" du logiciel malveillant.
Cette technique peut être très gênante lors du débogage du logiciel malveillant, car le débogueur arrête l'exécution lorsque l'exception se produit, ce qui nous oblige à trouver le gestionnaire d'exception et à nous assurer que nous en avons le contrôle au préalable.
Obfuscation des chaînes de caractères
Toutes les chaînes pertinentes utilisées par le logiciel malveillant sont obscurcies, ce qui rend difficile l'identification statique de toute chaîne pertinente. Afin de désobfusquer ses chaînes au moment de l'exécution, le logiciel malveillant génère une clé de 8 octets qu'il utilise comme entrée pour l'algorithme de désobfuscation.
L'algorithme de génération de la clé est assez simple. Il reçoit une valeur de départ codée en dur et effectue quelques opérations arithmétiques de base à l'intérieur d'une boucle. Le compteur de boucles est également une valeur codée en dur qui était très élevée dans les échantillons analysés (par ex. 0x20c87548).
Nous pouvons simplifier les opérations utilisées pour générer la clé dans l'extrait python suivant :
x = graine
y = 0
i = 0
while i < compteur :
a = (x * x) >> 32
b = (x * y) + (x * y)
si y :
y = (a + b) & 0xffffffff
else :
y = a
x = ((x * x) & 0xffffffff) + i
i += 1
key = struct.pack("< 2I", *[x, y])
L'algorithme utilisé pour désobfusquer les chaînes de caractères comporte quelques étapes supplémentaires et d'autres opérations telles que AND, OR et NOT. Ces opérations sont appliquées à la chaîne obscurcie elle-même et la dernière étape de la boucle de désobfuscation consiste à appliquer une opération XOR sur plusieurs octets à l'aide de la clé de 8 octets générée précédemment. Les opérations peuvent être simplifiées dans l'extrait python suivant :
i = 0
while i < len(enc_str) :
dec_str[i] = enc_str[i]
j = 0
while j < 8 :
v1 = (dec_str[i] >> j) & 1
v2 = (dec_str[i] >> (j + 1)) & 1
si v1 != v2 :
si v1 == 0 :
dec_str[i] = (dec_str[i] & ~(1 << (j + 1))) & 0xFF
else :
dec_str[i] = (dec_str[i] | (1 << (j + 1))) & 0xFF
si v2 == 0 :
dec_str[i] = (dec_str[i] & ~(1 << j)) & 0xFF
else :
dec_str[i] = (dec_str[i] | (1 << j)) & 0xFF
j += 2
dec_str[i] = ~dec_str[i] & 0xFF
dec_str[i] = (dec_str[i] ^ clé [i % len(clé)]) & 0xFF
i += 1
Il convient de mentionner que les algorithmes contiennent un grand nombre d'opérations inutiles pour la désobfuscation elle-même et qu'elles ont été supprimées dans les extraits présentés.
L'équipe de Netskope Threat Labs a créé deux scripts pour faciliter le processus de désobfuscation : l'un pour générer automatiquement la clé de 8 octets et l'autre pour effectuer le décryptage de la chaîne.
Hachage de l'API
Le logiciel malveillant utilise la technique bien connue du hachage de l'API pour résoudre les fonctions de l'API Windows qu'il utilise au moment de l'exécution. L'algorithme utilisé est le même que celui signalé en 2022, à savoir xxHash32, avec la même graine de 1 fournie à la fonction de hachage.
L'algorithme xxHash32 est facilement reconnaissable en raison du nombre de constantes qu'il utilise dans ses opérations :
Pour tenter d'obscurcir encore davantage les fonctions hachées, le logiciel malveillant ajoute ou soustrait des constantes spécifiques aux résultats du hachage. La valeur constante est différente pour chaque échantillon. Voici un exemple dans lequel le logiciel malveillant a ajouté la valeur 0x4f5dcad4 au résultat du hachage :
Détection de Netskope
- Netskope Threat Protection
- Win32.Ransomware.Playde
- Netskope Advanced Threat Protection offre une protection proactive contre cette menace.
- Gen.Malware.Detect.By.StHeur et Gen:Heur.Mint.Zard.55 indique un échantillon qui a été détecté à l'aide d'une analyse statique.
- Gen.Detect.By.NSCloudSandbox.tr indique qu'un échantillon a été détecté par notre sandbox.
Conclusions
Comme nous pouvons l'observer dans cet article de blog, le ransomware Play utilise plusieurs techniques anti-analyse pour tenter de ralentir son analyse. De l'obscurcissement des chaînes de caractères au ROP et à l'utilisation du détournement de SEH, les acteurs de la menace mettent souvent à jour leur arsenal et étendent leurs techniques pour rendre l'impact de leurs attaques encore plus destructeur. Netskope Threat Labs continuera à suivre l'évolution du ransomware Play et de son TTP.
CIO
Tous les CIO liés à cette campagne, les scripts et les règles de Yara sont disponibles dans notre dépôt GitHub.