Résumé exécutif
Cet article détaille notre analyse technique du VVS stealer, également stylisé VVS $tealer, y compris l'utilisation de l'offuscation et de l'évasion de détection par ses distributeurs.
Ce stealer est écrit en Python et cible les utilisateurs de Discord, exfiltrant des informations sensibles telles que les identifiants et les jetons (tokens) stockés dans les comptes Discord. Ce stealer était autrefois en développement actif et commercialisé sur Telegram dès avril 2025.
Le code du VVS stealer est offusqué par Pyarmor. Cet outil est utilisé pour offusquer les scripts Python afin d'entraver l'analyse statique et la détection basée sur les signatures. Pyarmor peut être utilisé à des fins légitimes, mais aussi exploité pour construire des logiciels malveillants furtifs.
Les auteurs de logiciels malveillants exploitent de plus en plus des techniques d'offuscation avancées pour échapper à la détection par les outils de cybersécurité, rendant leurs logiciels malveillants plus difficiles à analyser et à rétro-ingénierier. Cet article montre comment nous avons désoffusqué des échantillons du VVS stealer pour mieux comprendre ses opérations.
Du fait de la facilité d'utilisation de Python pour les auteurs de maliciels et de l'offuscation complexe utilisée par cette menace, le résultat est une famille de logiciels malveillants très efficace et furtive.
Les clients de Palo Alto Networks sont mieux protégés grâce aux produits et services suivants :
- Advanced WildFire
- Advanced URL Filtering et Advanced DNS Security
- Cortex XDR et XSIAM préviennent les menaces décrites dans cet article en employant le Malware Prevention Engine
Si vous pensez avoir été compromis ou si vous avez une urgence, contactez l'équipe de réponse aux incidents de Unit 42.
| Sujets Unit 42 liés | Infostealer, Anti-analysis, Discord, Pyarmor |
Introduction
Discord est une plateforme de messagerie sociale et de communication devenue une cible populaire pour les logiciels malveillants, comme le VVS stealer. Le VVS stealer est conçu pour voler les informations Discord et les données de navigateur d'une victime.
La Figure 1 montre les capacités annoncées du VVS stealer, notamment :
- Le vol de données Discord (jetons et informations de compte)
- L'interception de sessions Discord actives via injection
- L'extraction de données de navigateurs web (cookies, mots de passe, historique de navigation et détails de remplissage automatique)

Le stealer assure également sa persistance en s'installant automatiquement au démarrage. Il opère furtivement en affichant de faux messages d'erreur et en capturant des captures d'écran. Pour une enquête plus approfondie sur l'opération, veuillez vous référer à l'article de DeepCode, Investigating VVS $tealer: A Python-Based Discord Malware.
Analyse technique
Cette section analyse un échantillon du maliciel VVS stealer protégé par Pyarmor avec le hachage SHA-256 suivant :
- c7e6591e5e021daa30f949a6f6e0699ef2935d2d7c06ea006e3b201c52666e07
La Figure 2 présente un diagramme récapitulatif illustrant le flux de travail complet de l'analyse de l'échantillon.

Première étape : Extraction depuis le binaire PyInstaller
L'échantillon que nous avons analysé est distribué sous forme de paquet PyInstaller. PyInstaller est un outil qui regroupe une application Python et ses dépendances dans un paquet pour permettre l'exécution d'une application empaquetée sans installer de modules supplémentaires.
Toute installation standard de PyInstaller est livrée avec l'utilitaire intégré pyi-archive_viewer. Nous avons utilisé cet utilitaire pour extraire et inspecter les fichiers suivants de notre échantillon :
- Le fichier bytecode Python nommé vvs
- Le fichier de bibliothèque de liens dynamiques (DLL) d'exécution Pyarmor nommé pyarmor_runtime.pyd, situé dans le sous-dossier pyarmor_runtime_007444
- Le fichier __init__.py accompagnant ce même sous-dossier, qui inclut les informations suivantes :
- Version de Pyarmor : 9.1.4 (Pro)
- Numéro de licence unique : 007444
- Horodatage : 2025-04-27T11:04:52.523525
- Nom du produit : vvs
- Le fichier __init__.py accompagnant ce même sous-dossier, qui inclut les informations suivantes :
- Fichier DLL Python 3.11 nommé python311.dll
- Les informations de version du fichier indiquent que la version de Python est la 3.11.5
PyInstaller stocke le bytecode Python (listé en 1.) sous sa forme brute. Cette forme brute fait référence à la séquence de bytecode commençant par la valeur e3. La valeur e3 est une combinaison à la fois du drapeau (flag) et du type, combinés via la constante FLAG_REF.
Le type représenté par la valeur e3 est calculé comme suit : type = e3 & ~FLAG_REF. Cela signifie que la valeur e3 est en fait le type 0x63 (la lettre c), également connu sous le nom de constante d'énumération TYPE_CODE. L'implémentation complète de cette dérivation peut être trouvée dans la base de code CPython 3.11.
La Figure 3 ci-dessous montre que cet objet code sérialisé par le module marshal est nu, manquant d'un en-tête de 16 octets accompagnateur (marqué en bleu). Pour fournir suffisamment d'éléments Python pour que le décompilateur ne rejette pas le fichier, nous devons restaurer au moins l'une des valeurs d'en-tête (le nombre magique de Python 3.11.5 au format 4 octets, little-endian) avant la décompilation, car le décompilateur Python attend un fichier bytecode Python valide (.pyc) comme entrée.

Nous commençons notre analyse en décompilant le fichier bytecode Python (.pyc) nommé vvs pour récupérer son code source Python équivalent (.py).
Deuxième étape : Décompilation vers le code source Python
Pycdc est un décompilateur de bytecode Python écrit en C++. Il fait partie du projet Decompyle++. Il prend en charge la décompilation du bytecode Python 3.11 « pour revenir à un code source Python valide et lisible par l'humain. » (Source : GitHub.) PyLingual est un autre décompilateur de bytecode Python.
Après avoir cloné le dépôt de code et compilé la base de code, l'exécutable généré peut être invoqué comme suit pour décompiler le bytecode Python en code source Python via Pycdc :
- pycdc.exe -c -v "3.11.5" "vvs.pyc" > "vvs.py"
Cela produira le code source Python décompilé montré dans la Figure 4.

Nous analysons ensuite le dernier argument de la fonction, qui peut être extrait via l'ast.NodeVisitor de Python 3.
Troisième étape : Démêler l'offuscation Pyarmor
La charge utile (payload) commence par l'en-tête Pyarmor montré dans la Figure 5.

La cryptographie est effectuée tout du long en utilisant l'algorithme Advanced Encryption Standard (AES) avec une clé de 128 bits, opérant en mode Compteur (CTR) avec une valeur initiale de deux (c'est-à-dire, AES-128-CTR). Le Tableau 1 montre la décomposition des champs.
| Décalages (Offsets) | Valeurs | Description |
| 0x00 … 0x07 | PY007444 | Signature de fichier contenant le numéro de licence unique |
| 0x09 | 03 | Version majeure de Python |
| 0x0a | 0b | Version mineure de Python |
| 0x14 | 09 | Type de protection :
|
| 0x1c … 0x1f | 40 00 00 00 | Début de la charge utile ELF, au format little-endian |
| 0x24 … 0x27 | 12 c9 06 00 | Quatre premiers octets du nonce AES-128-CTR |
| 0x2c … 0x33 | dc d2 98 a1 ea 11 fd f4 | Huit octets restants du nonce AES-128-CTR |
| 0x38 … 0x3b | a0 7f 02 00 | Fin de la charge utile ELF, au format little-endian |
Tableau 1. Décomposition des champs présents dans l'en-tête Pyarmor.
Ce même motif (surligné en jaune) se répète une fois de plus après la fin de la charge utile ELF, pour extraire et déchiffrer la charge utile bytecode Pyarmor.
Mode BCC
Le mode BCC (probablement une abréviation de ByteCode-to-Compilation) convertit la plupart « des fonctions et méthodes dans les scripts en fonctions C équivalentes. Ces fonctions C seront compilées directement en instructions machine, puis appelées par les scripts offusqués. » (Source : Documentation Pyarmor.)
Le mode BCC est invoqué comme suit : pyarmor gen --enable-bcc script.py.
Ces fonctions C converties sont stockées dans un fichier ELF séparé, produit aux côtés du bytecode marshallé par Pyarmor.
Le mappage des constantes Python vers les fonctions BCC peut être obtenu en utilisant cette implémentation. Par exemple, dans la méthode Python get_encryption_key(browser_path), la constante __pyarmor_bcc_58580__ mappe vers la fonction BCC bcc_180, dont le corps de fonction est situé au décalage 0x4e70 du fichier ELF.
En se référant à cette analyse du contenu du fichier ELF, en particulier la structure bcc_ftable, la Figure 6 montre une partie de la fonction BCC bcc_180 décompilée :

Nous pouvons grossièrement récupérer un équivalent du code original de la méthode Python get_encryption_key, comme montré dans la Figure 7.

Format de bytecode marshallé
Le bytecode marshallé de Pyarmor 9 diffère du bytecode Python 3.11 standard de plusieurs manières. Premièrement, le bit 0x20000000 est défini dans le champ co_flags pour indiquer qu'il est offusqué par Pyarmor. Deuxièmement, il y a un champ de données supplémentaire, dont la longueur est indiquée par la valeur de son premier octet.
De plus, deopt_code() doit être désactivé pour que la séquence de bytecode soit déchiffrée avec succès. Nous discuterons des paramètres cryptographiques dans une section ultérieure de cet article.
Structure de l'objet code
Les objets code Pyarmor sont spécialement conçus, en ce sens qu'ils devraient contenir certains artefacts. Il est courant de s'attendre à trouver l'instruction LOAD_CONST __pyarmor_enter_*__ dans le préambule et l'instruction LOAD_CONST __pyarmor_exit_*__ dans la fin du désassemblage. Ces deux instructions envelopperaient le bytecode chiffré, comme montré dans le Tableau 2.
| Opération | Argument |
| … | |
| LOAD_CONST | __pyarmor_enter_58592__ |
| LOAD_CONST | \x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x20\x16\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 |
| … séquence de bytecode chiffrée (à examiner dans la section suivante) … | |
| LOAD_CONST | __pyarmor_exit_58593__ |
| LOAD_CONST | \x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x20\x16\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 |
| … | |
Tableau 2. Instructions liées à Pyarmor dans le désassemblage de <module>.
Une fois la séquence de bytecode chiffrée déchiffrée, elle pourrait révéler des chaînes chiffrées ou des invocations de fonctions BCC. Les chaînes chiffrées (examinées dans une section ultérieure de cet article) sont précédées par une instruction LOAD_CONST __pyarmor_assert_*__. Il y a aussi l'instruction LOAD_CONST __pyarmor_bcc_*__ pour invoquer une fonction BCC (examinée plus tôt dans cet article).
Chiffrement de l'objet code
Les séquences de bytecode entre le marqueur de début (__pyarmor_enter_*__) et le marqueur de fin (__pyarmor_exit_*__) sont chiffrées en AES-128-CTR. La clé AES associée (273b1b1373cf25e054a61e2cb8a947b8) est extraite de la DLL d'exécution Pyarmor liée au numéro de licence unique.
D'autre part, la clé XOR du nonce AES correspondant (2db99d18a0763ed70bbd6b3c) est uniquement spécifique à la charge utile bytecode Pyarmor, pour laquelle il existe une implémentation de la logique d'extraction de cette valeur. Cette clé est XORée avec les 12 octets au marqueur de fin (__pyarmor_exit_*__) pour produire le nonce AES correct utilisé dans le déchiffrement.
Chiffrement des chaînes de caractères
De même, les constantes de chaîne de plus de huit caractères sont chiffrées en AES-128-CTR (appelé « mixed » dans la terminologie Pyarmor). La clé AES associée est également 273b1b1373cf25e054a61e2cb8a947b8, mais cette fois, le nonce AES correspondant (692e767673e95c45a1e6876d) est calculé à partir de la DLL d'exécution Pyarmor liée au numéro de licence unique.
De plus, une valeur de préfixe 0x81 indique que la constante de chaîne est chiffrée. Sinon, une valeur de préfixe 0x01 est utilisée à la place.
Maintenant que la protection Pyarmor est désarmée, nous allons procéder à la présentation de certaines des capacités clés du VVS stealer dans la section suivante.
Capacités du maliciel
Une fois les couches d'offuscation Pyarmor — y compris le mode BCC et le chiffrement de chaînes AES-128-CTR — retirées avec succès, nous avons pu exposer la logique Python sous-jacente. Ce code désoffusqué a révélé un stealer conçu non seulement pour l'exfiltration de données, mais aussi pour le détournement de session active et la persistance. La section suivante détaille les capacités opérationnelles spécifiques du VVS stealer qui ont été découvertes lors de cette analyse.
L'échantillon de maliciel expire après le 2026-10-31 23:59:59. Il cessera de fonctionner en se terminant prématurément.
L'échantillon de maliciel effectue toutes les requêtes HTTP en envoyant la chaîne User-Agent fixe Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36.
Nous allons maintenant donner un aperçu des principales capacités du maliciel, telles qu'annoncées sur Telegram.
Données Discord
L'échantillon de maliciel recherche d'abord des jetons Discord potentiellement chiffrés. Les jetons Discord chiffrés sont des chaînes commençant par le préfixe dQw4w9WgXcQ:. L'échantillon de maliciel utilise des expressions régulières pour former un motif à partir de ce préfixe de chaîne. Il utilise ensuite ce motif pour chercher à l'intérieur du contenu des fichiers avec les extensions .ldb ou .log, stockés dans le répertoire LevelDB.
Ensuite, l'échantillon de maliciel déchiffre la valeur encrypted_key dans le fichier Local State, via l'interface de programmation d'application de protection des données (DPAPI). Avec cette valeur encrypted_key déchiffrée comme paramètre de clé AES, l'échantillon de maliciel applique l'algorithme AES, opérant en mode Galois/Counter Mode (GCM), sur les jetons Discord chiffrés, pour les déchiffrer.
L'échantillon de maliciel utilise ensuite les jetons Discord déchiffrés pour interroger divers points de terminaison (endpoints) de l'interface de programmation d'application (API) Discord pour obtenir des informations sur l'utilisateur, notamment :
- Abonnement Nitro (fonctionnalités Discord Premium)
- Méthodes de paiement
- ID utilisateur
- Nom d'utilisateur
- Numéro de téléphone
- Amis
- Guildes (serveurs)
- Statut de l'authentification multifacteur (MFA)
- Paramètres régionaux (Locale)
- Statut de vérification
- Image d'avatar
- Adresse IP (via le service ipify)
- Nom de l'ordinateur
Après avoir rassemblé toutes ces informations, l'échantillon de maliciel procède à leur exfiltration au format JavaScript Object Notation (JSON). L'exfiltration a lieu via des requêtes HTTP POST vers les points de terminaison webhook prédéfinis (variable d'environnement %WEBHOOK% et URL de repli codées en dur).
Les webhooks sont « un moyen facile de poster des messages dans des canaux sur Discord. Ils ne nécessitent pas d'utilisateur bot ou d'authentification pour être utilisés. » (Source : Portail des développeurs Discord.)
Injection Discord
Le code responsable de cette fonctionnalité se trouve dans la classe Inj, probablement une abréviation de Injection.
Dans cette classe, l'échantillon de maliciel tue d'abord les processus de l'application Discord en cours d'exécution, s'il y en a. Il télécharge ensuite la charge utile JavaScript (JS) depuis un fichier distant nommé injection-obf.js (le suffixe -obf signifie probablement une version offusquée du script), remplaçant l'URL du point de terminaison webhook et discord_desktop_core, dans le répertoire de l'application Discord. Ce fichier JS est offusqué par l'outil JavaScript Obfuscator et peut être désoffusqué via le désoffuscateur Obfuscator.io.
Certaines des fonctionnalités principales du code JS injecté sont mises en évidence dans les captures d'écran suivantes, en commençant par ses extraits de code de configuration et d'exfiltration, montrés dans la Figure 8.

La Figure 8 montre le code JS injecté responsable de l'établissement de la persistance dans l'application Discord, basé sur le framework Electron. Ce framework utilise des archives Atom Shell Archive Format (ASAR) pour regrouper l'intégralité de la base de code de l'application dans un seul fichier, montré dans la Figure 9.

La Figure 10 montre le code JS injecté responsable de la surveillance du trafic réseau via le protocole Chrome DevTools (CDP).

La Figure 11 montre les fonctions utilitaires de support et les hooks d'événement dans le code JS injecté. Les hooks d'événement sont des fonctions de rappel (callback) qui s'exécutent lorsque l'utilisateur de l'application Discord effectue une action spécifique. Les actions d'intérêt sont lorsque l'utilisateur consulte ses codes de secours, change son mot de passe ou ajoute une méthode de paiement. Les fonctions de rappel liées à ces actions sont capables de collecter les informations de compte et de facturation de l'utilisateur Discord.

Par la suite, l'échantillon de maliciel redémarre un processus d'application Discord compromis via Update.exe, ce qu'il fait avec le commutateur de ligne de commande --processStart.
Données du navigateur web
L'échantillon de maliciel cible une liste d'applications de navigateur web, notamment :
- Chrome
- Edge
- 7Star
- Amigo
- Brave
- CentBrowser
- Discord
- Epic Privacy Browser
- Iridium
- Kometa
- Lightcord
- Mozilla Firefox
- Opera
- Orbitum
- Sputnik
- Torch
- Uran
- Vivaldi
- Yandex
De ces cibles, l'échantillon de maliciel extrait les données suivantes, lorsqu'elles sont présentes :
- Remplissage automatique (Autofill)
- Cookies
- Historique
- Mots de passe
Une fois ces données extraites, l'échantillon de maliciel les prépare pour l'exfiltration en les compressant dans un seul fichier d'archive ZIP nommé <USERNAME>_vault.zip. Il exfiltre ensuite ce fichier via des requêtes HTTP POST vers les points de terminaison webhook prédéfinis, similaire au processus d'exfiltration des données Discord.
Persistance au démarrage
L'échantillon de maliciel se copie dans le dossier %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup pour obtenir une persistance au démarrage. Le maliciel reste sur l'appareil de l'utilisateur, lui permettant de continuer à exfiltrer des données si, par exemple, l'utilisateur tente d'installer une nouvelle copie de l'application Discord.
Fausse erreur
L'échantillon de maliciel utilise l'API Win32, spécifiquement la fonction MessageBoxW dans la bibliothèque User32.dll, pour afficher une boîte de message modale concernant une fausse erreur fatale nécessitant le redémarrage de l'ordinateur. Une boîte de message modale est une petite fenêtre de dialogue nécessitant une interaction de l'utilisateur avant que l'application puisse continuer, comme montré dans la Figure 12.

Conclusion
Le VVS stealer démontre comment des outils comme Pyarmor, qui peuvent être utilisés à des fins légitimes, peuvent également être exploités pour construire des logiciels malveillants furtifs visant à détourner des identifiants pour des plateformes populaires telles que Discord. Son émergence signale un besoin pour les défenseurs de renforcer la surveillance autour du vol d'identifiants et de l'abus de compte.
Protection et atténuation Palo Alto Networks
Les clients de Palo Alto Networks sont mieux protégés contre les menaces discutées ci-dessus grâce aux produits suivants :
Les modèles d'apprentissage automatique et les techniques d'analyse d'Advanced WildFire ont été revus et mis à jour à la lumière des indicateurs partagés dans cette recherche.
Advanced URL Filtering et Advanced DNS Security identifient les domaines et URL connus associés à cette activité comme malveillants.
Cortex XDR et XSIAM préviennent les menaces décrites dans cet article en employant le Malware Prevention Engine. Cette approche combine plusieurs couches de protection, incluant Advanced WildFire, la protection contre les menaces comportementales (Behavioral Threat Protection) et le module d'analyse locale, pour empêcher les logiciels malveillants connus et inconnus de causer des dommages aux points de terminaison (endpoints).
Si vous pensez avoir été compromis ou si vous avez une urgence, contactez l'équipe de réponse aux incidents de Unit 42 ou appelez :
- Amérique du Nord : Numéro vert : +1 (866) 486-4842 (866.4.UNIT42)
- Royaume-Uni : +44.20.3743.3660
- Europe et Moyen-Orient : +31.20.299.3130
- Asie : +65.6983.8730
- Japon : +81.50.1790.0200
- Australie : +61.2.4062.7950
- Inde : 000 800 050 45107
- Corée du Sud : +82.080.467.8774
Palo Alto Networks a partagé ces découvertes avec nos membres de la Cyber Threat Alliance (CTA). Les membres de la CTA utilisent ces renseignements pour déployer rapidement des protections pour leurs clients et pour perturber systématiquement les acteurs cybermalveillants. En savoir plus sur la Cyber Threat Alliance.
Indicateurs de compromission
Hachages SHA-256 des échantillons de maliciels :
- 307d9cefa7a3147eb78c69eded273e47c08df44c2004f839548963268d19dd87
- 7a1554383345f31f3482ba3729c1126af7c1d9376abb07ad3ee189660c166a2b
- c7e6591e5e021daa30f949a6f6e0699ef2935d2d7c06ea006e3b201c52666e07
URL de webhook Discord :
- hxxps[://]ptb.discord[.]com/api/webhooks/1360401843963826236/TkFvXfHFXrBIKT3EaqekJefvdvt39XTAxeOIWECeSrBbNLKDR5yPcn75uIqKEzdfs9o2
- hxxps[://]ptb.discord[.]com/api/webhooks/1360259628440621087/YCo9eVnIBOYSMn8Xr6zX5C7AJF22z26WljaJk4zr6IiThnUrVyfWCZYs6JjSC12IC8c0
Ressources supplémentaires
- Unpacking Pyarmor v8+ scripts – Leonard Rapp et Hendrik Eckardt, G DATA Advanced Analytics GmbH
- Obfuscated Malicious Python Scripts with PyArmor – Xavier Mertens, SANS Internet Storm Center