Stateo est un ensemble d’outils développés à l’origine pour faciliter l’analyse de Stantinko, un botnet performant des fraudes aux clics, de l’injection publicitaire, des fraudes sur les médias sociaux, des attaques de vols de mots de passe et du cryptominage.
Stadeo a été révélé pour la première fois au Black Hat USA 2020, afin d’être publié pour usage libre.
Les scripts, entièrement écrits en Python, traitent des techniques uniques de Stantinko en matière d'aplatissement des flux de contrôle (CFF) et d'obfuscation des chaînes de caractères décrites dans notre article de mars 2020. En outre, ils peuvent être utilisés à d'autres fins. Par exemple, nous avons déjà étendu notre approche à la désobfuscation du CFF présenté dans Emotet - un cheval de Troie qui vole les références bancaires et qui télécharge des charges utiles supplémentaires telles que des logiciels de rançon.
Notre méthode de désobfuscation utilise IDA, qui est un outil standard dans l’industrie, et Miasm , un cadre open-source nous offrant de multiples analyses de flux de données, un engin d’exécution symbolique, un engin d’exécution de symboles dynamique et les moyens de réassembler des fonctions modifiées.
Vous pouvez obtenir Stadeo au : https://github.com/eset/stadeo.
Exemples d’utilisation
Dans tous les exemples ci-dessous, nous avons établi un serveur RPyC sous 10.1.40.164:4455 (4455 étant le port par défaut), puis avons communiqué avec le serveur provenant d’une console Python.
Nous utiliserons deux classes de Stadeo :
- CFFStrategies pour désobfuscation CFF;
- StringRevealer pour désobfuscation d’une chaîne.
Ces deux classes peuvent être initialisées tant pour des architectures 32 et 64 bits.
Désobfuscation d’une fonction unique
SHA-1 d’un échantillon : 791ad58d9bb66ea08465aad4ea968656c81d0b8e
Le code ci-dessous désobfusque la fonction à 0x1800158B0 avec le paramètre dans le registre R9 fixé à 0x567C et rédige la version désobfusquée à l’adresse 0x18008D000.
Le paramètre R9 est une variable de contrôle pour la fusion des fonctions de la boucle CFF (variable de fusion) et il doit être spécifié en utilisant les expressions Miasm, qui sont documentées ici; notez qu'il faut utiliser RSP_init/ESP_init au lieu de RSP/ESP pour se référer aux paramètres du paquet. Par exemple, nous pourrions utiliser ExprMem(ExprId(“ESP_init”, 32) + ExprInt(4, 32), 32) pour cibler ce paquet de paramètres sur une architecture à 32 bits.
from stadeo.cff.cff_strategies import CFFStrategies
from miasm.expression.expression import ExprId, ExprInt
cs = CFFStrategies(64)
cs.solve_loop(0x1800158B0,
0x18008D000,
context={ExprId("R9", 64): ExprInt(0x567c, 64)},
ip='10.1.40.164')
Traitement des fonctions accessibles
SHA-1 de l'échantillon : e0087a763929dee998deebbcfa707273380f05ca
Le code suivant reconnait uniquement les fonctions obfusquées accessibles par 0x1002DC50 et cherche des candidats pour fusionner des variables. Les fonctionnées reconnues et désobfusquées avec succès débutent à 0x10098000.
from stadeo.cff.cff_strategies import CFFStrategies
strat = CFFStrategies(32)
res = strat.process_merging(0x1002DC50,
0x10098000,
ip="10.1.40.164")
Résultat partiel :
skipping 0x1002dc50 with val None: 0xbadf00d
mapping: 0x1001ffc0 -> 0x10098000 with val @32[ESP_init + 0x8]: 0x6cef
skipping 0x100010f0 with val None: 0xbadf00d
mapping: 0x1000f0c0 -> 0x100982b7 with val @32[ESP_init + 0x8]: 0x2012
mapping: 0x1003f410 -> 0x10098c8a with val @32[ESP_init + 0x4]: 0x21a4
mapping: 0x1003f410 -> 0x10098f3d with val @32[ESP_init + 0x4]: 0x772a
skipping 0x1004ee79 (library func)
...
Les fonctions cartographiées ont été correctement désobstruées et les fonctions sautées n'ont pas été considérées comme obscurcies. Le format des lignes cartographiques dans la sortie est le suivant :
mapping: %obfuscated_function_address% -> %deobfuscated_function_address% with val %merging_variable%: % merging_variable_value%
The default value for merging_variable_value is 0x0BADF00D. The skipping lines follow the same pattern, but there’s no deobfuscated_function_address. Library functions recognized by IDA are just skipped without further processing.
La valeur par défaut pour merging_variable_value est 0x0BADF00D. Le saut de ligne suit le même modèle, mais sans deobfuscated_function_address. Les fonctions du répertoire reconnues par IDA sont simplement ignorées, sans autre traitement.
Traitement de toutes les fonctions
SHA-1 de l’échantillon : e575f01d3df0b38fc9dc7549e6e762936b9cc3c3
We use the following code only to deal with CFF featured in Emotet, whose CFF implementation fits into the description of common control-flow flattening here.
We prefer this approach because the method CFFStrategies.process_all() does not attempt to recognize merging variables that are not present in Emotet and searches only for one CFF loop per function; hence it is more efficient.
Successfully recognized and deobfuscated functions are sequentially written from 0x0040B000. Format of the output is the same as in the process_merging method used in the Processing reachable functions example, but naturally there won’t be any merging variables.
Nous utilisons le code suivant uniquement pour traiter le CFF présenté dans Emotet, dont la mise en œuvre du CFF s'inscrit dans la description de l'aplatissement du flux de contrôle commun ici.
Nous préférons cette approche, puisque la méthode CFFStrategies.process_all()ne tente pas de reconnaitre les données fusionnées qui ne sont pas présentes dans Emotet et ne recherche qu’une boucle CFF par fonction; elle est donc plus efficace.
Les fonctions reconnues et obfusquées sont écrites de manière séquentielle en 0x0040B000. Le format du résultat est le même que dans le process_merging, c’est-à-dire la méthode utilisée dans l’exemple Traitement des fonctions accessibles, mais il n’y aura naturellement pas de fusions de variables.
from stadeo.cff.cff_strategies import CFFStrategies
strat = CFFStrategies(32)
res = strat.process_all(0x0040b000,
ip="10.1.40.164")
Résultat partiel :
mapping: 0x00401020 -> 0x0040b000 with val None: 0xbadf00d
skipping 0x00401670 with val None: 0xbadf00d
mapping: 0x00401730 -> 0x0040b656 with val None: 0xbadf00d
...
Réorienter les références vers des fonctions désobfusquées
Stadeo ne met pas automatiquement à jour les références aux fonctions après leur désobfuscation. Dans l'exemple ci-dessous, nous montrons comment corriger un appel de fonction dans la figure 1 à partir de l'exemple Désobfuscation d’une fonction unique. La référence corrigée est illustrée dans la figure 4.
Nous avons utilisé le code suivant pour corriger les appels, dont le quatrième paramètre est 0x567C, de la fonction obfusquée en 0x1800158B0 à la fonction désobfusquée en 0x18008D000. Prenez note qu’afin de s’assurer qu’IDA ait reconnu les paramètres de la fonction correctement et possiblement, corrigé.
from stadeo.utils.xref_patcher import patch_xrefs
patch_xrefs(0x1800158B0,
0x18008D000,
{3: 0x567c},
ip='10.1.40.164')
Révéler les chaînes de caractères obscurcies dans une fonction
La fonction StringRevealer.process_funcs() révèle des chaînes obfusquées dans la fonction spécifiée et retourne une cartographie des chaînes désobfusquées et de leurs adresses.
Notez que le flux de contrôle de la fonction cible doit déjà avoir été désobfusqué.
Dans l’exemple ci-dessous, nous avons désobfusqué la chaîne de la fonction à 0x100982B7, montré dans la Figure 5. Cette fonction elle-même a été désobfusquée dans l’exemple Traitement des fonctions accessibles ci-dessus.
from stadeo.string.string_revealer import StringRevealer
sr = StringRevealer(32)
strings = sr.process_funcs([0x100982B7],
ip="10.1.40.164")
Le contenu des chaînes de caractères variables après l'exécution est :
{0x100982B7: {'SeLockMemoryPrivilege'}}
Nous espérons que l’outil Stadeo et ces explications sur leur utilisation vous servira. Si vous avez des questions, n'hésitez pas à nous contacter à l’adresse threatintel[at]eset.com ou à ouvrir une demande au https://github.com/eset/stadeo.