Au cœur de MS-SHLLINK : une visite champ par champ du format binaire .lnk
· 6 min de lecture
Le format Shell Link de Windows est l'un de ces rares cas où ce que vous voyez dans Explorer est entièrement et publiquement spécifié. Microsoft le documente comme [MS-SHLLINK], Shell Link (.LNK) Binary File Format, et tout parser conforme le suit octet par octet. Ci-dessous se trouve la visite pratique, dans l'ordre dans lequel un parser lit réellement le fichier. Je signalerai les endroits où les parsers se trompent couramment et les champs auxquels un analyste DFIR devrait se soucier.
1. ShellLinkHeader (76 octets)
L'en-tête de taille fixe avec lequel chaque .lnk s'ouvre.
- HeaderSize, toujours
0x0000004C(76). Les quatre octets little-endian4C 00 00 00sont le test de reconnaissance Shell Link le plus fiable. Tout le reste n'est pas un.lnk, quoi que dise l'extension. - LinkCLSID,
00021401-0000-0000-C000-000000000046, l'id de classe COM pour les objets ShellLink. Stocké en little-endian-mélangé selon la convention COM ; les octets ne sont pas dans l'ordre que suggère la forme lisible par l'humain. - LinkFlags, 32 bits qui décident quelles sections optionnelles suivent. Bits qui valent la peine d'être connus au premier coup d'œil :
HasLinkTargetIDList,HasLinkInfo,HasName,HasRelativePath,HasWorkingDir,HasArguments,HasIconLocation,IsUnicode,ForceNoLinkInfo,HasExpString,RunInSeparateProcess,HasExpIcon. - FileAttributes, 32 bits reflétant les attributs NTFS de la cible (READONLY, HIDDEN, SYSTEM, DIRECTORY, ARCHIVE).
- CreationTime / AccessTime / WriteTime, trois valeurs FILETIME 64 bits. Tics de 100 ns depuis 1601-01-01 UTC. Instantanés pris à la création du lien ; ils ne se mettent pas à jour par la suite. Une discordance entre les FILETIMEs de l'en-tête et les métadonnées actuelles de la cible vous dit quand le lien lui-même a été écrit pour la première fois, qui est fréquemment l'horodatage le plus utile.
- FileSize, IconIndex, ShowCommand, HotKey, plus des octets réservés qui doivent être zéro.
ShowCommand = 7 (minimisé) sur un .lnk qui pointe vers cmd.exe est un fort indicateur de malware. Mérite d'être signalé tôt.
2. LinkTargetIDList (optionnel)
Présent si HasLinkTargetIDList. Une séquence préfixée par sa longueur d'enregistrements ItemID qui parcourt depuis un dossier shell racine (Bureau, Mon Ordinateur, Réseau) à travers chaque étape du namespace shell jusqu'à la cible. Chaque ItemID a son propre préfixe de longueur ; la liste se termine par un terminateur de longueur zéro.
L'encodage exact de chaque ItemID dépend du type de shell item, lecteur, entrée de système de fichiers, applet du panneau de configuration, partage réseau, et est partiellement opaque à la spec du format. LECmd d'Eric Zimmerman et liblnk de libyal implémentent tous deux les types de shell items courants ; lnkparse3 en couvre moins. Quand le parcours IDList décode proprement mais que LinkInfo est absent ou que ForceNoLinkInfo est activé, l'IDList est votre seul chemin vers la cible, et un décodage partiel peut encore produire une chaîne de chemin utilisable.
3. LinkInfo (optionnel)
Présent si HasLinkInfo et ForceNoLinkInfo est effacé. Porte les informations nécessaires pour résoudre la cible quand le parcours IDList échoue ou est absent.
- VolumeID, type de lecteur, numéro de série, étiquette de volume. Le serial est votre clé de corrélation des médias amovibles.
- LocalBasePath, le chemin absolu sur la machine d'origine. Contient généralement le nom d'utilisateur, qui est un signal doux d'attribution.
- CommonNetworkRelativeLink, chemin UNC et type de fournisseur réseau pour les raccourcis qui vivaient sur un partage réseau.
- CommonPathSuffix, la queue au-delà du LocalBasePath ou NetName.
Des variantes Unicode de chaque chemin existent aux côtés des variantes ANSI, présentes selon le drapeau de version à l'intérieur de l'en-tête LinkInfo. Un parser qui ne lit que les variantes ANSI représentera mal les chemins non-ASCII.
4. StringData (optionnel)
Une séquence de chaînes préfixées par leur longueur, présentes dans cet ordre si le LinkFlag correspondant est activé :
NAME_STRING → RELATIVE_PATH → WORKING_DIR → COMMAND_LINE_ARGUMENTS → ICON_LOCATION
Chaque chaîne est préfixée par sa longueur (little-endian 16 bits, en caractères, pas en octets) et encodée en UTF-16LE si IsUnicode est activé, sinon dans la code page ANSI système. L'ordre compte absolument. Un parser qui ne suit pas quels LinkFlags étaient activés lira mal chaque chaîne suivante. C'est le bug le plus courant dans les parsers LNK faits maison.
Pour DFIR : COMMAND_LINE_ARGUMENTS est là où vivent les arguments de la charge utile de phishing. ICON_LOCATION est là où vit le chemin de l'icône usurpée. WORKING_DIR révèle souvent les lettres de lecteur USB et les dossiers de staging.
5. Blocs ExtraData (optionnels, répétitifs)
Zéro ou plusieurs blocs ExtraData, chacun (size, signature, payload). La signature choisit le type de bloc :
| Signature | Bloc |
|---|---|
| 0xA0000001 | EnvironmentVariableDataBlock |
| 0xA0000002 | ConsoleDataBlock |
| 0xA0000003 | TrackerDataBlock |
| 0xA0000004 | ConsoleFEDataBlock |
| 0xA0000005 | SpecialFolderDataBlock |
| 0xA0000006 | DarwinDataBlock |
| 0xA0000007 | IconEnvironmentDataBlock |
| 0xA0000008 | ShimDataBlock |
| 0xA0000009 | PropertyStoreDataBlock |
| 0xA000000B | KnownFolderDataBlock |
| 0xA000000C | VistaAndAboveIDListDataBlock |
La liste se termine par un TerminalBlock de 4 octets (size < 0x4). Itérez, routez sur la signature, décodez la charge utile en conséquence.
Le célèbre en forensique est TrackerDataBlock. Il enregistre le nom NetBIOS de la machine d'origine et un GUID droid Distributed Link Tracking dérivé de l'adresse MAC. Le droid est un UUID v1 ; les six derniers octets sont la MAC. C'est de là que viennent les victoires d'attribution.
PropertyStoreDataBlock est un property store sérialisé avec des valeurs arbitraires indexées par GUID. Porte parfois un chemin faisant autorité que le LinkInfo n'a pas. EnvironmentVariableDataBlock se résout à l'exécution, un %TEMP% littéral est du bruit bénin ; un %TEMP%\evil.dll littéral ne l'est pas. DarwinDataBlock est le descripteur de l'installateur MSI ; il apparaît dans les raccourcis légitimes vers les applications installées par MSI et paraît bizarre si vous ne l'avez jamais vu auparavant.
En assemblant le tout
Un parser est une machine à états pilotée par LinkFlags. Chaque drapeau active ou désactive une section en aval. L'ordre n'est pas négociable. La spec est stricte sur les octets réservés-doivent-être-zéro, donc un seul bit de drapeau corrompu corrompt la longueur de chaque section suivante.
Parsers de référence à diff-tester contre votre sortie : LECmd d'Eric Zimmerman, liblnk de libyal, lnkparse3 et la Windows-LNK-Parsing-Library. Quand deux d'entre eux sont d'accord et un troisième est en désaccord, la spec l'emporte.
Pour voir tout cela sur un vrai fichier, déposez-en un dans le parser sur la page d'accueil, chaque champ ci-dessus est rendu explicitement.
Pour aller plus loin
- Microsoft, [MS-SHLLINK] sur Microsoft Learn, la spec faisant autorité.
- Qu'est-ce qu'un fichier .lnk Windows ?, introduction courte.
- Comment ouvrir un fichier .lnk, chemins d'inspection sûrs.
- Analyse forensique des fichiers .lnk, ce que les enquêteurs extraient de chaque bloc.
- Le parser Jumplist, pour les flux LNK incorporés à l'intérieur des fichiers
.automaticDestinations-mset.customDestinations-ms.