Skip to content

Leçon 2.4 · Formats binaires· 35 min

Ressources, overlays et autres cachettes

Où un PE transporte des données hors de son code : arbre des ressources, infos de version, overlays, signatures Authenticode, répertoires .NET et TLS.

Objectifs

  • Parcourir l’arbre des ressources à trois niveaux et reconnaître les types de ressources qui comptent pour le triage
  • Utiliser les informations de version comme métadonnées et IOC tout en sachant à quel point elles se falsifient facilement
  • Calculer où commence l’overlay d’un PE et expliquer pourquoi droppers et installateurs l’utilisent
  • Interpréter correctement une signature Authenticode, y compris pour les fichiers signés mais malveillants
  • Repérer les répertoires de données .NET et TLS et savoir ce que chacun implique pour l’étape suivante

Jusqu’ici, le format PE a paru bien rangé : les en-têtes décrivent les sections, les sections contiennent le code et les données, et la table des imports indique de quelles API le programme a besoin. Mais un fichier PE offre beaucoup de place pour des octets qui ne sont ni du code ni des données ordinaires. Icônes, chaînes de version, manifestes, certificats, archives entières collées à la fin du fichier. Les logiciels légitimes utilisent chacun de ces emplacements, les malwares (logiciels malveillants) aussi, car un second exécutable caché dans une « icône » ou ajouté après la dernière section est invisible pour quiconque ne regarde que .text.

Cette leçon couvre ces emplacements. Ils comptent surtout pendant le triage : trouver tôt une charge utile embarquée peut épargner des heures de rétro-ingénierie du code qui la dépaquette.

Retour sur les répertoires de données

Dans En-têtes PE, vous avez rencontré le tableau des répertoires de données à la fin de l’en-tête optionnel. Chaque entrée est une paire (address, size) qui pointe vers une structure consommée par le chargeur ou par d’autres composants de Windows. Quatre d’entre elles font l’objet de cette leçon :

IndexRépertoireL’adresse est…Intérêt pour l’analyste
2ResourceRVAIcônes, boîtes de dialogue, informations de version — et charges utiles embarquées
4Certificate (Security)Offset de fichierSignature Authenticode
9TLSRVACallbacks exécutés avant le point d’entrée
14CLR Runtime HeaderRVASignale un assembly .NET — changez d’outils

Notez l’intrus : l’« adresse » du répertoire de certificats est un offset de fichier brut, pas une RVA. La signature n’est jamais projetée en mémoire, donc une RVA n’aurait aucun sens. Les outils qui la convertissent aveuglément via la table des sections produisent des résultats absurdes.

L’arbre des ressources

Le répertoire de ressources (généralement dans une section nommée .rsrc) est un petit arbre à trois niveaux fixes :

text
  Resource directory (root)
  ├── Type        e.g. RT_ICON (3), RT_RCDATA (10), RT_VERSION (16), or a name "CONFIG"
  │   └── Name    numeric ID (#1) or string name ("PAYLOAD")
  │       └── Language   e.g. 0x0409 (en-US), 0x0000 (neutral)
  │           └── Data entry → RVA + size of the raw bytes

Chaque niveau est un IMAGE_RESOURCE_DIRECTORY suivi d’entrées qui sont soit un identifiant entier, soit un offset vers un nom Unicode. Les feuilles sont des enregistrements IMAGE_RESOURCE_DATA_ENTRY contenant la RVA et la taille des données elles-mêmes.

Les types que vous rencontrerez le plus souvent en triage :

TypeIDUsage normalVu dans les malwares comme…
RT_ICON / RT_GROUP_ICON3 / 14Icône de l’applicationIcônes volées (PDF, Word) pour tromper l’utilisateur
RT_RCDATA10Données binaires arbitrairesConfiguration chiffrée, charge utile de second niveau
RT_VERSION16Informations de version du fichier/produitUsurpation d’un éditeur légitime
RT_MANIFEST24Paramètres side-by-side et UACDemandes requireAdministrator
RT_STRING6Chaînes localiséesRarement intéressant
Types nommés personnalisés—Propres à l’applicationN’importe quoi ; des noms comme BIN, DATA, X

Les programmes lisent leurs propres ressources à l’exécution avec FindResource, LoadResource, LockResource et SizeofResource. Voir cette séquence dans les imports d’un petit binaire doté d’un gros blob RT_RCDATA est un indice fort que ce blob est la véritable charge utile — généralement déchiffrée ou décompressée en mémoire avant usage.

Astuce : Vérifiez l’entropie et les premiers octets de chaque ressource volumineuse. Une ressource qui commence par MZ est un exécutable embarqué ; une ressource d’entropie proche de 8,0 est compressée ou chiffrée. Detect It Easy et PE-bear affichent tous deux le détail de chaque ressource.

Informations de version : utiles, mais trivialement falsifiables

La ressource RT_VERSION contient une structure VS_VERSIONINFO : des numéros de version fixes, plus une table StringFileInfo avec des clés comme CompanyName, FileDescription, InternalName, OriginalFilename et ProductName.

Pour un analyste, elle vaut la peine d’être lue pour deux raisons opposées :

  • Comme IOC (indicateur de compromission) et indice de regroupement. Les builders réutilisent souvent les mêmes chaînes étranges tout au long d’une campagne — un nom de société mal orthographié ou un InternalName stable fait une bonne condition YARA.
  • Comme signal d’alarme. Un fichier qui prétend CompanyName: Microsoft Corporation alors qu’il n’est pas signé, qu’il a été compilé avec MinGW et qu’il se trouve dans %APPDATA% ment. Un OriginalFilename différent du nom sur disque mérite aussi d’être noté — il révèle souvent le nom utilisé par l’auteur.

Ne considérez jamais les informations de version comme une preuve d’origine. N’importe qui peut y écrire n’importe quelle chaîne avec un compilateur de ressources ou un éditeur comme Resource Hacker.

Les overlays : des données après la dernière section

Le chargeur ne projette que ce que décrit la table des sections. Tout ce qui est ajouté après la fin des données brutes de la dernière section est ignoré au chargement, mais fait toujours partie du fichier sur disque. Cette queue s’appelle l’overlay.

Calculer où elle commence est simple :

text
overlay_start = max(section.PointerToRawData + section.SizeOfRawData)
overlay_size  = file_size - overlay_start        (if positive)

Les usages légitimes des overlays sont courants : les installateurs (NSIS, Inno Setup) y ajoutent leurs archives compressées, les archives auto-extractibles font de même, et les fichiers signés y placent le blob Authenticode. Les usages malveillants en sont le reflet : les droppers y ajoutent une charge utile chiffrée, les builders un bloc de configuration, et certains échantillons se gonflent de centaines de mégaoctets de déchets pour dépasser les limites de taille des sandboxes (bacs à sable) et des scanners.

Un programme trouve son propre overlay en ouvrant son propre fichier (GetModuleFileName + CreateFile/ReadFile) et en se positionnant après les sections. Ce schéma d’auto-lecture dans un petit binaire est un autre indice de dropper.

Attention : Deux choses gonflent la taille apparente de l’overlay. La table des certificats d’un fichier signé se trouve après les sections, si bien qu’un calcul naïf la compte comme données d’overlay. Et certaines chaînes de compilation (MinGW sans -s) laissent une table de symboles COFF après la dernière section. Regardez toujours ce que l’overlay contient avant de conclure quoi que ce soit.

Authenticode : ce qu’une signature prouve et ne prouve pas

Le répertoire de certificats pointe vers une ou plusieurs structures WIN_CERTIFICATE contenant un blob PKCS#7 SignedData. La signature couvre un hash du fichier calculé en sautant le champ de somme de contrôle, l’entrée du répertoire de certificats et la table des certificats elle-même.

Ce que vous apprend une signature valide :

  • Le fichier n’a pas été modifié depuis sa signature (dans les plages hachées).
  • Le signataire détenait la clé privée de ce certificat au moment de la signature.

Ce qu’elle ne vous apprend pas : que le fichier est bénin. Les malwares signés sont une réalité récurrente — des certificats sont volés à de vraies entreprises, achetés via des sociétés écrans ou délivrés à des développeurs frauduleux. Traitez une signature comme un élément d’attribution à vérifier (qui est le signataire ? le certificat est-il révoqué ? le signataire est-il cohérent avec ce fichier ?), pas comme un verdict.

Sous Windows, sigcheck -a de Sysinternals ou l’onglet Signatures numériques du fichier affiche la chaîne de certification ; sur n’importe quelle plateforme, osslsigncode verify ou un script pefile peut extraire le blob pour l’inspecter.

Deux autres répertoires qui changent votre plan

Le répertoire TLS

Le répertoire TLS (index 9) décrit le stockage local aux threads — et, surtout, un tableau de callbacks TLS que le chargeur appelle avant l’exécution du point d’entrée. Un débogueur qui s’arrête au point d’entrée les a déjà manqués, c’est pourquoi les malwares les utilisent pour l’anti-débogage et l’unpacking (dépaquetage) précoce. Si ce répertoire est présent dans un petit binaire, listez ses callbacks avant toute autre chose. La page de technique callbacks TLS couvre les détails, et Comment un binaire est chargé et exécuté montre où ils s’insèrent dans la séquence du chargeur.

CLR Runtime Header (.NET)

Si le répertoire de données 14 n’est pas vide, le fichier est un assembly .NET. Son « vrai » code est du bytecode CIL accompagné de métadonnées, pas du x86. Le point d’entrée natif est un stub minuscule qui saute dans mscoree.dll. Arrêtez de lire du désassemblage : passez à un décompilateur .NET comme dnSpyEx ou ILSpy, qui reconstituent un code C# proche du source. Detect It Easy le signale immédiatement.

Lab : construire, cacher et retrouver

Ce lab construit un programme Windows inoffensif qui embarque une petite « config » texte comme ressource et transporte des données ajoutées sous forme d’overlay — les mêmes structures qu’utilise un dropper, sans rien de malveillant à l’intérieur. Il vous faut mingw-w64 (brew install mingw-w64 ou apt install mingw-w64) et Python avec pefile.

  1. Créez config.txt :

    text
    server=update.example.com
    interval=3600
    mode=demo
  2. Créez res.rc, qui embarque le fichier en tant que RT_RCDATA et ajoute des informations de version :

    c
    #include <windows.h>
    
    CONFIG RCDATA "config.txt"
    
    1 VERSIONINFO
    FILEVERSION     1,2,3,4
    PRODUCTVERSION  1,2,3,4
    FILEOS          VOS_NT_WINDOWS32
    FILETYPE        VFT_APP
    BEGIN
      BLOCK "StringFileInfo"
      BEGIN
        BLOCK "040904B0"
        BEGIN
          VALUE "CompanyName",      "Contoso Lab Tools"
          VALUE "FileDescription",  "Resource demo"
          VALUE "OriginalFilename", "resdemo.exe"
          VALUE "ProductName",      "Lab Resource Demo"
        END
      END
      BLOCK "VarFileInfo"
      BEGIN
        VALUE "Translation", 0x0409, 1200
      END
    END
  3. Créez resdemo.c, qui lit sa propre ressource à l’exécution :

    c
    #include <windows.h>
    #include <stdio.h>
    
    int main(void) {
        HRSRC h = FindResourceA(NULL, "CONFIG", MAKEINTRESOURCEA(10)); /* RT_RCDATA */
        if (!h) { puts("resource not found"); return 1; }
        DWORD size = SizeofResource(NULL, h);
        const char *data = LockResource(LoadResource(NULL, h));
        printf("embedded config (%lu bytes):\n%.*s", size, (int)size, data);
        return 0;
    }
  4. Compilez-le. -s supprime la table de symboles COFF pour qu’elle ne se fasse pas passer pour un overlay :

    bash
    x86_64-w64-mingw32-windres res.rc -O coff -o res.o
    x86_64-w64-mingw32-gcc -s -o resdemo.exe resdemo.c res.o
  5. Créez un overlay en ajoutant des octets :

    bash
    cp resdemo.exe resdemo-overlay.exe
    printf 'OVERLAY-MARKER:this-data-is-never-mapped' >> resdemo-overlay.exe
  6. Retrouvez tout avec un court script pefile, carve.py :

    python
    import sys
    import pefile
    
    pe = pefile.PE(sys.argv[1])
    
    # Resource tree: type -> name -> language
    for rtype in pe.DIRECTORY_ENTRY_RESOURCE.entries:
        tname = pefile.RESOURCE_TYPE.get(rtype.id, str(rtype.name))
        for rname in rtype.directory.entries:
            name = str(rname.name) if rname.name else f"#{rname.id}"
            for rlang in rname.directory.entries:
                rva, size = rlang.data.struct.OffsetToData, rlang.data.struct.Size
                print(f"{tname:<12} {name:<8} lang=0x{rlang.id:04x} rva=0x{rva:x} size={size}")
                if tname == "RT_RCDATA":
                    open(f"rsrc_{name}.bin", "wb").write(pe.get_data(rva, size))
    
    # Version strings
    for fileinfo in getattr(pe, "FileInfo", []):
        for entry in fileinfo:
            if entry.Key == b"StringFileInfo":
                for table in entry.StringTable:
                    for k, v in table.entries.items():
                        print(f"  {k.decode():<18} {v.decode()}")
    
    # Overlay
    end = max(s.PointerToRawData + s.SizeOfRawData for s in pe.sections)
    print(f"file size {len(pe.__data__)}, sections end at {end}")
    if pe.get_overlay_data_start_offset() is not None:
        open("overlay.bin", "wb").write(pe.get_overlay())
        print("overlay written to overlay.bin")
    bash
    python3 carve.py resdemo-overlay.exe
    cat rsrc_CONFIG.bin overlay.bin
  7. Si vous disposez d’une VM Windows, ouvrez resdemo-overlay.exe dans Resource Hacker, PE-bear et Detect It Easy, et retrouvez les trois mêmes artefacts dans chaque interface. Exécutez-le et vérifiez qu’il affiche la config embarquée — l’overlay n’a aucun effet sur l’exécution.

Questions : strings trouve-t-il le texte de la config dans l’exécutable ? Le trouverait-il si la ressource était encodée par XOR ? Quels imports de resdemo.exe vous mettraient sur la piste de la lecture de ses propres ressources ? Lancez carve.py sur le build compilé sans -s : que signale-t-il comme overlay, et pourquoi est-ce trompeur ?

À retenir

  • L’arbre des ressources a trois niveaux (type, nom, langue) ; les grosses ressources RT_RCDATA ou à nom personnalisé sont des candidates de choix pour héberger des charges utiles embarquées.
  • Les informations de version sont utiles pour regrouper des échantillons et repérer les usurpations, mais c’est du texte contrôlé par l’attaquant, jamais une preuve d’origine.
  • L’overlay commence à la fin des données brutes de la dernière section ; installateurs, droppers et signatures s’y trouvent tous, alors inspectez son contenu avant de juger.
  • Une signature Authenticode valide prouve l’intégrité et la possession de la clé, pas l’innocence.
  • Un répertoire TLS signifie que du code s’exécute avant le point d’entrée ; un en-tête CLR signifie que vous devez passer à un décompilateur .NET.