Skip to content

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

Documents et archives malveillants

Trier fichiers Office, RTF, PDF, archives, images disque et raccourcis LNK malveillants sans les ouvrir, et en extraire l’étape suivante et ses IOC.

Objectifs

  • Identifier le véritable format d’une pièce jointe à partir de ses octets magiques, quoi qu’en dise son extension
  • Localiser le contenu actif des fichiers Office : macros VBA et XLM, points d’entrée à exécution automatique, modèles distants, objets OLE intégrés et champs DDE
  • Lire la structure d’objets d’un PDF et y trouver les entrées /OpenAction, /JavaScript, /Launch, /URI et /EmbeddedFile avec pdfid et un analyseur PDF
  • Expliquer comment les fichiers ISO, VHD, les archives et les fichiers LNK servent à livrer des charges utiles, et comment les conteneurs ont affecté le Mark-of-the-Web
  • Extraire les URL de l’étape suivante, les noms des fichiers déposés et les hashs des objets intégrés, puis en tirer un rapport et une règle YARA

La plupart des malwares (logiciels malveillants) n’atteignent pas leur victime sous la forme d’un EXE nu. Ils arrivent déguisés en facture ou en avis d’expédition : un document Word, un PDF, un ZIP, une image disque ou un raccourci. Le fichier est rarement la charge utile. C’est un véhicule de livraison dont le seul rôle est de faire exécuter autre chose : une macro qui lance PowerShell, un lien qui récupère un modèle, un exécutable intégré, ou un exploit qui dépose du shellcode.

Cela détermine l’objectif de l’analyste. Face à une pièce jointe suspecte, vous voulez répondre à trois questions : quel est réellement ce format, où se trouve le contenu actif, et quelle est l’étape suivante (une URL, un fichier intégré, une ligne de commande) ? Et vous voulez y répondre sans ouvrir le fichier dans Word, Acrobat ou l’Explorateur, car l’ouvrir est précisément ce pour quoi l’attaquant l’a conçu. Chaque technique de cette leçon analyse le conteneur en tant que données.

Identifier d’abord le véritable format

Les extensions mentent, et les applications sont tolérantes. Word ouvrira sans broncher un RTF renommé en .doc, et Windows décide comment traiter un fichier d’après son extension, non d’après son contenu. Commencez par les premiers octets, comme dans Identifier et hacher les fichiers.

FormatOctets magiquesOùRemarques
Fichier composé OLE2 (.doc, .xls, .ppt, .msg)D0 CF 11 E0 A1 B1 1A E1Offset 0Un petit système de fichiers de stockages et de flux à l’intérieur d’un seul fichier
OOXML (.docx, .docm, .xlsx, .xlsm, .pptx)50 4B 03 04 (PK..)Offset 0Un ZIP de parties XML ; également JAR, APK et ZIP ordinaire
RTF{\rtfOffset 0Texte brut ; Word tolère des en-têtes abîmés comme {\rt
PDF%PDF-Près du débutLes lecteurs acceptent l’en-tête n’importe où dans les 1024 premiers octets
ISO 9660 (.iso, souvent .img)CD001Offset 0x8001Les 32 premiers Kio forment une zone système réservée, généralement remplie de zéros
VHD / VHDXconectix / vhdxfilePied de fichier (VHD) / offset 0 (VHDX)Monté par Windows d’un double-clic
Raccourci Windows (.lnk)4C 00 00 00 01 14 02 00Offset 0Taille d’en-tête 0x4C suivie du CLSID du shell link
OneNote (.one)E4 52 5C 7B 8C D8 A7 4DOffset 0Début du GUID du fichier de section ; les fichiers intégrés sont stockés en clair

file et Detect It Easy reconnaissent tous ces formats. Une discordance avec l’extension constitue en soi une découverte.

Le HTML smuggling n’a pas sa place dans le tableau, car il n’y a pas de format particulier : la pièce jointe est un fichier .html ordinaire. Son script contient une charge utile sous forme de chaîne Base64 ou de tableau d’octets, la transforme en Blob et déclenche un téléchargement avec URL.createObjectURL et un élément <a download>, si bien que le ZIP ou l’ISO est assemblé dans le navigateur et ne franchit jamais la passerelle de messagerie sous forme de fichier. Traitez-le comme un malware de script : trouvez le blob, décodez-le statiquement et triez ce qu’il contient.

Documents Office

Où vivent les macros

Le code VBA est stocké dans une structure OLE2 dans les deux générations de formats Office : le stockage Macros/VBA dans un .doc historique, _VBA_PROJECT_CUR/VBA dans un .xls. Un fichier OOXML prenant en charge les macros (.docm, .xlsm) transporte un fichier OLE complet comme membre ZIP word/vbaProject.bin ou xl/vbaProject.bin, si bien qu’un simple unzip -l vous dit si un projet de macros existe.

Chaque flux de module contient deux choses : le code source, compressé avec l’algorithme MS-OVBA, et le p-code, la forme compilée pour une version précise de VBA. Office exécute le p-code quand la version correspond et recompile à partir du source quand elle ne correspond pas.

Cette séparation rend possible le VBA stomping : l’attaquant écrase ou vide le source tout en conservant le p-code malveillant. olevba montre alors un source inoffensif ou vide, et pourtant Office, dans une version correspondante, exécute le p-code. olevba signale un stomping probable quand le source et le p-code divergent, et pcodedmp désassemble directement le p-code.

Les points d’entrée qui s’exécutent automatiquement

Une macro est inoffensive tant que rien ne l’appelle. Cherchez d’abord les procédures qu’Office exécute de lui-même :

HôteNoms à exécution automatique
WordAutoOpen, Document_Open, AutoClose, Document_Close, AutoExec, AutoNew, Document_New
ExcelAuto_Open, Workbook_Open, Workbook_Activate, Auto_Close, Workbook_BeforeClose
TousÉvénements de contrôles ActiveX comme InkPicture1_Painted ou *_Layout, utilisés pour éviter les noms évidents

À partir du point d’entrée, suivez les appels jusqu’aux points d’exécution (sinks) qui agissent sur le système : Shell, CreateObject("WScript.Shell"), WScript.Shell.Run, MSXML2.XMLHTTP, SaveToFile d’ADODB.Stream, Win32_Process.Create de WMI, et les instructions Declare qui importent des API Windows comme VirtualAlloc et CreateThread pour exécuter du shellcode dans le processus. Désobfusquez comme pour les scripts : décodez les chaînes statiquement et conservez chaque couche.

Macros Excel 4.0 (XLM)

Avant VBA, Excel disposait de feuilles de macros : des formules comme =EXEC(), =CALL() et =REGISTER() dans des cellules, exécutées à partir d’un nom défini Auto_Open. Les attaquants les ont ressuscitées vers 2020, car de nombreux outils et scanners les ignoraient. Les feuilles de macros sont souvent très masquées (very hidden, un état qu’on ne peut définir que par programmation), et les formules construisent des chaînes avec CHAR() et des références de cellules dispersées dans la feuille. olevba signale le XLM via son plugin BIFF, et XLMMacroDeobfuscator émule les formules pour retrouver les appels finaux. Microsoft désactive désormais les macros XLM par défaut.

Modèles distants et relations externes

Un document OOXML n’a pas besoin de macro pour en récupérer une. Les relations de chaque partie se trouvent dans un fichier _rels/*.rels, et une relation avec TargetMode="External" pointe hors du paquet. Quand word/_rels/settings.xml.rels contient une relation attachedTemplate vers une URL, Word télécharge ce modèle à l’ouverture du document, et le modèle peut transporter les macros. C’est l’injection de modèle distant (ATT&CK T1221) : la pièce jointe elle-même est propre, et la charge utile se trouve sur un serveur qui peut n’être activé que quelques heures. Le même mécanisme, avec une relation oleObject pointant vers une page HTML, a servi de voie de livraison à Follina (CVE-2022-30190). Toute cible externe dans une partie .rels est une URL d’étape suivante.

Objets OLE intégrés et DDE

Les documents peuvent intégrer d’autres fichiers sous forme d’objets OLE : un objet Package enveloppant un EXE, un script ou un LNK, avec une icône sur laquelle l’utilisateur est invité à double-cliquer, ou un objet d’une classe précise qu’un composant vulnérable va analyser. Ressources, overlays et autres cachettes faisait la même remarque à propos des PE : des conteneurs dans des conteneurs.

Les champs DDE (Dynamic Data Exchange) ont été détournés en 2017 comme moyen d’exécuter des commandes sans macro : un champ tel que DDEAUTO c:\\windows\\system32\\cmd.exe "/k ..." dans le corps du document demande à Office de lancer un programme lors de la mise à jour des champs. Microsoft a depuis désactivé par défaut les mises à jour DDE automatiques, mais d’anciens échantillons et des variantes CSV apparaissent encore.

La méthode oletools

Décrite ici plutôt qu’exécutée sur un échantillon réel :

  1. oleid sample.doc : un résumé des risques portant sur le format, le chiffrement, le VBA, le XLM et les relations externes.
  2. olevba --decode sample.doc > olevba.txt : le source VBA décompressé et un tableau de mots-clés (AutoExec, Suspicious, IOC). Lisez le tableau, puis le code à partir de la procédure à exécution automatique.
  3. oledump.py sample.doc liste chaque flux avec un index, en marquant les flux VBA d’un M ; oledump.py -s 8 -v sample.doc décompresse un module lorsque la sortie d’olevba est partielle.
  4. Si olevba signale du stomping, comparez le désassemblage du p-code produit par pcodedmp avec le source visible.
  5. oleobj -d out/ sample.doc extrait les objets intégrés (et liste les relations externes en OOXML) ; msodde sample.doc affiche les champs DDE. Hachez chaque objet extrait et triez-le comme un nouvel échantillon.

Attention : Les fichiers Office chiffrés cachent tout à ces outils. oleid indique Encrypted: True, et olevba essaie automatiquement le mot de passe par défaut de lecture seule VelvetSweatshop. Pour les autres mots de passe, que les e-mails d’hameçonnage fournissent souvent dans leur corps, déchiffrez une copie avec msoffcrypto-tool avant l’analyse.

RTF : objets et Éditeur d’équations

Le RTF est du texte : il ne peut donc pas contenir de VBA, mais ses mots de contrôle \object et \objdata intègrent des objets OLE en hexadécimal. L’analyseur est extrêmement tolérant : les mots de contrôle inconnus sont ignorés, les espaces et les déchets sont admis à l’intérieur des données hexadécimales, et les en-têtes peuvent être altérés. Les attaquants exploitent cette tolérance pour casser les signatures, c’est pourquoi vous devriez utiliser rtfobj plutôt qu’une expression régulière. Il liste chaque objet avec son nom de classe et en extrait les données brutes.

Le nom de classe qui a le plus compté est Equation.3. L’Éditeur d’équations de Microsoft, EQNEDT32.EXE, était un composant datant de 2000 qui s’exécutait dans son propre processus, sans les protections modernes comme l’ASLR. CVE-2017-11882 était un dépassement de tampon sur la pile dans son analyse des noms de polices, et CVE-2018-0802 un second dépassement découvert peu après. Un RTF contenant un objet Equation.3 suffisait à exécuter du shellcode sans aucune invite de macro, sur n’importe quelle version d’Office non corrigée, et ces exploits sont restés dans les campagnes grand public pendant des années après que Microsoft a supprimé le composant en janvier 2018. Quand rtfobj affiche un objet d’équation, extrayez-le, localisez le shellcode et poursuivez avec Analyse de shellcode.

Structure d’un PDF

Un PDF est un ensemble d’objets numérotés (7 0 obj ... endobj) qui se référencent les uns les autres (7 0 R), une table xref qui donne l’offset de chaque objet, et un trailer qui désigne le catalogue racine. Les objets sont des dictionnaires, des tableaux, des chaînes, des noms et des nombres ; les données volumineuses se trouvent dans des flux (streams), dont le dictionnaire nomme les filtres qui leur sont appliqués (/FlateDecode est du zlib ; /ASCIIHexDecode, /ASCII85Decode, /LZWDecode et /RunLengthDecode sont aussi courants, et les filtres peuvent s’enchaîner). Les flux compressés expliquent pourquoi grep ne trouve rien d’intéressant dans la plupart des PDF malveillants.

Les noms qui comptent :

NomSignification
/OpenAction, /AAUne action à exécuter à l’ouverture du document ou d’une page (actions supplémentaires)
/JavaScript, /JSAction JavaScript et son code, en ligne ou dans un flux
/LaunchLancer un programme externe ou ouvrir un fichier
/EmbeddedFileUn fichier joint à l’intérieur du PDF
/URI, /SubmitForm, /GoToROuvrir une URL, envoyer des données de formulaire, ouvrir un autre document
/ObjStmFlux d’objets : des objets stockés compressés à l’intérieur d’un autre flux
/Encrypt, /XFA, /RichMedia, /JBIG2DecodeChiffrement, formulaires XML, Flash, et un filtre lié à des exploits passés

Deux astuces d’évasion reviennent : les noms échappés en hexadécimal (/J#61vaScript vaut /JavaScript) et les objets cachés dans un /ObjStm, invisibles pour un scanner qui ne lit que le niveau supérieur. pdfid normalise les noms échappés et compte les /ObjStm ; un compte non nul signifie qu’il vous faut un analyseur capable de déplier les flux d’objets.

La plupart des PDF malveillants actuels sont des leurres d’hameçonnage avec un lien /URI ou un QR code, si bien que l’URL constitue souvent à elle seule toute la découverte. Avec les outils de Didier Stevens : pdfid pour le comptage des mots-clés, puis pdf-parser.py -s /OpenAction pour rechercher un nom, pdf-parser.py -o 7 -f pour afficher l’objet 7 avec ses filtres appliqués, et -d out.bin pour dumper le flux décodé. peepdf-3 offre les mêmes possibilités via des commandes comme object, stream et extract uri.

Conteneurs, archives et Mark-of-the-Web

Quand Windows enregistre un fichier provenant d’Internet, le navigateur ou le client de messagerie ajoute un flux de données alternatif NTFS nommé Zone.Identifier avec ZoneId=3. C’est ce Mark-of-the-Web (MotW) qui fait que SmartScreen avertit à propos des exécutables, qu’Office ouvre les documents en mode protégé et, depuis 2022, bloque purement et simplement les macros dans les documents provenant d’Internet.

Les attaquants ont réagi en enveloppant les charges utiles dans des conteneurs qui ne transmettaient pas la marque à leur contenu :

  • Les fichiers ISO, IMG et VHD se montent comme un lecteur d’un double-clic. Jusqu’à la mise à jour Windows de novembre 2022, les fichiers d’un ISO monté n’héritaient pas du MotW, si bien qu’un LNK ou un EXE qu’il contenait s’exécutait sans l’avertissement qu’un téléchargement direct aurait déclenché.
  • Les archives ZIP et 7z protégées par mot de passe mettent en échec l’analyse des passerelles, car le contenu est chiffré et le mot de passe se trouve dans le corps de l’e-mail. Certains extracteurs tiers ne propagent pas le MotW, ou seulement s’ils sont configurés pour le faire.
  • Les archives imbriquées (un ZIP dans un ISO dans un ZIP) épuisent les limites de récursion des scanners et multiplient les chances qu’une couche perde la marque.

Une fois que Microsoft a bloqué les macros provenant d’Internet en 2022, le couple ISO plus LNK, puis les fichiers OneNote contenant des scripts intégrés, sont devenus le mode de livraison privilégié des campagnes grand public. Pour l’analyse, extrayez avec 7-Zip, qui lit l’ISO, le VHD et la plupart des archives sans rien monter, triez chaque fichier qu’ils contiennent et consignez l’imbrication complète.

Les fichiers LNK comme lanceurs

Un raccourci n’a besoin d’aucun exploit. Il pointe vers un binaire légitime comme cmd.exe, powershell.exe, mshta.exe ou rundll32.exe, avec des arguments qui font le travail et une icône empruntée à un lecteur PDF ou à un dossier. Les arguments sont souvent complétés par des espaces pour que la boîte de dialogue Propriétés n’en montre qu’un début innocent.

Les champs à extraire, avec LnkParse3 (lnkparse sample.lnk, ou -j pour du JSON) :

  1. La cible : le chemin figurant dans les structures LinkTargetIDList et LinkInfo.
  2. Les arguments de ligne de commande issus de StringData : généralement la charge utile, souvent une commande PowerShell encodée à décoder comme dans la leçon sur les scripts.
  3. L’emplacement de l’icône et le répertoire de travail : ce que le leurre imite et l’endroit où il s’attend à trouver des fichiers d’accompagnement.
  4. Le bloc Tracker data dans ExtraData : le nom NetBIOS et une adresse MAC de la machine qui a créé le raccourci. Ils décrivent la machine de build de l’attaquant et permettent bien de regrouper les campagnes.
  5. Les données ajoutées après les structures LNK, que certains échantillons découpent avec findstr ou PowerShell.

Ce qu’il faut extraire et rapporter

Pour chaque document ou conteneur, collectez :

  • La chaîne de formats : chaque couche, du conteneur extérieur à la charge utile, avec son type réel et son SHA-256.
  • Le contenu actif : points d’entrée des macros, cellules XLM, JavaScript, champs DDE, classes des objets d’exploit, cible et arguments des LNK.
  • Les URL de l’étape suivante issues des macros, des cibles .rels, des actions PDF et des arguments LNK, neutralisées (hxxp://update[.]example[.]com/lab).
  • Les noms des fichiers déposés comme %TEMP%\invoice.dll, et la commande qui les exécute.
  • Les hashs des objets intégrés : le même objet OLE ou le même modèle revient souvent d’un leurre à l’autre avec un texte différent.
  • Les IOC des macros : clés de décodage, déclarations d’API, user-agents, métadonnées d’auteur, identifiants de machine des LNK.

Rédigez ensuite le tout selon la structure du rapport de triage.

Pour la détection, souvenez-vous de ce que voit YARA. La structure OLE et PDF est visible dans le fichier brut, donc uint32(0) == 0x46445025 (%PDF) avec des noms comme /OpenAction et /JavaScript fonctionne. Mais les parties OOXML sont compressées (deflate) dans le ZIP et le source VBA est compressé dans son flux, si bien que les chaînes que vous lisez dans la sortie d’olevba ne se trouvent généralement pas dans les octets du fichier. Appliquez ces règles aux parties extraites, comme le montre le lab, et décompressez avant d’analyser.

Lab : un PDF et un DOCX analysés sans visionneuse

Vous allez construire deux fichiers inoffensifs avec Python et trouver leur contenu actif uniquement avec des analyseurs. Le JavaScript du PDF ne fait qu’appeler app.alert("lab") ; le DOCX ne contient aucune macro ; tous deux pointent vers le domaine réservé example.com. Les sorties sont réelles, obtenues avec Python 3.14.7 sous macOS, oletools 0.60.2, pdfid 0.2.7, peepdf-3 6.0.0 et yara-python dans un environnement virtuel. pdf-parser n’étant pas sur PyPI, peepdf-3 joue son rôle.

  1. Créez un environnement isolé :

    bash
    python3 -m venv venv
    ./venv/bin/pip install oletools pdfid peepdf-3 yara-python
  2. Enregistrez make_pdf.py. Il écrit sept objets : un catalogue dont l’/OpenAction pointe vers une action JavaScript, une page avec une annotation de lien, et un flux /FlateDecode contenant le script :

    python
    # make_pdf.py: build a harmless lab PDF with an /OpenAction JavaScript and a /URI link
    import zlib
    
    js = zlib.compress(b'app.alert("lab");')
    objs = [
        b"<< /Type /Catalog /Pages 2 0 R /OpenAction 5 0 R >>",
        b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
        b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Annots [6 0 R] >>",
        b"<< /Length 0 >>\nstream\n\nendstream",
        b"<< /Type /Action /S /JavaScript /JS 7 0 R >>",
        b"<< /Type /Annot /Subtype /Link /Rect [0 0 612 792] /A << /S /URI /URI (http://update.example.com/lab) >> >>",
        b"<< /Length %d /Filter /FlateDecode >>\nstream\n" % len(js) + js + b"\nendstream",
    ]
    out = bytearray(b"%PDF-1.7\n%\xe2\xe3\xcf\xd3\n")
    offsets = []
    for n, body in enumerate(objs, start=1):
        offsets.append(len(out))
        out += b"%d 0 obj\n" % n + body + b"\nendobj\n"
    xref = len(out)
    out += b"xref\n0 %d\n0000000000 65535 f \n" % (len(objs) + 1)
    for off in offsets:
        out += b"%010d 00000 n \n" % off
    out += b"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (len(objs) + 1, xref)
    open("statement.pdf", "wb").write(out)
    print(f"wrote statement.pdf, {len(out)} bytes")
  3. Construisez-le et vérifiez ce qu’une simple recherche textuelle peut voir :

    bash
    ./venv/bin/python make_pdf.py
    grep -ac 'app.alert' statement.pdf
    grep -ao '/URI ([^)]*)' statement.pdf
    text
    wrote statement.pdf, 793 bytes
    0
    /URI (http://update.example.com/lab)

    L’URL est une chaîne non compressée et apparaît. Le script se trouve dans un flux compressé et n’apparaît pas.

  4. Lancez pdfid (le paquet pip l’installe en tant que module) et masquez les comptes nuls :

    bash
    ./venv/bin/python -m pdfid statement.pdf | grep -vE ' 0$'
    text
    PDFiD 0.2.7 statement.pdf
     PDF Header: %PDF-1.7
     obj                    7
     endobj                 7
     stream                 2
     endstream              2
     xref                   1
     trailer                1
     startxref              1
     /Page                  1
     /JS                    1
     /JavaScript            1
     /OpenAction            1

    /OpenAction avec /JavaScript est le couple classique : du code qui s’exécute à l’ouverture du fichier. pdfid ne compte pas /URI par défaut, une raison de plus de poursuivre avec un analyseur.

  5. Obtenez le résumé de peepdf-3 (-g désactive les couleurs, -f force l’analyse malgré les erreurs). Réduit à la section d’analyse :

    bash
    ./venv/bin/peepdf -g -f statement.pdf
    text
    Objects: 7
    Streams: 2
    URIs: 1
    ...
    	Streams (2): [4, 7]
    	Encoded (1): [7]
    	Objects with URIs (1): [6]
    	Objects with JS code (1): [7]
    	Suspicious elements (3):
    		/OpenAction (1): [1]
    		/JS (1): [5]
    		/JavaScript (1): [5]
  6. Suivez la chaîne du catalogue jusqu’au code, un objet à la fois :

    bash
    for c in "object 1" "object 5" "filters 7" "stream 7" "extract uri"; do
      ./venv/bin/peepdf -g -f -C "$c" statement.pdf
    done
    text
    << /Type /Catalog
    /Pages 2 0 R
    /OpenAction 5 0 R >>
    
    << /Type /Action
    /S /JavaScript
    /JS 7 0 R >>
    
    /FlateDecode
    
    app.alert("lab");
    
    http://update.example.com/lab 6

    L’objet 1 s’ouvre avec l’objet 5, une action JavaScript dont le code est l’objet 7, compressé. peepdf l’a décodé sans qu’aucun lecteur ne l’exécute. Avec pdf-parser : -s /OpenAction, puis -o 5, puis -o 7 -f.

  7. Enregistrez make_docx.py, qui écrit un paquet OOXML de six parties dont la relation des paramètres pointe vers un modèle distant :

    python
    # make_docx.py: build a harmless .docx whose settings point to a remote template
    import zipfile
    
    W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    PR = "http://schemas.openxmlformats.org/package/2006/relationships"
    
    parts = {
        "[Content_Types].xml": """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
    <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
    <Default Extension="xml" ContentType="application/xml"/>
    <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
    <Override PartName="/word/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/>
    </Types>""",
        "_rels/.rels": f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Relationships xmlns="{PR}">
    <Relationship Id="rId1" Type="{R}/officeDocument" Target="word/document.xml"/>
    </Relationships>""",
        "word/document.xml": f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <w:document xmlns:w="{W}"><w:body><w:p><w:r><w:t>Invoice attached. Please enable editing.</w:t></w:r></w:p></w:body></w:document>""",
        "word/_rels/document.xml.rels": f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Relationships xmlns="{PR}">
    <Relationship Id="rId1" Type="{R}/settings" Target="settings.xml"/>
    </Relationships>""",
        "word/settings.xml": f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <w:settings xmlns:w="{W}" xmlns:r="{R}"><w:attachedTemplate r:id="rId1"/></w:settings>""",
        "word/_rels/settings.xml.rels": f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Relationships xmlns="{PR}">
    <Relationship Id="rId1" Type="{R}/attachedTemplate" Target="http://update.example.com/lab/template.dotm" TargetMode="External"/>
    </Relationships>""",
    }
    
    with zipfile.ZipFile("invoice.docx", "w", zipfile.ZIP_DEFLATED) as z:
        for name, text in parts.items():
            z.writestr(zipfile.ZipInfo(name, date_time=(2026, 9, 29, 9, 0, 0)), text, zipfile.ZIP_DEFLATED)
    print("wrote invoice.docx")
  8. Construisez-le, confirmez le format et listez les parties :

    bash
    ./venv/bin/python make_docx.py
    xxd -l 16 invoice.docx
    file invoice.docx
    unzip -l invoice.docx
    grep -ac update.example invoice.docx
    text
    wrote invoice.docx
    00000000: 504b 0304 1400 0000 0800 0048 3d5d d765  PK.........H=].e
    invoice.docx: Microsoft Word 2007+
    Archive:  invoice.docx
      Length      Date    Time    Name
    ---------  ---------- -----   ----
          566  09-29-2026 09:00   [Content_Types].xml
          300  09-29-2026 09:00   _rels/.rels
          242  09-29-2026 09:00   word/document.xml
          289  09-29-2026 09:00   word/_rels/document.xml.rels
          263  09-29-2026 09:00   word/settings.xml
          350  09-29-2026 09:00   word/_rels/settings.xml.rels
    ---------                     -------
         2010                     6 files
    0

    PK et une arborescence word/ : de l’OOXML, sans vbaProject.bin. Un grep brut sur le domaine ne trouve rien, car chaque partie est compressée.

  9. Lisez les parties de relations directement depuis l’archive, sans les extraire sur le disque :

    bash
    unzip -p invoice.docx 'word/_rels/*.rels' | grep -o '<Relationship [^>]*TargetMode="External"[^>]*>'
    text
    <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/attachedTemplate" Target="http://update.example.com/lab/template.dotm" TargetMode="External"/>
  10. Laissez oletools parvenir à la même conclusion. Sortie débarrassée des bannières :

    bash
    ./venv/bin/oleid invoice.docx
    ./venv/bin/olevba invoice.docx
    ./venv/bin/oleobj invoice.docx
    text
    File format         |MS Word 2007+       |info      |
                        |Document (.docx)    |          |
    Container format    |OpenXML             |info      |Container type
    Encrypted           |False               |none      |The file is not encrypted
    VBA Macros          |No                  |none      |This file does not contain
                        |                    |          |VBA macros.
    XLM Macros          |No                  |none      |This file does not contain
                        |                    |          |Excel 4/XLM macros.
    External            |1                   |HIGH      |External relationships
    Relationships       |                    |          |found: attachedTemplate -
                        |                    |          |use oleobj for details
    
    Type: OpenXML
    No VBA or XLM macros found.
    
    Found relationship 'attachedTemplate' with external link http://update.example.com/lab/template.dotm

    olevba ne trouve à juste titre aucune macro ; un triage qui s’arrêterait là déclarerait le fichier propre. oleid classe la relation externe en HIGH et oleobj nomme l’URL du modèle : l’étape suivante.

  11. Écrivez une règle YARA pour la relation et vérifiez où elle correspond. Enregistrez remote_template.yar :

    text
    rule OOXML_Remote_Template_Rels
    {
        meta:
            description = "Relationship part that loads an Office template from a URL"
            scope = "extracted OOXML .rels parts"
        strings:
            $type = "/relationships/attachedTemplate" ascii
            $ext  = "TargetMode=\"External\"" ascii
            $url  = /Target="https?:\/\/[^"]{4,200}"/ ascii
        condition:
            filesize < 20KB and all of them
    }

    Puis exécutez-la sur les parties extraites et sur le DOCX lui-même :

    bash
    mkdir -p ext && (cd ext && unzip -oq ../invoice.docx)
    ./venv/bin/python - <<'EOF'
    import yara, pathlib
    r = yara.compile("remote_template.yar")
    for p in sorted(pathlib.Path("ext").rglob("*")):
        if p.is_file():
            m = r.match(str(p))
            if m: print(p, [x.rule for x in m])
    print("docx itself:", r.match("invoice.docx"))
    EOF
    text
    ext/word/_rels/settings.xml.rels ['OOXML_Remote_Template_Rels']
    docx itself: []

    Elle correspond à la partie extraite et manque le DOCX : la compression cache les chaînes.

Questions : Pourquoi grep a-t-il trouvé l’URL du PDF mais pas son JavaScript, et pourquoi n’a-t-il trouvé aucune des deux chaînes dans le DOCX ? Quelle ligne unique de pdfid vous ferait douter de ses autres comptes ? Si un attaquant remplaçait /JavaScript par /J#61vaScript dans l’objet 5, quels outils de ce lab le signaleraient encore ? Pourquoi une passerelle de messagerie qui ne vérifie que la présence de macros laisserait-elle passer invoice.docx, et que ferait Word à l’ouverture par un utilisateur ? Rédigez la section IOC d’un rapport pour les deux fichiers, avec les URL neutralisées et le SHA-256 de chaque fichier.

À retenir

  • Les documents et les archives sont des véhicules de livraison. Trouvez le contenu actif et l’étape suivante en analysant le fichier en tant que données, jamais en l’ouvrant.
  • Identifiez le format à partir des octets magiques : OLE2 D0 CF 11 E0, OOXML PK, RTF {\rtf, PDF %PDF-, ISO CD001 à 0x8001, LNK 4C 00 00 00.
  • Dans les fichiers Office, cherchez vbaProject.bin ou les stockages VBA, les noms à exécution automatique comme AutoOpen, Document_Open et Workbook_Open, les feuilles de macros XLM, les cibles .rels externes, les objets OLE intégrés et les champs DDE. Le VBA « stompé » cache son source.
  • Dans les PDF, suivez les références d’objets depuis /OpenAction ou /AA jusqu’à /JavaScript, /Launch, /URI ou /EmbeddedFile, en décodant les flux avec un analyseur plutôt qu’avec une visionneuse.
  • Les conteneurs (ISO, VHD, archives chiffrées et imbriquées) et les lanceurs LNK se sont répandus parce qu’ils échappaient au Mark-of-the-Web et au blocage des macros. Extrayez-les avec 7-Zip, analysez les arguments et les données Tracker des LNK, et consignez la chaîne complète.
  • Rapportez la chaîne de formats avec les hashs, le contenu actif, les URL d’étape suivante neutralisées et les fichiers déposés, et écrivez des règles YARA sur le contenu décompressé lorsque le conteneur compresse ce que vous voulez détecter.