Cet article, qui se veut didactique, vise à illustrer le processus de désobfuscation de ce script malveillant en utilisant les fonctionnalités de recherche et de remplacement d’expressions régulières fournies par l’éditeur de texte Vim, et à présenter les principales fonctionnalités du code désobfusqué. Le script est disponible sur VirusTotal (sha256 : eb60b608bed639d5111adee68addfd5815f1e5c109a0e03a109f808eb77875d0).
Cheat sheets : désobfuscation avec Vim
La complexité de l’obfuscation d’un script malveillant varie selon les cas : dans certains le processus de désobfuscation nécessite la mise au point d’outils spécifiques (un script Python par exemple), dans d’autres un (bon) éditeur de texte et quelques expressions régulières suffisent à retrouver le code original. Pour le script qui nous intéresse ici, la désobfuscation a été réalisée en combinant les expressions, commandes et fonctions Vim présentées ci-dessous.
Expressions utilisées
Expression |
Catégorie |
Cible |
Exemple d’expression |
Exemple de cible |
|
[a-z] |
Caractères |
Caractère minuscule compris dans une gamme donnée |
[a-e] |
a1fe |
|
[a-zA-Z0-9] |
Caractères |
Caractère minuscule, majuscule ou chiffre compris dans une gamme donnée |
[a-e0-3] |
a1f4 |
|
[^a-z] |
Caractères |
Caractère non compris dans une gamme donnée |
|
ab1fe |
|
\d |
Caractères |
Caractère correspondant à un chiffre en base décimale |
\d |
ab1c9 |
|
\x |
Caractères |
Caractère correspondant à un chiffre en base hexadécimale |
\x |
__bter5 |
|
\+ |
Quantificateurs |
Au moins un des élément précédant le quantificateur |
[a-z]\+ |
ab1z |
|
\{3} |
Quantificateurs |
Exactement 3 des éléments précédant le quantificateur |
[a-z]\{3} |
ab12cde345def |
|
\(\) |
Logique |
Sous-groupe au sein d’un pattern |
[a-z]_\([0-9]\) |
utilisée avec submatch() ou :%s (voir ci-dessous) |
Commandes Vim utilisées
Commande |
Fonction |
Exemple |
Effet |
/ |
Recherche un pattern |
/[a-z] |
Recherche toutes les lettres en minuscule |
:%s/a/b/c |
Remplace l’élément a par l’élément b (c = option) |
:%s/[a-z]/0/g |
Remplace toutes les lettres en minuscule par 0 (option g = tout le document) |
\= |
Évalue une expression |
:%s/[a-z]/\=nr2char(0x30)/g |
Idem |
Fonctions Vim utilisées
Fonction |
Définition |
str2nr(a, b) |
Convertit la chaine de caractères a en un entier de base b |
nr2char(a) |
Convertit le code a d’un caractère en son caractère correspondant |
submatch(a) |
Fait référence au sous-groupe d’index a au sein d’une expression |
Désobfuscation du script
Du JavaScript encodé au JavaScript obfusqué
Le script malveillant se présente au format .jse (JavaScript encoded). Il s’agit d’un format de fichier développé par Microsoft dans l’objectif de rendre le code original d’un fichier JavaScript illisible. Ce format de fichier, initialement mis au point pour les développeurs qui souhaitaient protéger leur code des regards indiscrets, a rapidement été adopté par les auteurs de malwares. Les fichiers *.jse peuvent être décodé en utilisant l’outil Windows Script Decoder. Nous obtenons en sortie un fichier JavaScript décodé qu’il faut maintenant désobfusquer (figure 1).
Figure 1 : Script encodé (en haut), utilisation du décodeur (milieu) et script décodé (en bas)
1er pattern d’obfuscation : codes Unicode
La première technique d’obfuscation consiste à remplacer certains caractères par la valeur hexadécimale de leur code Unicode (figure 2).
Figure 2 : Premier pattern d’obfuscation
Par exemple, le nom de la variable WshShell peut être obfusqué de la manière suivante :
var \u0057\u0073\u0068\u0053\u0068\u0065\u006c\u006c
L’expression régulière correspondant à ce pattern d’obfuscation est la suivante :
L’antislash est interprété, par défaut, comme le début d’une commande. Si nous voulons chercher ce caractère en tant que tel, il doit être précédé d’un antislash; ainsi, \\u correspond à la chaine de caractères \u. Nous cherchons ensuite quatre hexadécimaux : \x permet de trouver un chiffre exprimé en base hexadécimale (0-f), et le quantificateur \{4} signifie exactement 4. L’accolade ouvrante est précédée d’un antislash pour indiquer le début d’une commande. Les parenthèses (précédées d’un antislash) permettent de délimiter un sous-pattern au sein de notre expression, auquel nous pouvons faire référence par la suite grâce à la fonction submatch().
Désobfuscation du pattern 1
La désobfuscation de ce premier pattern peut être réalisée avec la commande suivante :
:%s/\\u\(\x\{4}\)/\=nr2char(str2nr(submatch(1),16))/g
- Nous retrouvons en premier paramètre le pattern recherché (\\u\(\x\{4}\)).
- Le second paramètre est le résultat de l’évaluation (\=) de l’expression nr2char(str2nr(submatch(1),16)) :
- la fonction submatch() référence la suite de 4 hexadécimaux du premier paramètre (\(\x\{4}\))
- la fonction str2nr() les convertit en un entier de base 16
- la fonction nr2char() convertit cet entier -qui correspond à un code caractère- en son caractère correspondant.
- L’option g (pour global) signifie que la commande est appliquée dans l’ensemble du document.
2ème pattern d’obfuscation : objets anonymes et couples clé / valeur
La deuxième technique d’obfuscation consiste à obfusquer un caractère en l’assignant comme valeur d’une clé (figure 3), et est à la base de la quasi-totalité des patterns qui seront présentés par la suite.
Figure 3 : pattern d’obfuscation basé sur des couples clé / valeur
Par exemple, le caractère t peut être obfusqué de la manière suivante :
{key1:'t',key2:value2}.key1
- Un objet anonyme ({...}) contenant 2 couples clé / valeur (key1:'t' et key2:value2) est créé
- La valeur de la première clé est utilisée ({...}.key1)
Ce pattern peut également servir à obfusquer tout ou partie d’une chaine de caractères en effectuant des concaténations. Par exemple, la chaine foo peut être obfusquée de la manière suivante :
{key1 :'f',key2:value2}.key1+{key3 :'o',key4:value4}.key3+{key5 :'o',key6:value6}.key5
La désobfuscation de ce pattern se fait à l’aide de 3 expressions régulières, très proches, qui permettent de conserver les guillemets de début et de fin de chaine. Toutes seront les variantes d’un même pattern générique auquel nous ajouterons des préfixes et / ou suffixes afin de distinguer début, milieu et fin de chaine. Pour que la désobfuscation fonctionne, il faut suivre l’ordre suivant : début de chaine, fin de chaine et milieu de chaine.
Construction du pattern générique
L’examen du code obfusqué permet d’établir que _[a-zA-Z]\+\d*_\d est une expression possible pour trouver les clés des premiers couples clé / valeur de chaque objet anonyme (cf. figure 3) :
- _ le nom de la clé commence par le caractère underscore,
- [a-zA-Z]\+ suivi d’au moins une lettre de l’alphabet,
- \d* suivie de 0 ou plus décimal,
- _ suivi d’un second caractère underscore,
- \d suivi d’un seul décimal.
L’examen du code révèle également que c’est toujours la première clé de l’objet anonyme qui est utilisée ({…}.key1) ; le caractère obfusqué peut être récupéré avec l’expression \(.\) :
- . signifie n’importe quel caractère
- \(\) permet de référencer un sous-groupe contenant ce caractère et de le récupérer par la suite
L’expression permettant de trouver les clés des seconds couples clé / valeur est très proche de la première : _[a-zA-Z]\+_\d. Les valeurs associées aux secondes clés, enfin, sont toujours composées d’au moins un décimal : \d\+.
En mettant tous ces éléments bout à bout, nous pouvons établir le pattern générique à partir duquel nous construirons nos 3 expressions régulières :
{_[a-zA-Z]\+\d*_\d:'\(.\)',_[a-zA-Z]\+_\d:'\d\+'}\._[a-zA-Z]\+\d*_\d
Note : dans cette expression régulière, le point . signifie n’importe quel caractère ; pour rechercher le caractère . en tant que tel, il faut le faire précéder d’un antislash.
Début de chaine
Globalement, le début d’une chaine de caractères ne peut être précédé du caractère + car ce dernier sert aux concaténations ; en préfixant notre pattern générique avec [^+], nous pouvons trouver tous les débuts de chaine. Lors de la désobfuscation, il faudra néanmoins préserver le caractère ([, =, etc…) précédant l’accolade { ; pour cela, le préfixe [^+] est intégré dans un sous-groupe : \([^+]\).
Pattern de début de chaine :
\([^+]\){_[a-zA-Z]\+\d*_\d:'\(.\)',_[a-zA-Z]\+_\d:'\d\+'}\._[a-zA-Z]\+\d*_\d
Fin de chaine
Le pattern générique, s’il se trouve en fin de chaine, sera toujours précédé du caractère + et suivi de l’un de ces 4 caractères : , ou ; ou ) ou ]. Ces 4 derniers caractères doivent être mis entre crochet [] pour indiquer que nous cherchons parmi une gamme. Là encore, un sous-groupe est nécessaire afin de les préserver lors de la désobfuscation, ce qui nous donne le suffixe \([,;)\]]\). Noter que le premier caractère ] est précédé d’un antislash pour indiquer qu’il s’agit du caractère en tant que tel et non d’un élément d’une commande.
Pattern de fin de chaine :
+{_[a-zA-Z]\+\d*_\d:'\(.\)',_[a-zA-Z]\+_\d:'\d\+'}\._[a-zA-Z]\+\d*_\d\([,;)\]]\)
Milieu de chaine
Le pattern générique, s’il obfusque des caractères situés en milieu de chaine, sera précédé du caractère +.
Pattern de milieu de chaine :
+{_[a-zA-Z]\+\d*_\d:'\(.\)',_[a-zA-Z]\+_\d:'\d\+'}\._[a-zA-Z]\+\d*_\d
Désobfuscation du pattern 2
Position |
Commande |
Début de chaine |
:%s/\([^+]\){_[a-zA-Z]\+\d*_\d:'\(.\)',_[a-zA-Z]\+_\d:'\d\+'}\._[a-zA-Z]\+\d*_\d/\1'\2/g |
Détail de la formule de remplacement \1'\2 :
- \1 correspond au caractère précédant l’accolade et que nous voulons préserver ([, =, etc…)
- ' permet d’insérer un guillemet de début de chaine
- \2 correspond au caractère obfusqué que nous voulons récupérer
Position |
Commande |
Fin de chaine |
:%s/+{_[a-zA-Z]\+\d*_\d:'\(.\)',_[a-zA-Z]\+_\d:'\d\+'}\._[a-zA-Z]\+\d*_\d\([,;)\]]\)/\1'\2/g |
Détail de la formule de remplacement \1'\2 :
- \1 correspond au caractère obfusqué que nous voulons récupérer
- ' permet d’insérer un guillemet de fin de chaine
- \2 correspond au caractère que nous voulons préserver (, ou ; ou ) ou ])
Position |
Commande |
Milieu de chaine |
:%s/+{_[a-zA-Z]\+\d*_\d:'\(.\)',_[a-zA-Z]\+_\d:'\d\+'}\._[a-zA-Z]\+\d*_\d/\1/g |
3ème pattern : test ternaire et méthode substr()
La troisième technique d’obfuscation consiste à obfusquer les caractères a e et r au sein de tests ternaires suivis d’un appel à la méthode substr() (figure 4).
Figure 4 : pattern d’obfuscation basé sur un test ternaire et la méthode substr().
Par exemple, le caractère e est obfusqué de la manière suivante :
{key:(((67 > 5) ? true : false) +'').substr(3, 1)}.key
Nous retrouvons l’utilisation d’un objet anonyme ({…}) auquel n’est associé, cette fois ci, qu’un seul couple clé / valeur. La valeur associée à la clé se compose d’un test ternaire ((67 > 5) ? true : false) dont le résultat est converti en chaine de caractères (+'') et à laquelle est appliquée la méthode substr().
Un test ternaire se compose de 3 éléments :
Elément |
Signification |
A gauche de l’opérateur ? |
Test à réaliser |
A gauche du caractère : |
Valeur à assigner si le test est vrai |
A droite du caractère : |
Valeur à assigner si le test est faux |
La méthode subsr(start, length) permet d’extraire une partie d’une chaine de caractère. Le paramètre start correspond à l’indice à partir duquel l’extraction commence, et le paramètre length indique combien de caractères extraire.
En résumé, pour le caractère e :
- Le test 67 > 5 est réalisé
- Le test est vrai, la clé key va donc recevoir la valeur true (en tant que chaine de caractère et non en tant que booléen, grâce à l’opération +'')
- La méthode substr(3, 1) extrait 1 caractère de la chaine true à partir du caractère ayant l’indice 3 (le compte commence à 0) : e
- {...}.key permet de récupérer le caractère e
L’obfuscation des caractères a et r suit la même logique :
- Caractère a :
- Le pattern d’obfuscation du caractère a remplace simplement le booléen true par un test qui est toujours faux (7 > 90)
- Le test 7899 > 1 est vrai, il renvoie donc le résultat du test 7 > 90, qui renvoie le booléen false, qui est converti en chaine de caractère (+'')
- Le caractère a est extrait via l’appelle à la méthode substr(1, 1)
- Caractère r :
- Le test 78 > 6 renvoie le booléen true, qui est converti en chaine de caractère (+'')
- Le caractère r est extrait via l’appelle à la méthode substr(1, 1)
Patterns génériques
Caractère |
Pattern générique |
e |
{_[a-zA-Z]\+\d*_\d:(((67 > 5) ? true : false)+'').substr(3,1)}\._[a-zA-Z]\+\d*_\d |
a |
{_[a-z]\+_\d:(((7899 > 1) ? (7>90) : false)+'').substr(1,1)}\._[a-z]\+_\d |
r |
{_[a-z]\+\d*_\d:(((78 > 6) ? true : false)+'').substr(1,1)}\._[a-z]\+\d*_\d |
Désobfuscation du pattern 3
Le processus de désobfuscation de ces 3 caractères est identique à celui du pattern 2 :
- début de chaine : préfixe \([^+]\)
- fin de chaine : préfixe + et suffixe \([,;)\]]\)
- milieu de chaine : préfixe +
Caractère |
Position et commande de désobfuscation |
e |
Fin de chaine :%s/+{_[a-zA-Z]\+\d*_\d:(((67 > 5) ? true : false)+'').substr(3,1)}\._[a-zA-Z]\+\d*_\d\([,;)\]]\)/e'\1/g |
e |
Milieu de chaine :%s/+{_[a-zA-Z]\+\d*_\d:(((67 > 5) ? true : false)+'').substr(3,1)}\._[a-zA-Z]\+\d*_\d/e/g |
a |
Début de chaine :%s/\([^+]\){_[a-z]\+_\d:(((7899 > 1) ? (7>90) : false)+'').substr(1,1)}\._[a-z]\+_\d/\1'a/g |
a |
Milieu de chaine :%s/+{_[a-z]\+_\d:(((7899 > 1) ? (7>90) : false)+'').substr(1,1)}\._[a-z]\+_\d/a/g |
r |
Début de chaine :%s/\([^+]\){_[a-z]\+\d*_\d:(((78 > 6) ? true : false)+'').substr(1,1)}\._[a-z]\+\d*_\d/\1'r/g |
r |
Fin de chaine :%s/+{_[a-z]\+\d*_\d:(((78 > 6) ? true : false)+'').substr(1,1)}\._[a-z]\+\d*_\d\([,;)\]]\)/r'\1/g |
r |
Milieu de chaine :%s/+{_[a-z]\+\d*_\d:(((78 > 6) ? true : false)+'').substr(1,1)}\._[a-z]\+\d*_\d/r/g |
4ème pattern : mot clé ‘function’ et méthode substr()
Cette technique d’obfuscation consiste à convertir une déclaration de fonction (function (a){return a}) en une chaine de caractères, puis à appeler la méthode substr(0, 1) sur cette chaine (figure 5).
Figure 5 : obfuscation du caractère f
Ce pattern permet d’obfusquer le caractère f. Dans certains cas l’appel à la méthode substr(0, 1) est précédé d’un appel à la méthode toUpperChar(), permettant d’obfusquer le caractère F.
Patterns génériques
Nous aurons 2 patterns génériques à rechercher :
Caractère |
Pattern générique |
f |
{_[a-zA-Z]\+_\d:(function (a){return a}+'').substr(0,1)}\._[a-zA-Z]\+_\d |
F |
{_[a-zA-Z]\+_\d:(function (a){return a}+'').toUpperCase().substr(0,1)}\._[a-zA-Z]\+_\d |
Désobfuscation du pattern 4
Là encore, la désobfuscation se fait en tenant compte des guillemets de début et de fin de chaine.
Caractère |
Position et commande de désobfuscation |
f |
Début de chaine :%s/\([^+]\){_[a-zA-Z]\+_\d:(function (a){return a}+'').substr(0,1)}\._[a-zA-Z]\+_\d/\1'f/g |
f |
Fin de chaine :%s/+{_[a-zA-Z]\+_\d:(function (a){return a}+'').substr(0,1)}\._[a-zA-Z]\+_\d\([,;)\]]\)/f'\1/g |
f |
Milieu de chaine :%s/+{_[a-zA-Z]\+_\d:(function (a){return a}+'').substr(0,1)}\._[a-zA-Z]\+_\d/f/g |
F |
Début de chaine :%s/\([^+]\){_[a-zA-Z]\+_\d:(function (a){return a}+'').toUpperCase().substr(0,1)}\._[a-zA-Z]\+_\d/\1'F/g |
F |
Milieu de chaine :%s/+{_[a-zA-Z]\+_\d:(function (a){return a}+'').toUpperCase().substr(0,1)}\._[a-zA-Z]\+_\d/F/g |
5ème pattern : méthode String.fromCharCode() et test ternaire
Cette technique d’obfuscation repose sur un test ternaire toujours vrai qui renvoie l’entier 120. Cet entier, qui représente le code ASCII (en base décimal) du caractère obfusqué, est passé en paramètre à la méthode String.fromCharCode() ; il correspond au caractère x (figure 6).
Figure 6 : obfuscation du caractère x
Le caractère x se trouve toujours en milieu de chaine de caractère, une seule expression sera donc nécessaire :
+{_[a-zA-Z]\+\d*_\d:String.fromCharCode( ((11 > 6) ? 120 : 'h') )}\._[a-zA-Z]\+\d*_\d
Désobfuscation du caractère x :
:%s/+{_[a-zA-Z]\+\d*_\d:String.fromCharCode( ((11 > 6) ? 120 : 'h') )}\._[a-zA-Z]\+\d*_\d/x/g
Fin de la désobfuscation
La majeure partie du script est maintenant désobfusquée. Afin d’en améliorer la lisibilité, quelques ajustements manuels restent à effectuer.
Mise en forme
Le script étant sur une seule ligne, l’ajout de retours à la ligne après les caractères ; et { permet d’en améliorer la lisibilité :
:%s/\([;{]\)/\1\r/g
Correction des concaténations impliquant des caractères spéciaux
Il suffit d’ajouter l’opérateur + et un guillemet de début / fin de chaine lorsque c’est nécessaire. Par exemple :
Math['floor']((Math['random']() * (999)) + 1).exe';
devient :
Math['floor']((Math['random']() * (999)) + 1)+'.exe';
Sur le même principe, nous avons la variable char123 qui correspond à la chaine \\ :
- dans un premier temps nous corrigeons la concaténation. Par exemple :
var wings_Ephesus71=char123ancor.jse';
devient:
var wings_Ephesus71=char123+'ancor.jse';
- dans un second temps, nous remplaçons le pattern char123 par ladite chaine :
:%s/char123/'\\\\'/g
Fonctionnalités du script malveillant
Le script malveillant est complètement désobfusqué, nous pouvons maintenant analyser ses principales fonctionnalités.
Persistance
Lors de sa première exécution, le script se copie dans le dossier Startup. Il assure ainsi son lancement à chaque démarrage du système d’exploitation (figure 7).
Figure 7 : mécanisme de persistance du script malveillant
Mouvements latéraux
Le script énumère les disques réseaux et les disques amovibles connectés à la machine infectée. Pour chaque disque, il dresse la liste des fichiers d’extension *.doc *.xls *.txt *.pdf *.pub *.ppt *.tif *.rtf *.mp3 *.mp4 *.avi et sauvegarde cette dernière dans un fichier temporaire nommé p_o_l_l_o_s.txt. Le script parcours ensuite cette liste de fichiers cibles, et pour chaque entrée il se copie lui-même dans un nouveau fichier nommé nom_du_fichier_cible.jse puis efface le fichier cible original (figure 8). Il s’agit d’un comportement typiquement observé avec les vers informatiques.
Figure 8 : code de propagation du script
Le script malveillant usurpe le nom des documents présents sur la machine infectée ; si l’utilisateur ne prête pas attention au changement d’icone lié au changement d’extension, il le propagera en pensant ouvrir son document.
Communication avec le centre de commande
Le script malveillant concatène le chemin vers le répertoire %TEMP% et le chemin vers lui-même dans le répertoire Startup, puis en calcul le hash (figure 9, panneau du haut) ; ce hash, unique pour chaque utilisateur, fait 10 caractères de long. Le script construit ensuite l’url avec laquelle il contactera son centre de commande (figure 9, panneau du bas). Le centre de commande est contacté via l’envoi d’une requête POST contenant le nom du système d’exploitation et la liste des processus en cours d’exécution.
Figure 9 : construction de l’url pour contacter le centre de commande
L’url se compose de 5 variables :
- une chaine de 32 caractères ([a-f0-9]) qui pourrait être un identifiant de campagne
- un nombre aléatoire compris entre 1 et 201
- un hash utilisateur unique de 10 chiffres
- un entier pouvant valoir 0, 7 ou le PID d’un processus
- la longueur de la chaine à partir de laquelle le hash utilisateur a été calculé
Le centre de commande répond en envoyant un binaire qui sera sauvegardé dans le répertoire %TEMP% sous un nom aléatoire, puis exécuté par le script.
Récupération et identification de la charge finale
Avec ces informations, nous pouvons forger une url et récupérer un exemplaire de la charge malveillante à l’aide de wget (figure 10).
Figure 10 : récupération de la charge malveillante finale depuis le centre de commande.
Lors de son exécution dans notre sandox, le binaire téléchargé a été identifié comme appartenant à la famille Gootkit, un malware bancaire (figure 11). Il a également été possible de récupérer les webinjects, et avec elles la liste des établissements bancaires ciblés : l’exemplaire de Gootkit récupéré ciblait spécifiquement des banques françaises, qui ont été alertées par Orange Cyberdefense.
Figure 11 : exécution de la charge malveillante en sandbox
Conclusion
Au cours de cet article, nous avons illustré le processus de désobfuscation du code d’un vers JavaScript et présenté ses principales fonctionnalités : persistance, propagation et téléchargement d’un malware bancaire. La propagation du vers, puisqu’elle se fait via la destruction de données, alerte rapidement qu’un réseau a été compromis. Un malware bancaire, au contraire, vise avant tout la furtivité. Ce tandem est intriguant, et les raisons de cette association restent obscures. D’un coté, nous pouvons imaginer que le centre de commande contacté distribue toute sorte de malwares, dont Gootkit. D’un autre nous pouvons imaginer que les auteurs de l’attaque ont fait le pari que seul le JavaScript serait détecté, laissant Gootkit tapis dans l’ombre, à l’image de la muleta et du capote d’un matador.
IOCs
Malware |
Nom de fichier |
Sha256 |
Vers |
Ancor.jse |
eb60b608bed639d5111adee68addfd5815f1e5c109a0e03a109f808eb77875d0 |
Gootkit |
aléatoire |
7ce8f7a379e2f70259efbb901fc758a3c16ef6513abe397fb9035c669bffd03c |
Pour aller plus loin
Consultant sécurité depuis 2016, j'interviens sur des missions de réponse à incident pour le CSIRT Orange Cyberdefense et participe à des projets de R&D sur l'analyse de malwares. Mes trigger words : rétro-ingénierie, assembleur, malwares