Skip to content

Leçon 10.3 · Au-delà de l'EXE· 45 min

Malwares PowerShell, JavaScript et VBScript

Désobfusquer PowerShell, JavaScript et VBScript malveillants sans les lancer : neutraliser eval, décoder les couches, lire les journaux, extraire les IOC.

Objectifs

  • Nommer les hôtes Windows qui exécutent les malwares de script (powershell.exe, wscript.exe, cscript.exe, mshta.exe) et les voies par lesquelles ces scripts arrivent
  • Reconnaître les familles d’obfuscation courantes : concaténation, codes de caractères, Base64 et -EncodedCommand, chaînes de format, bruit d’échappement et eval en couches
  • Éplucher un script obfusqué couche par couche en remplaçant son point d’exécution par un affichage, sans exécuter la charge utile
  • Expliquer ce que la journalisation des blocs de script (événement 4104), la journalisation des modules, AMSI et les lignes de commande Sysmon apportent à un défenseur
  • Extraire d’un script décodé les URL, les chemins et les hashs des étapes suivantes, et les consigner dans un rapport

Tous les échantillons ne sont pas des fichiers PE. Une large part des malwares (logiciels malveillants) de première étape tient en quelques kilo-octets de texte : un fichier .js dans une pièce jointe ZIP, un .vbs déposé par un raccourci, un .hta récupéré via un lien, ou une unique ligne de commande powershell.exe capturée par un EDR quelques secondes avant l’arrivée de la vraie charge utile. Il n’y a aucun en-tête à analyser et rien à désassembler. Ce qui vous sépare de la réponse, c’est l’obfuscation, souvent en plusieurs couches.

Les scripts sont aussi les échantillons les plus faciles à exécuter par accident. Double-cliquer sur un fichier .js sous Windows n’ouvre pas un éditeur : cela l’exécute. Cette leçon adopte le point de vue d’un analyste à qui l’on remet un tel script et qui doit déterminer ce qu’il fait, quelle infrastructure il contacte et ce qu’il dépose, sans le laisser s’exécuter sur une vraie machine.

Où l’on rencontre les malwares de script

Chaque machine Windows est livrée avec des interpréteurs de script, les scripts se modifient trivialement d’une campagne à l’autre, et ils peuvent récupérer et lancer l’étape suivante sans binaire compilé. Points d’entrée typiques :

  • Pièces jointes d’e-mails : des fichiers .js, .vbs, .wsf ou .hta, généralement dans une archive ZIP, ISO ou protégée par mot de passe pour passer les filtres de messagerie.
  • Fichiers de raccourci (.lnk) dont la cible est powershell.exe ou mshta.exe avec une longue chaîne d’arguments.
  • Macros Office qui ne font guère plus que lancer PowerShell avec une commande encodée. La prochaine leçon, Documents et archives malveillants, couvre le versant document.
  • Fausses pages de mise à jour et de CAPTCHA qui demandent à la victime de coller une commande dans la boîte de dialogue Exécuter ou dans un terminal.
  • Post-exploitation : les attaquants déjà présents sur un réseau utilisent PowerShell pour la reconnaissance, le mouvement latéral et le chargement d’outils directement en mémoire.

Dans la plupart de ces cas, le script est un downloader : il récupère une deuxième étape (un EXE, une DLL, du shellcode ou un autre script) et l’exécute. Votre travail consiste à trouver ce qu’il récupère, d’où, et comment il l’exécute.

Les hôtes qui les exécutent

HôteExécuteRemarques
powershell.exe / pwsh.exe.ps1, -Command en ligne, -EncodedCommandAccès complet à .NET ; Windows PowerShell 5.1 est intégré, PowerShell 7 est pwsh.exe
wscript.exe.js, .jse, .vbs, .vbe, .wsfWindows Script Host, version graphique ; le gestionnaire par défaut quand un utilisateur double-clique
cscript.exeComme wscript.exeVersion console de Windows Script Host ; la sortie va dans le terminal
mshta.exe.hta, URL vbscript: ou javascript: en ligneExécute une application HTML avec tous les privilèges locaux, hors de la sandbox (bac à sable) du navigateur
cmd.exe.bat, .cmdSurtout utilisé comme liant pour lancer l’un des hôtes ci-dessus

Deux choses surprennent les débutants. D’abord, un fichier .js sous Windows Script Host est du JScript, l’ancien dialecte JavaScript de Microsoft, et non du JavaScript de navigateur ou de Node.js : il n’y a ni document ni console, mais il y a WScript.Shell pour exécuter des commandes et ActiveXObject pour les téléchargements HTTP et l’accès aux fichiers. Ensuite, .jse et .vbe sont des scripts « encodés », selon un schéma Microsoft trivial que des décodeurs publics inversent instantanément ; ils ne sont pas chiffrés. Microsoft a annoncé l’obsolescence de VBScript, mais il reste présent sur la plupart des systèmes que vous examinerez.

Le processus parent compte autant que le fichier : un winword.exe qui lance powershell.exe constitue une détection à lui seul. Processus, threads et DLL explique l’arbre des processus.

Familles d’obfuscation

L’obfuscation de script poursuit un seul but : faire en sorte qu’aucun scanner et aucun humain ne voie les chaînes intéressantes (http://, DownloadString, Invoke-Expression, WScript.Shell) dans le fichier tel qu’il est livré. Les techniques sont peu nombreuses et elles se cumulent.

FamilleExemple PowerShellExemple JavaScript / VBScript
Concaténation et découpage'Down' + 'loadStr' + 'ing'"WScr" + "ipt.Sh" + "ell"
Codes de caractères[char]73 + [char]69 + [char]88String.fromCharCode(101,118,97,108), Chr(69) & Chr(120)
Base64[Convert]::FromBase64String(...), -EncodedCommandatob(...) dans les navigateurs ; décodeurs maison en JScript
Réordonnancement par chaîne de format"{2}{0}{1}" -f 'ad','String','Downlo'accès à un tableau par indices mélangés : _0x3a[4] + _0x3a[1]
Bruit d’échappement et de casseI`nv`oKe-ExPrEsSiOn (les accents graves sont ignorés)c^m^d dans cmd.exe (les carets sont ignorés) ; casse aléatoire en VBScript
Renommage$qZx, $____, ${ }_0x4f2a, lettres isolées, sosies Unicode
Inversion et découpage en tranches-join ('gnirts'[-1..-6]).split("").reverse().join("")
Chiffrement ou compressionIO.Compression.DeflateStream, boucles XORBoucles XOR ou RC4 avec une clé stockée à côté du blob
Exécution en couchesInvoke-Expression, iex, [scriptblock]::Create()eval, new Function, Execute et ExecuteGlobal en VBScript

La plupart de ces techniques ne changent que la façon dont une chaîne est écrite. La dernière ligne est ce qui retransforme la chaîne en code, et c’est la clé de tout le processus.

-EncodedCommand est en UTF-16LE

Le paramètre -EncodedCommand de PowerShell (accepté sous la forme -e, -en, -enc, -ec et d’autres préfixes, quelle que soit la casse) prend le Base64 du texte de la commande encodé en UTF-16LE, le même encodage large que celui décrit dans Chaînes et chaînes obfusquées. Décodez-le en UTF-8 et chaque caractère sera suivi d’un octet nul. Ces blobs se reconnaissent à l’œil : le Base64 d’un texte ASCII en UTF-16LE est rempli de caractères A, car chaque octet nul contribue à des suites comme AA et à des motifs tels que JABhACAA, l’encodage de $a . Décodez en UTF-16LE (From Base64 puis Decode text en UTF-16LE dans CyberChef) et vous obtenez du code lisible.

Pourquoi des couches ?

Chaque couche est une chaîne que la couche précédente décode et transmet à un point d’exécution (sink). Une chaîne typique : un .lnk exécute powershell -enc <Base64> ; la commande décodée concatène des fragments en un deuxième blob Base64, le décompresse avec DeflateStream et passe le résultat à Invoke-Expression ; cette troisième couche télécharge une DLL et la charge en mémoire avec [Reflection.Assembly]::Load, le cousin scripté de l’injection réflexive de DLL. Trois couches, et seule la dernière fait quelque chose d’intéressant.

Éplucher un script sans risque

La méthode qui fonctionne sur presque tout :

  1. Travaillez sur une copie, dans le laboratoire. Renommez le fichier avec une extension inoffensive (.js.txt, .ps1.txt) pour qu’un double-clic malencontreux ouvre un éditeur, et faites le travail dans votre VM d’analyse, réseau coupé ou simulé. Hachez d’abord l’original.
  2. Mettez en forme. Les scripts d’une seule ligne deviennent lisibles après js-beautify, un formateur PowerShell ou un simple découpage sur ;. Renommez les variables au fur et à mesure que vous les comprenez.
  3. Trouvez les points d’exécution. Recherchez eval, Function(, Execute, ExecuteGlobal, Invoke-Expression, iex, ScriptBlock, .Invoke(, Start-Process, WScript.Shell, .Run(, .Exec( et ActiveXObject. Tout ce qui précède un point d’exécution est généralement du décodage ; le point d’exécution est l’endroit où la couche suivante prend vie.
  4. Remplacez le point d’exécution par un affichage. eval(x) devient console.log(x), Invoke-Expression $x devient Write-Output $x, et Execute x en VBScript devient WScript.Echo x. Le code de décodage s’exécute, et le résultat décodé est affiché au lieu d’être exécuté.
  5. Recommencez sur la sortie affichée jusqu’à atteindre du code qui fait autre chose que décoder du code.
  6. Décodez statiquement quand c’est possible. Si une couche n’est que du Base64 ou un tableau de codes de caractères, décodez-la dans CyberChef ou en quelques lignes de Python plutôt que d’exécuter quoi que ce soit.

Attention : Remplacer le point d’exécution final n’est sûr que si rien avant lui n’agit sur le système. Lisez chaque ligne qui va s’exécuter. Les malwares téléchargent souvent, écrivent un fichier ou vérifient l’environnement tôt, et décodent plus tard. Si une ligne appelle DownloadString, Run, Start-Process, WScript.Shell ou un ActiveXObject, neutralisez-la aussi, ou décodez cette partie à la main. Et exécutez le script neutralisé dans la VM du laboratoire, jamais sur votre poste de travail.

Pour le JScript, il existe une couche de sécurité supplémentaire : Node.js ne possède ni WScript ni ActiveXObject, si bien que les appels propres à Windows échouent au lieu de s’exécuter. Des émulateurs comme box-js vont plus loin et fournissent de fausses versions de ces objets qui journalisent chaque appel (URL demandées, fichiers écrits, commandes lancées) sans les effectuer.

Astuce : Conservez chaque couche. Enregistrez chaque étape décodée dans son propre fichier (stage1.ps1, stage2.ps1...) et notez comment vous êtes passé de l’une à la suivante. Votre rapport a besoin de la chaîne, et une couche ultérieure réutilise parfois une clé ou une fonction utilitaire d’une couche antérieure.

D’abord le statique, ensuite l’exécution contrôlée

L’épluchage statique est la voie par défaut : vous voyez chaque étape, rien ne communique avec le réseau, et le décodeur fonctionne sur l’échantillon suivant de la même famille. Il échoue lorsque la clé d’une couche est dérivée à l’exécution (nom d’hôte, valeur de registre, réponse du serveur), ou lorsque l’émulation manuelle de l’obfuscation prend trop de temps.

L’exécution devient alors le meilleur outil, sous contrôle : le véritable hôte (powershell.exe, wscript.exe) dans la VM isolée, avec une sandbox ou des outils de surveillance qui enregistrent ce qui se passe, de faux services réseau qui répondent, et un instantané auquel revenir. C’est de l’analyse dynamique à proprement parler ; la leçon Surveillance comportementale du module 6 couvre l’outillage. Pour PowerShell, le laboratoire offre un avantage particulier, décrit ci-après : Windows lui-même journalisera les couches décodées pour vous.

Ce qu’enregistre la télémétrie Windows

Les malwares de script ont une faiblesse que les binaires n’ont pas : l’interpréteur appartient à Microsoft, et Microsoft l’a instrumenté. Quatre sources comptent.

SourceOùCe qu’elle capture
Journalisation des blocs de scriptMicrosoft-Windows-PowerShell/Operational, événement 4104Le texte de chaque bloc de script que PowerShell compile, y compris les blocs créés à l’exécution
Journalisation des modulesMême journal, événement 4103Détails de l’exécution du pipeline : commandes invoquées et leurs paramètres
AMSITransmis au produit antimalware installéLe contenu du script remis au scanner juste avant son exécution, depuis PowerShell, Windows Script Host, VBA d’Office et d’autres
Création de processusÉvénement Sysmon 1, ou événement de sécurité 4688 avec l’audit de la ligne de commandeLigne de commande complète, processus parent, hashs (Sysmon), utilisateur

La journalisation des blocs de script est celle que les analystes adorent. PowerShell journalise un bloc de script lorsqu’il le compile, et chaque Invoke-Expression ou [scriptblock]::Create() compile un nouveau bloc. Ainsi, lorsque la couche 1 décode la couche 2 et la passe à Invoke-Expression, la couche 2 est journalisée dans son propre événement 4104, sous la forme reçue par le moteur : après le décodage Base64, la décompression et la concaténation qui l’ont produite. L’obfuscation qui se produit à l’intérieur d’un bloc reste visible dans le texte de ce bloc, mais chaque couche que l’attaquant exécute arrive en clair dans le journal. Les grands blocs sont répartis sur plusieurs événements qui partagent un identifiant de bloc de script. La journalisation des blocs de script s’active par stratégie de groupe (« Turn on PowerShell Script Block Logging ») ; même sans cela, PowerShell 5 et versions ultérieures journalisent au niveau Warning les blocs qui contiennent des mots-clés suspects connus.

AMSI, l’Antimalware Scan Interface, repose sur le même principe : l’hôte transmet à l’antivirus, via amsi.dll, le contenu qu’il s’apprête à exécuter, si bien que le scanner voit l’argument d’eval plutôt que le fichier obfusqué. C’est pourquoi tant de scripts PowerShell malveillants commencent par un contournement d’AMSI, par exemple du code qui patche AmsiScanBuffer en mémoire. Des chaînes comme AmsiScanBuffer, amsiInitFailed ou System.Management.Automation.AmsiUtils dans un script sont un signal fort à elles seules et un bon matériau pour YARA.

Les événements de création de processus vous donnent le lanceur : la ligne exacte powershell.exe -NoP -W Hidden -Enc ..., son parent et l’utilisateur. C’est souvent tout ce que vous envoie un intervenant en réponse à incident, et le lab ci-dessous part d’une telle ligne.

Astuce : Dans la VM du laboratoire, activez la journalisation des blocs de script avant d’exécuter un échantillon PowerShell, puis lisez ensuite les événements 4104 avec l’Observateur d’événements ou Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational'. Vous obtenez chaque couche décodée, dans l’ordre, sans écrire un seul décodeur.

Extraire les IOC et rédiger le rapport

Une fois la dernière couche lisible, collectez :

  • Les indicateurs réseau : URL, domaines, adresses IP, user-agents et chemins d’URL. Les scripts transportent souvent plusieurs URL de repli ; prenez-les toutes.
  • Les indicateurs sur l’hôte : fichiers écrits ($env:TEMP\x.dll, %APPDATA%\...\update.vbs), clés de registre, noms de tâches planifiées, et la ligne de commande utilisée pour lancer l’étape suivante (rundll32.exe x.dll,Start, regsvr32 /s).
  • Les hashs des étapes suivantes : si le laboratoire a servi ou capturé le fichier téléchargé, hachez-le et triez-le comme un nouvel échantillon en suivant les étapes d’Identifier et hacher les fichiers.
  • Les clés et constantes utilisées par les décodeurs, qui identifient la famille ou la boîte à outils et constituent un bon matériau de signature.

Dans le rapport, en suivant la structure du rapport de triage, décrivez la livraison (pièce jointe, raccourci, macro), l’hôte (quel interpréteur et quel processus parent), la chaîne de couches (l’encodage de chaque couche et son hash, étiquetés comme artefacts dérivés), le comportement final et les IOC (indicateurs de compromission). Ajoutez des conseils de détection exploitables par les défenseurs : la paire parent-enfant, le motif de ligne de commande, le contenu 4104 à rechercher. Neutralisez chaque URL (hxxp://update[.]example[.]com/lab) pour que personne ne clique dessus depuis le rapport.

Lab : deux scripts épluchés sans exécuter la charge utile

Vous allez désobfusquer deux petits échantillons inoffensifs dont le seul comportement réel est d’afficher la chaîne de démonstration http://update.example.com/lab. Les sorties ci-dessous sont réelles, obtenues avec Node.js v25.7.0 et Python 3.14.7 sous macOS. PowerShell n’était pas installé sur cette machine : la couche PowerShell est donc décodée avec Python, et l’équivalent pwsh est décrit mais pas montré.

  1. Créez invoice_0923.js, un échantillon JavaScript qui utilise un tableau de codes de caractères, une inversion de chaîne et eval :

    js
    // invoice_0923.js: harmless lab sample
    var _0x4f = [59,41,117,40,103,111,108,46,101,108,111,115,110,111,99,32,59,34,98,97,108,47,109,111,99,46,101,108,112,109,97,120,101,34,32,43,32,34,46,101,116,97,100,112,117,47,47,58,112,116,116,104,34,32,61,32,117,32,114,97,118];
    var _0x9a = String.fromCharCode.apply(null, _0x4f);
    var _0x1c = _0x9a.split("").reverse().join("");
    eval(_0x1c);
  2. Créez cmdline.txt, une ligne de commande PowerShell telle qu’un EDR l’enregistrerait :

    text
    powershell.exe -NoP -W Hidden -Enc JABhACAAPQAgACcAVwByAGkAdABlAC0ATwB1AHQAJwAgACsAIAAnAHAAdQB0ACcAOwAgACQAYgAgAD0AIAAnACgAJwAnAGgAdAB0AHAAOgAvAC8AdQBwAGQAJwAnACAAKwAgACcAJwBhAHQAZQAuAGUAeABhAG0AcABsAGUALgBjAG8AbQAvAGwAYQBiACcAJwApACcAOwAgAEkAbgB2AG8AawBlAC0ARQB4AHAAcgBlAHMAcwBpAG8AbgAgACgAJABhACAAKwAgACcAIAAnACAAKwAgACQAYgApAA==
  3. Vérifiez que l’indicateur n’est pas visible, puis trouvez les points d’exécution :

    bash
    grep -c example invoice_0923.js cmdline.txt
    grep -nE 'eval|Function\(|Invoke-Expression|IEX|-Enc' invoice_0923.js cmdline.txt | cut -c1-90
    text
    invoice_0923.js:0
    cmdline.txt:0
    invoice_0923.js:5:eval(_0x1c);
    cmdline.txt:1:powershell.exe -NoP -W Hidden -Enc JABhACAAPQAgACcAVwByAGkAdABlAC0ATwB1AHQAJ

    Aucun des deux fichiers ne contient le domaine. Le point d’exécution JavaScript est eval, à la ligne 5. L’échantillon PowerShell n’a pas encore de point d’exécution visible : -Enc masque toute la commande.

  4. Lisez le JavaScript avant d’exécuter quoi que ce soit. Les lignes 2 à 4 ne font que construire une chaîne : fromCharCode transforme des nombres en caractères, puis split, reverse et join l’inversent. Rien ne touche au système de fichiers ni au réseau avant eval. Neutralisez le point d’exécution et vérifiez la modification :

    bash
    sed 's/^eval(/console.log(/' invoice_0923.js > invoice_0923.safe.js
    diff invoice_0923.js invoice_0923.safe.js
    text
    5c5
    < eval(_0x1c);
    ---
    > console.log(_0x1c);
  5. N’exécutez que la copie neutralisée (dans votre VM de laboratoire) :

    bash
    node invoice_0923.safe.js
    text
    var u = "http://update." + "example.com/lab"; console.log(u);

    Le texte affiché est la couche 2, sous forme de code source : une concaténation qui construit l’URL et l’affiche. Un véritable échantillon appellerait ici new ActiveXObject("MSXML2.XMLHTTP") au lieu de console.log. La couche 2 n’a pas d’autre point d’exécution, donc l’épluchage s’arrête ; la concaténation est assez simple pour être reconstituée à l’œil. Vous avez maintenant l’URL sans l’avoir exécutée.

  6. Essayez de décoder le blob PowerShell de manière naïve :

    bash
    awk '{print $NF}' cmdline.txt | base64 -d | head -c 60 | xxd | head -3
    text
    00000000: 2400 6100 2000 3d00 2000 2700 5700 7200  $.a. .=. .'.W.r.
    00000010: 6900 7400 6500 2d00 4f00 7500 7400 2700  i.t.e.-.O.u.t.'.
    00000020: 2000 2b00 2000 2700 7000 7500 7400 2700   .+. .'.p.u.t.'.

    Un octet sur deux est nul : c’est du texte UTF-16LE, exactement comme l’exige -EncodedCommand.

  7. Enregistrez decode_enc.py, qui décode l’argument en UTF-16LE, fusionne les concaténations 'a' + 'b' de littéraux entre apostrophes, puis traite chaque littéral comme du code potentiel pour la couche suivante :

    python
    import base64, re, sys
    
    # Tokens: a whole single-quoted literal ('' inside is an escaped quote),
    # a plus sign, or a run of anything else.
    TOKEN = re.compile(r"'(?:[^']|'')*'|\+|[^'+]+")
    
    def fold(code):
        """Join 'abc' + 'def' into 'abcdef', scanning left to right."""
        out = []
        for t in TOKEN.findall(code):
            t = t.strip() if not t.startswith("'") else t
            if not t:
                continue
            # top of the output stack is <literal> + : merge the two literals
            if t.startswith("'") and out[-2:-1] and out[-1] == "+" and out[-2].startswith("'"):
                out.pop()
                t = out.pop()[:-1] + t[1:]
            out.append(t)
        return " ".join(out)
    
    def literals(code):
        """Contents of each literal, with '' turned back into '."""
        return [t[1:-1].replace("''", "'") for t in TOKEN.findall(code) if t.startswith("'")]
    
    cmdline = open(sys.argv[1], encoding="utf-8").read().strip()
    
    # 1. The argument after -e / -enc / -EncodedCommand (PowerShell accepts prefixes).
    blob = re.search(r"-e[a-z]*\s+([A-Za-z0-9+/=]+)", cmdline, re.I).group(1)
    print(f"[+] Base64 argument: {len(blob)} chars")
    
    # 2. -EncodedCommand is Base64 of UTF-16LE text, not UTF-8.
    layer1 = base64.b64decode(blob).decode("utf-16le")
    print("[+] layer 1, decoded:\n    " + layer1)
    print("[+] layer 1, folded:\n    " + fold(layer1))
    
    # 3. A literal may itself be code for Invoke-Expression: unescape and fold again.
    print("[+] layer 2, literals folded:")
    urls = set()
    for s in literals(fold(layer1)):
        inner = fold(s)
        print("    " + inner)
        urls.update(re.findall(r"https?://[^\s'\")]+", inner))
    print("[+] URLs:", sorted(urls))

    Le découpeur en jetons consomme chaque littéral en entier depuis son apostrophe ouvrante, car '' à l’intérieur est une apostrophe échappée. Une expression régulière de recherche-remplacement plus simple fusionnait des fragments à l’intérieur de $b et corrompait l’URL. Les décodeurs échouent en silence : comparez toujours leur sortie à l’entrée, à l’œil.

  8. Exécutez-le :

    bash
    python3 decode_enc.py cmdline.txt
    text
    [+] Base64 argument: 296 chars
    [+] layer 1, decoded:
        $a = 'Write-Out' + 'put'; $b = '(''http://upd'' + ''ate.example.com/lab'')'; Invoke-Expression ($a + ' ' + $b)
    [+] layer 1, folded:
        $a = 'Write-Output' ; $b = '(''http://upd'' + ''ate.example.com/lab'')' ; Invoke-Expression ($a + ' ' + $b)
    [+] layer 2, literals folded:
        Write-Output
        ( 'http://update.example.com/lab' )
    
    [+] URLs: ['http://update.example.com/lab']

    La couche 1 construit un nom de commande et un argument à partir de fragments et les passe à Invoke-Expression. La chaîne $b contient la couche 2 sous forme de code source, avec sa propre concaténation ; la fusionner une fois de plus révèle l’URL. La ligne vide correspond au littéral séparateur ' '. Rien n’a été exécuté à aucun moment.

  9. Si PowerShell est disponible (dans la VM du laboratoire, pwsh sur n’importe quelle plateforme ou Windows PowerShell), le même épluchage utilise l’interpréteur lui-même pour les parties inoffensives. Décodez l’argument avec [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($blob)) (Unicode est le nom que .NET donne à UTF-16LE) et enregistrez-le sous layer1.ps1. Lisez-le, vérifiez que seules des opérations sur des chaînes précèdent le point d’exécution, remplacez Invoke-Expression par Write-Output et exécutez la copie modifiée avec pwsh -NoProfile -File layer1.ps1. Elle doit afficher la couche 2, Write-Output ('http://upd' + 'ate.example.com/lab'), sous forme de texte. Si vous exécutez plutôt la ligne de commande d’origine dans une VM Windows de laboratoire où la journalisation des blocs de script est activée, l’événement 4104 enregistre à la fois la couche 1 et le bloc créé par Invoke-Expression.

  10. Collez l’argument Base64 dans CyberChef, appliquez From Base64 puis Decode text (UTF-16LE (1200)), et comparez avec l’étape 8.

Questions : Pourquoi grep ne trouve-t-il ni le domaine ni Invoke-Expression dans cmdline.txt ? À l’étape 4, quelles lignes auriez-vous dû neutraliser si la ligne 3 avait été new ActiveXObject("WScript.Shell").Run(...) au lieu d’une opération sur des chaînes ? Si l’échantillon avait utilisé "{1}{0}" -f 'ssion','Invoke-Expre' au lieu de 'Invoke-Expre' + 'ssion', comment faudrait-il modifier decode_enc.py ? Quels événements 4104 un défenseur verrait-il si cette ligne de commande s’exécutait sur une machine où la journalisation des blocs de script est activée, et que contiendrait chacun d’eux ? Rédigez la section IOC d’un rapport pour ces deux échantillons, avec l’URL neutralisée.

À retenir

  • Les malwares de script s’exécutent sur des hôtes livrés avec chaque machine Windows : powershell.exe, wscript.exe et cscript.exe pour JScript et VBScript, et mshta.exe pour les applications HTML. Le processus parent fait partie des éléments de preuve.
  • L’obfuscation change la façon dont les chaînes sont écrites (concaténation, codes de caractères, Base64, chaînes de format, bruit d’échappement, renommage) ; un point d’exécution tel qu’eval ou Invoke-Expression transforme le résultat en couche suivante.
  • -EncodedCommand est du Base64 d’UTF-16LE. Décodez-le en UTF-16LE, dans CyberChef ou en trois lignes de Python.
  • Épluchez en remplaçant le point d’exécution par un affichage, après avoir vérifié que rien avant lui n’agit sur le système, et recommencez jusqu’à ce que le code cesse de décoder du code. Décodez statiquement chaque fois que c’est possible, et n’exécutez quoi que ce soit que dans le laboratoire.
  • La journalisation des blocs de script (événement 4104) enregistre chaque bloc de script au moment où PowerShell le compile, si bien que chaque couche passée à Invoke-Expression apparaît en clair ; AMSI donne la même vue à l’antimalware, ce qui explique que les attaquants cherchent à le désactiver.
  • Rapportez la livraison, l’hôte, la chaîne de couches avec les hashs, le comportement final et les IOC neutralisés, ainsi que les motifs de ligne de commande et de processus parent que les défenseurs peuvent rechercher.