Leçon 3.1 · Triage statique· 40 min
Identifier et hacher les fichiers
Identifiez un fichier par ses magic bytes plutôt que par son nom, puis prenez son empreinte avec des hashs cryptographiques, d’imports, flous et Rich.
Objectifs
- Identifier le type réel d’un fichier à partir de ses magic bytes avec file, TrID et Detect It Easy
- Expliquer ce que prouvent les hashs cryptographiques, et pourquoi un seul octet modifié les rend inutiles pour mesurer la similarité
- Utiliser imphash, ssdeep, TLSH et le hash du Rich header pour regrouper des échantillons apparentés
- Effectuer des recherches de réputation par hash sans divulguer de fichiers confidentiels
- Tenir un journal de triage sur lequel d’autres analystes peuvent s’appuyer
Le triage commence par deux questions qui semblent triviales : quel est ce fichier, et l’avons-nous déjà vu ? Si vous vous trompez sur la première, vous ouvrez un script dans un parseur PE, ou un exécutable dans un lecteur PDF. Si vous négligez la seconde, vous passez une journée à analyser par rétro-ingénierie quelque chose qu’un collègue a terminé le mois dernier.
Cette leçon ouvre le module 3. Vous connaissez désormais les formats PE et ELF de l’intérieur ; à partir d’ici, vous appliquez ces connaissances rapidement, dans l’ordre que suit un vrai triage. D’abord l’identité : le type réel du fichier, puis un ensemble de hashs qui répondent chacun à une question différente.
Les noms et les extensions mentent
Le nom et l’extension du fichier sont choisis par celui qui l’a envoyé. Windows se sert de l’extension pour décider quoi faire lors d’un double-clic, et c’est précisément pour cela que les attaquants en abusent :
invoice.pdf.exe, avec l’option « Masquer les extensions des fichiers dont le type est connu » activée, s’affiche commeinvoice.pdf.- Un caractère de forçage droite-à-gauche (U+202E) inséré dans
report[U+202E]fdp.exeamène l’Explorateur à afficher le nomreportexe.pdf. - Un PE renommé en
update.datouthumbs.dbne s’exécutera pas sur un clic, mais un loader peut toujours le mapper. L’extension ne dit rien du contenu.
D’où la première règle du triage : ignorez le nom, lisez les octets. Consignez le nom d’origine dans vos notes comme un élément de preuve, jamais comme un fait sur le fichier.
Magic bytes
La plupart des formats binaires commencent par une signature fixe, le nombre magique (magic number). Vous en avez déjà rencontré deux : MZ pour les fichiers PE (En-têtes PE) et \x7fELF (ELF pour les analystes de malware). Voici ceux que vous verrez le plus souvent lors du triage de malwares (logiciels malveillants) :
| Format | Premiers octets (hex) | ASCII | Remarques |
|---|---|---|---|
| Exécutable PE / DOS | 4D 5A | MZ | À confirmer avec PE\0\0 à e_lfanew |
| ELF | 7F 45 4C 46 | .ELF | L’octet 4 donne 32/64 bits, l’octet 5 le boutisme |
| ZIP (et DOCX, XLSX, JAR, APK) | 50 4B 03 04 | PK.. | Office Open XML est un ZIP ; regardez à l’intérieur |
25 50 44 46 2D | %PDF- | Les lecteurs tolèrent des données parasites avant | |
| Fichier composé OLE | D0 CF 11 E0 A1 B1 1A E1 | Anciens .doc/.xls/.ppt, .msi | |
| 7-Zip | 37 7A BC AF 27 1C | 7z..'. | |
| RAR | 52 61 72 21 1A 07 | Rar!.. | Suivi de 00 (v4) ou 01 00 (v5) |
| Raccourci Windows (LNK) | 4C 00 00 00 01 14 02 00 | L... | Taille de l’en-tête, puis le CLSID du shell link |
| gzip | 1F 8B | Courant pour les charges utiles Linux | |
| Cabinet | 4D 53 43 46 | MSCF |
Une signature est nécessaire, pas suffisante. PK vous dit « conteneur ZIP », pas s’il s’agit d’un document Word, d’une archive Java ou d’une application Android. Et de nombreux formats, comme les scripts, les fichiers HTA, les fichiers batch et PowerShell, n’ont aucun nombre magique : ce sont simplement du texte.
Attention : Les parseurs sont souvent plus tolérants que ne le laissent penser les signatures. Les lecteurs PDF acceptent un en-tête qui n’est pas à l’offset 0, et les outils d’archivage trouvent un ZIP dont le répertoire central se trouve à la fin d’un autre fichier. Un fichier peut être à la fois un GIF valide et un ZIP valide (un « polyglotte »). Si une vérification de type et le comportement d’un outil divergent, ne vous fiez à aucun des deux tant que vous n’avez pas regardé les octets.
file et libmagic
Sous Linux et macOS, file compare le contenu à la base de signatures et de tests structurels de libmagic :
cp hello.exe invoice.pdf
file invoice.pdf
xxd -l 16 invoice.pdfinvoice.pdf: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
00000000: 4d5a 9000 0300 0000 0400 0000 ffff 0000 MZ..............L’extension dit PDF, les octets disent PE32+. file -z regarde à l’intérieur des fichiers compressés, et file -k continue après la première correspondance, ce qui aide avec les polyglottes.
TrID et Detect It Easy
Sous Windows, deux outils font le même travail avec des points forts différents :
- TrID évalue un fichier par rapport à des milliers de définitions communautaires et affiche les candidats classés avec des pourcentages. Il est utile pour les formats obscurs que libmagic ne connaît pas.
- Detect It Easy (DIE) va plus loin pour les exécutables : au-delà du format, ses signatures nomment le compilateur, l’éditeur de liens, le packer ou le protecteur, comme MSVC, mingw-w64, Go, .NET, UPX ou Themida, et il affiche l’entropie par section. Pour un PE, DIE est généralement le premier outil à ouvrir. Son volet packer fait l’objet d’une leçon dédiée, Détecter les packers et l’entropie.
Tous trois sont heuristiques. Lorsqu’ils divergent, ouvrez une vue hexadécimale et vérifiez la signature vous-même.
Hashs cryptographiques : l’identité
Un hash cryptographique associe à n’importe quelle entrée un condensé de taille fixe. Deux propriétés comptent pour un analyste :
- Identité. En pratique, un même condensé signifie les mêmes octets : un hash nomme donc un fichier de façon unique et exacte. C’est pourquoi les hashs sont la clé primaire de toute base d’échantillons, de tout rapport de sandbox (bac à sable) et de tout flux d’IOC (indicateurs de compromission).
- Effet avalanche. Changez un seul bit n’importe où et environ la moitié des bits du condensé basculent. Le nouveau hash ne vous dit rien de la proximité entre les fichiers.
| Algorithme | Condensé | Statut | Où on le voit encore |
|---|---|---|---|
| MD5 | 128 bits | Collisions praticables depuis 2004 | Anciens flux d’IOC, consoles EDR, imphash |
| SHA-1 | 160 bits | Collision pratique démontrée en 2017 (SHAttered) | Git, outils historiques, VirusTotal |
| SHA-256 | 256 bits | Aucune attaque pratique connue | La norme à consigner et à partager |
Les collisions comptent lorsqu’un attaquant peut fabriquer deux fichiers ayant le même hash, par exemple pour faire placer un jumeau bénin sur liste blanche. Pour nommer des échantillons et partager des IOC, MD5 et SHA-1 fonctionnent encore, mais consignez SHA-256 comme identifiant de référence et ajoutez les autres, car de nombreux outils et flux les attendent encore.
Get-FileHash .\sample.bin -Algorithm SHA256
certutil -hashfile .\sample.bin SHA256sha256sum sample.bin # macOS: shasum -a 256 sample.binL’effet avalanche est aussi la limite. Un hash de fichier est un IOC parfait pour ce fichier exact et inutile pour le build suivant. Les opérateurs de malwares le savent : les générateurs (builders) qui inscrivent un identifiant de campagne unique dans chaque copie, ou qui ajoutent des octets aléatoires à l’overlay (voir Ressources et overlays), donnent à chaque victime un SHA-256 différent. C’est pourquoi les hashs se trouvent au bas de la « pyramide de la douleur » (pyramid of pain) : les changer ne coûte rien à l’attaquant.
Astuce : Hachez le fichier reçu et tout ce que vous en extrayez (le ZIP, le document qu’il contient, l’EXE déposé), et consignez la relation parent-enfant. Les équipes de réponse qui fouillent les postes ont besoin du hash du fichier qui atterrit réellement sur le disque, et c’est rarement la pièce jointe de l’e-mail.
Hashs de similarité : les parents
Pour trouver des fichiers apparentés, il faut des hashs qui changent un peu quand le fichier change un peu. Quatre sont utilisés au quotidien.
Imphash
Vous avez déjà rencontré l’imphash dans Imports, exports et l’IAT : un MD5 calculé sur la liste d’imports normalisée. Il ignore entièrement le code et les données, si bien qu’il survit aux chaînes modifiées, au code recompilé et aux données parasites ajoutées, tant que la liste d’imports et son ordre restent identiques. Il entre en collision pour les fichiers packés et pour les tables d’imports minuscules ou dominées par le runtime.
ssdeep : hachage par morceaux déclenché par le contexte
ssdeep (Kornblum, 2006) découpe le fichier en morceaux à des frontières choisies par le contenu, et non à des offsets fixes. Un hash glissant calculé sur une petite fenêtre mobile déclenche une frontière chaque fois que sa valeur remplit une condition qui dépend d’une taille de bloc. Chaque morceau est haché et réduit à un seul caractère Base64, et les caractères sont concaténés :
192:PFNFU39HUbMetCyMYUdHQNEmeyVxHk/wjE6zCPiMpnHHmyVAgsZguy0HLVDSDRmb:Pu9HXjyVxEpUCNzVBuy0HLuscq
│ │ │
│ └ one character per chunk at the block size └ same, at double block size
└ block sizeComme les frontières dépendent du contenu local, insérer des octets à un endroit ne décale que les morceaux voisins, et le reste de la signature reste inchangé. Comparer deux signatures donne un score de 0 à 100 fondé sur le nombre de modifications nécessaires pour transformer une chaîne en l’autre. Les limites :
- Les signatures ne sont comparables que si leurs tailles de bloc sont égales ou diffèrent d’un facteur deux. Des fichiers de tailles très différentes obtiennent toujours 0.
- Les petits fichiers produisent des signatures courtes et des scores instables.
- La compression, le chiffrement ou le packing réécrivent chaque octet : deux copies packées du même programme semblent donc sans rapport.
TLSH
TLSH, le Trend Micro Locality Sensitive Hash, adopte une autre approche. Il fait glisser une fenêtre de 5 octets sur le fichier, compte les triplets d’octets dans 128 compartiments, et encode le compte de chaque compartiment sous forme de quartile. Il ajoute un en-tête construit à partir de la longueur du fichier et d’une somme de contrôle. Comparer deux condensés TLSH donne une distance : 0 signifie quasi identique, et elle augmente sans plafond fixe à mesure que les fichiers divergent. Contrairement à ssdeep, TLSH compare des fichiers de tailles différentes et se laisse plus difficilement tromper par une réorganisation du contenu, ce qui explique que plusieurs grandes plateformes de threat intelligence l’indexent. Il a besoin d’au moins 50 octets suffisamment variés et renvoie TNULL sinon.
Le hash du Rich header
Pour les fichiers compilés avec MSVC, le Rich header enregistre la chaîne de compilation qui a produit chaque objet. Un hash de son contenu décodé regroupe les échantillons issus du même environnement de build, même entre projets différents. VirusTotal l’affiche, et pefile en calcule un avec pe.get_rich_header_hash(). mingw-w64, Go et Delphi ne produisent pas de Rich header, le hash est donc vide, et les attaquants peuvent copier ou falsifier l’en-tête.
Quel hash répond à quelle question
| Hash | Survit à | Casse sur | Idéal pour |
|---|---|---|---|
| SHA-256 | Rien | Toute modification | Identité exacte, IOC, déduplication |
| Imphash | Modifications du code et des données | Changements de la liste d’imports, packing | Même base de code, builds différents |
| ssdeep | Modifications locales, données ajoutées | Fort changement de taille, packing | Quasi-doublons, variantes de même taille |
| TLSH | Modifications, réorganisation, changement de taille | Packing, chiffrement | Regroupement à grande échelle |
| Rich hash | Tout sauf la chaîne de compilation | Chaînes non MSVC, falsification | Même machine de build |
Aucun hash de similarité n’est fiable à lui seul. Deux hashs indépendants qui concordent constituent une piste ; ajoutez des chaînes, du code ou une infrastructure en commun et cela devient un constat.
Recherches de réputation, en toute sécurité
Une fois les hashs en main, demandez-vous si le monde connaît déjà ce fichier : d’abord votre propre EDR et votre base d’échantillons, puis des services publics comme VirusTotal et MalwareBazaar. Recherchez par hash ; ne téléversez pas.
La différence compte. Une recherche par hash ne révèle presque rien : le condensé ne peut pas être inversé pour retrouver le fichier. Un téléversement livre le fichier entier, et sur la plupart des services publics il devient accessible aux autres abonnés, qui peuvent télécharger les fichiers soumis. Cela crée deux risques distincts :
- Fuite de données. Un document de phishing taillé pour votre organisation peut contenir de vrais noms, des noms de projets internes ou un tableur volé. Un malware qui embarque une configuration propre à la victime peut contenir votre domaine, vos paramètres de proxy, voire des identifiants. Le téléverser revient à publier tout cela à des inconnus.
- Alerter l’attaquant. Les opérateurs de campagnes ciblées surveillent les services publics pour y repérer leurs propres hashs. Le premier téléversement d’un implant unique leur apprend qu’ils ont été découverts, souvent avant que votre réponse à incident ne soit prête.
Attention : La même prudence s’applique aux URL et aux domaines que vous extrayez. Demander à un scanner public de « scanner » une URL l’amène à récupérer cette URL depuis sa propre infrastructure, ce que l’opérateur peut voir. Recherchez plutôt l’indicateur neutralisé (defanged) dans les données historiques, et suivez la politique de partage de votre organisation (par exemple le Traffic Light Protocol) avant que quoi que ce soit ne sorte de vos murs.
Si une recherche par hash ne renvoie rien, c’est aussi un résultat : l’échantillon est nouveau, ciblé ou fraîchement recompilé. Notez-le et poursuivez le reste du triage.
Tenir un journal de triage
Chaque fichier que vous manipulez obtient une ligne, consignée avant de commencer à l’expérimenter. Un tableur suffit ; c’est la discipline qui compte :
date sha256 (full) md5 / sha1 size type (magic + DIE) original name / source
imphash ssdeep tlsh rich parent sha256 lookup result + date
analyst notes (defanged indicators, next steps)Consignez la date de la recherche : « 0 détection » ne signifie pas la même chose le premier jour et un mois plus tard. Consignez le hash parent pour qu’une charge utile extraite puisse toujours être rattachée à l’e-mail ou à l’archive d’où elle provient. Et gardez le journal avec le dossier, pas dans votre tête, pour que l’analyste suivant puisse reprendre là où vous vous êtes arrêté.
Lab : quels hashs voient la famille ?
Vous allez compiler un programme bénin, en dériver de petites variantes et mesurer la réaction de chaque hash. Les commandes utilisent mingw-w64 sous Linux ou macOS ; n’importe quel Python 3.9+ convient pour le script.
-
Écrivez le programme de base :
c // triage.c - benign lab program #include <windows.h> #include <stdio.h> static const char *BANNER = "lab build A: checking for updates"; static const char *SERVER = "http://update.example.com/lab"; int main(void) { SYSTEMTIME st; GetLocalTime(&st); printf("%s\n", BANNER); printf("would contact %s at %02d:%02d\n", SERVER, st.wHour, st.wMinute); Sleep(100); return 0; } -
Compilez les variantes. Chacune diffère de
v1_base.exed’une seule manière contrôlée :bash x86_64-w64-mingw32-gcc -O0 -s -o v1_base.exe triage.c x86_64-w64-mingw32-gcc -O0 -s -o v1_again.exe triage.c # same source, rebuilt sed 's/lab build A/lab build B/' triage.c > triage_b.c x86_64-w64-mingw32-gcc -O0 -s -o v2_string.exe triage_b.c # one character changed x86_64-w64-mingw32-gcc -O2 -s -o v3_O2.exe triage.c # different optimisation x86_64-w64-mingw32-gcc -O0 -o v4_symbols.exe triage.c # not stripped sed 's/Sleep(100);/Sleep(100); printf("%lu\\n", GetTickCount());/' triage.c > triage_c.c x86_64-w64-mingw32-gcc -O0 -s -o v6_import.exe triage_c.c # one extra import cp v1_base.exe v5_flipped.exe printf '\x01' | dd of=v5_flipped.exe bs=1 \ seek=$(( $(wc -c < v1_base.exe) - 16 )) conv=notrunc # one byte near the end -
Installez les bibliothèques dans un environnement virtuel.
ppdeepest une implémentation de ssdeep en Python pur ;python-ssdeepfonctionne aussi si libfuzzy est installée.bash python3 -m venv venv && . venv/bin/activate pip install pefile ppdeep py-tlsh -
Calculez chaque hash et comparez-le à celui du build de base :
python # hashes.py - compare identity and similarity hashes across variants import sys, hashlib, pefile, ppdeep, tlsh def row(path): data = open(path, "rb").read() pe = pefile.PE(data=data) return {"file": path, "sha256": hashlib.sha256(data).hexdigest(), "imphash": pe.get_imphash(), "ssdeep": ppdeep.hash(data), "tlsh": tlsh.hash(data)} rows = [row(p) for p in sys.argv[1:]] base = rows[0] print(f"{'vs ' + base['file']:<18}{'sha256':>8}{'imphash':>9}{'ssdeep':>8}{'tlsh':>6}") for r in rows[1:]: same = lambda k: "same" if r[k] == base[k] else "diff" print(f"{r['file']:<18}{same('sha256'):>8}{same('imphash'):>9}" f"{ppdeep.compare(base['ssdeep'], r['ssdeep']):>8}" f"{tlsh.diff(base['tlsh'], r['tlsh']):>6}")bash python3 hashes.py v1_base.exe v1_again.exe v2_string.exe v5_flipped.exe \ v3_O2.exe v6_import.exe v4_symbols.exeUne exécution avec GCC 15 de mingw-w64 a produit le tableau ci-dessous. Vos chiffres varieront selon la version du compilateur, et les distances TLSH bougent de quelques points entre des builds par ailleurs identiques, mais la tendance devrait se maintenir. ssdeep est un score de similarité (plus il est élevé, plus les fichiers sont proches) ; TLSH est une distance (plus elle est faible, plus ils sont proches).
text vs v1_base.exe sha256 imphash ssdeep tlsh v1_again.exe diff same 100 1 v2_string.exe diff same 99 3 v5_flipped.exe diff same 100 1 v3_O2.exe diff same 65 32 v6_import.exe diff diff 71 48 v4_symbols.exe diff same 0 511 -
Expliquez
v1_again.exe. Même source, même compilateur, et pourtant un SHA-256 différent. Trouvez les octets qui diffèrent :bash cmp -l v1_base.exe v1_again.exeLes différences se trouvent dans le
TimeDateStampCOFF et leCheckSumde l’en-tête optionnel (voir En-têtes PE). Chaque build produit un nouveau hash de fichier, même sans aucune modification du code. -
Vérifiez le hash du Rich header. Exécutez
python3 -c "import pefile; print(repr(pefile.PE('v1_base.exe').get_rich_header_hash()))". Un build mingw renvoie une chaîne vide. Si vous disposez de MSVC, compileztriage.caveccldeux fois, depuis des versions différentes de Visual Studio, et comparez. -
Identifiez chaque variante avec
fileet, si vous avez une VM Windows, passez-les dans Detect It Easy et TrID. Renommez-en une en.pdfet vérifiez qu’aucun des outils ne s’en soucie. -
Commencez votre journal de triage avec une ligne par variante.
Questions : Pourquoi v4_symbols.exe obtient-il 0 avec ssdeep mais tout de même une distance TLSH finie ? Quel hash relierait encore v6_import.exe à la base si l’auteur l’avait aussi packé avec UPX ? Quels deux hashs publieriez-vous comme IOC pour cette « famille », et lesquels garderiez-vous en interne comme pivots de regroupement ? Qu’arriverait-il aux cinq hashs si le générateur ajoutait 4 Ko d’octets aléatoires à l’overlay ?
À retenir
- Identifiez les fichiers par leurs magic bytes et leur structure, jamais par leur nom ou leur extension ;
file, TrID et DIE sont des heuristiques que vous confirmez avec une vue hexadécimale. - SHA-256 nomme un fichier exact. Une recompilation, un horodatage ou un seul octet modifié donne un nouveau hash : les hashs de fichier sont donc des IOC précis mais fragiles.
- Imphash, ssdeep, TLSH et le hash du Rich header survivent chacun à des changements différents. Utilisez-en au moins deux ensemble pour regrouper, et sachez que le packing met en échec la plupart d’entre eux.
- Recherchez les hashs, ne téléversez pas les fichiers : les téléversements peuvent divulguer des données de victimes et avertir l’attaquant.
- Consignez chaque fichier avec ses hashs, son type, sa source, son parent et la date de recherche avant d’aller plus loin.