Dans les derniers mois, CPR a enquêté sur l’utilisation du JavaScript V8 compilé par les auteurs de logiciels malveillants. Le JavaScript V8 compilé est une fonctionnalité moins connue de V8, le moteur JavaScript de Google, qui permet la compilation du JavaScript en bytecode de bas niveau. Cette technique aide les attaquants à éviter les détections statiques et à cacher leur code source original, le rendant presque impossible à analyser statiquement.
Pour analyser statiquement les fichiers JavaScript compilés, nous avons utilisé un outil personnalisé nouvellement développé nommé ”, spécialement conçu pour décompiler le bytecode V8 en un langage lisible de haut niveau. En utilisant View8, nous avons décompilé des milliers d’applications malveillantes V8 compilées, couvrant divers types de logiciels malveillants, tels que les outils d’accès à distance (RAT), les voleurs, les mineurs et même les ransomwares.
Comme le V8 compilé est rarement examiné, des portions significatives des échantillons ont un taux de détection très faible par les fournisseurs de sécurité, même s’ils ont été utilisés dans des attaques réelles.
Dans cet article, nous expliquons ce qu’est le JavaScript V8 compilé, comment les attaquants peuvent l’utiliser dans leurs logiciels malveillants et, surtout, comment il apparaît dans la nature, tel qu’utilisé par de vrais acteurs de la menace.
V8 est un moteur JavaScript open-source développé par Google. Il est écrit en C++ et utilisé dans Google Chrome et plusieurs autres projets publics, y compris Node.js. Le bytecode V8 (Ignition) sert d’étape intermédiaire dans le processus d’optimisation du code JavaScript. Il permet au moteur V8 d’exécuter efficacement du JavaScript en sérialisant et en traduisant le code optimisé plus près du code machine.
V8 prend en charge la possibilité de mettre en cache le bytecode sérialisé pour une exécution ultérieure par l’interprète. Bien qu’initialement conçue pour améliorer les performances en contournant les étapes d’analyse initiales, cette fonctionnalité est utilisée par les développeurs, et surtout par les auteurs de logiciels malveillants, pour cacher le code source de l’application.
Pour exploiter cette fonctionnalité et compiler du JavaScript en bytecode V8 sérialisé, nous pouvons utiliser le module intégré dans la plateforme Node.js. La méthode utilise deux paramètres : le premier est le code JavaScript, et le second est un dictionnaire d’options. Dans le cas de la compilation, nous passons l’option, ce qui donne un tampon avec le bytecode sérialisé. Par exemple, considérez le code suivant :
const vm = require('vm');// Compiling JavaScript into serialized bytecodelet helloWorld = new vm.Script("console.log('hello world!')", { produceCachedData: true });let compiledBuffer = helloWorld.cachedData;
Alors que le module fournit une méthode native et directe pour la sérialisation du bytecode, il est plus pratique d’utiliser le module qui simplifie le processus à la fois pour la compilation du bytecode et pour l’exécution ultérieure.
const bytenode = require('bytenode');// Compiling JavaScript into bytecode and executing itbytenode.compileFile('script.js', 'script.jsc'); // Compiling JavaScript to bytecoderequire('./script.jsc'); // Running the compiled bytecode
L’objet bytecode V8 se compose d’en-têtes précédant les données sérialisées. Voici la répartition structurelle de l’en-tête (Remarque - Dans les anciennes versions de V8, la structure est légèrement différente) :
struct CahcedDataHeaders{static const uint32_t kMagicNumber;// 0xC0DE0000 ^ ExternalReferenceTable::kSizestatic const uint32_t kVersionHash;// V8 version hashedstatic const uint32_t kSourceHash;// Original source code lengthstatic const uint32_t kFlagHash// V8 flags hashedstatic const uint32_t kPayloadLength// Bytecode lengthstatic const uint32_t kChecksum// Bytecode Adler-32 checksum};
Le bytecode V8 compilé est conçu pour s’exécuter exclusivement sur la version de V8 pour laquelle il a été compilé. Avant de désérialiser l’objet compilé, le moteur V8 compare la version actuelle (hachée) avec la version stockée dans les en-têtes. En cas de divergence, le processus d’analyse échoue.
Comme le bytecode V8 compilé est lié à la version spécifique pour laquelle il a été compilé, les attaquants doivent assurer la compatibilité entre le bytecode et le moteur V8 pour une exécution réussie. Cela peut être réalisé de différentes manières. Trois approches courantes :
À l’heure où nous écrivons ces lignes, il n’existe pas de solution publiquement connue pour décompiler le bytecode V8 en un langage de haut niveau. Bien qu’il y ait eu un effort communautaire pour développer un , il était dédié à une version spécifique de V8, et il a été jugé trop difficile à reproduire pour d’autres versions.
View8 est un nouvel outil d’analyse statique open-source pour décompiler le bytecode V8 en un code lisible de haut niveau. Cet outil, écrit en Python, a été développé par l’un de nos membres de CPR et est maintenant disponible pour la communauté de la sécurité. View8 prend un fichier compilé en argument et produit une version décompilée textuelle dans un langage similaire à JavaScript. Plus important encore, l’outil est relativement simple à maintenir pour plusieurs versions de V8.
En utilisant View8, nous avons réussi à décompiler et analyser des milliers de fichiers V8 malveillants compilés provenant de diverses sources. Notre enquête a découvert un large éventail de familles de logiciels malveillants, y compris des voleurs, des chargeurs, des RAT, des effaceurs et des ransomwares. Remarquablement, la majorité de ces fichiers avaient un score de détection très faible sur VirusTotal.
Les acteurs de la menace semblent en être très conscients, car nous avons vu des auteurs de logiciels malveillants souligner les faibles taux de détection de certaines familles de logiciels malveillants qui utilisaient du code V8 :
Dans la bataille en cours entre les experts en sécurité et les acteurs de la menace, les développeurs de logiciels malveillants continuent de trouver de nouveaux stratagèmes pour cacher leurs attaques. Il n’est pas surprenant qu’ils aient commencé à utiliser V8, car cette technologie est couramment utilisée pour créer des logiciels, elle est très répandue et extrêmement difficile à analyser.
Dans cet article, nous vous donnons une compréhension de base de la façon dont le code compilé V8 est utilisé non seulement dans les applications régulières mais aussi à des fins malveillantes. Comme cette technologie est déjà utilisée par les acteurs de la menace, nous avons inclus des exemples de son application dans différentes familles de logiciels malveillants, principalement par ChromeLoader, qui est écrit de manière à suggérer que les attaquants sont très familiers avec la technologie.
Beaucoup de nos informations proviennent de notre utilisation de View8, un nouvel outil qui facilite la décomposition du code V8 compilé. Nous avons pu étudier plus facilement les logiciels malveillants V8 en les traduisant en une forme de pseudo-JavaScript plus facile à comprendre et donc à analyser. Nous espérons que cet outil, devenant disponible, aidera d’autres à trouver et à arrêter les logiciels malveillants V8 qui volent sous le radar depuis trop longtemps.