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 :
| Index | Répertoire | L’adresse est… | Intérêt pour l’analyste |
|---|---|---|---|
| 2 | Resource | RVA | Icônes, boîtes de dialogue, informations de version — et charges utiles embarquées |
| 4 | Certificate (Security) | Offset de fichier | Signature Authenticode |
| 9 | TLS | RVA | Callbacks exécutés avant le point d’entrée |
| 14 | CLR Runtime Header | RVA | Signale 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 :
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 bytesChaque 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 :
| Type | ID | Usage normal | Vu dans les malwares comme… |
|---|---|---|---|
RT_ICON / RT_GROUP_ICON | 3 / 14 | Icône de l’application | Icônes volées (PDF, Word) pour tromper l’utilisateur |
RT_RCDATA | 10 | Données binaires arbitraires | Configuration chiffrée, charge utile de second niveau |
RT_VERSION | 16 | Informations de version du fichier/produit | Usurpation d’un éditeur légitime |
RT_MANIFEST | 24 | Paramètres side-by-side et UAC | Demandes requireAdministrator |
RT_STRING | 6 | Chaînes localisées | Rarement intéressant |
| Types nommés personnalisés | — | Propres à l’application | N’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
MZest 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
InternalNamestable fait une bonne condition YARA. - Comme signal d’alarme. Un fichier qui prétend
CompanyName: Microsoft Corporationalors qu’il n’est pas signé, qu’il a été compilé avec MinGW et qu’il se trouve dans%APPDATA%ment. UnOriginalFilenamediffé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 :
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.
-
Créez
config.txt:text server=update.example.com interval=3600 mode=demo -
Créez
res.rc, qui embarque le fichier en tant queRT_RCDATAet 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 -
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; } -
Compilez-le.
-ssupprime 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 -
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 -
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 -
Si vous disposez d’une VM Windows, ouvrez
resdemo-overlay.exedans 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_RCDATAou à 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.