Zum Inhalt springen
← Zurück zum Blog

Innenansicht von MS-SHLLINK: eine Feld-für-Feld-Tour durch das .lnk-Binärformat

· 5 Min. Lesezeit

Das Windows-Shell-Link-Format ist einer dieser seltenen Fälle, in denen das, was Sie im Explorer sehen, vollständig und öffentlich spezifiziert ist. Microsoft dokumentiert es als [MS-SHLLINK], Shell Link (.LNK) Binary File Format, und jeder konforme Parser folgt ihm Byte für Byte. Unten ist die praktische Tour, in der Reihenfolge, in der ein Parser die Datei tatsächlich liest. Ich werde die Stellen markieren, an denen Parser häufig Fehler machen, und die Felder, auf die ein DFIR-Analyst achten sollte.

1. ShellLinkHeader (76 Bytes)

Der fest-große Header, mit dem jede .lnk beginnt.

  • HeaderSize, immer 0x0000004C (76). Das vier-Byte Little-Endian 4C 00 00 00 ist der zuverlässigste Shell-Link-Sniff-Test. Alles andere ist keine .lnk, was auch immer die Erweiterung sagt.
  • LinkCLSID, 00021401-0000-0000-C000-000000000046, die COM-Klassen-ID für ShellLink-Objekte. Gespeichert little-endian-vertauscht gemäß COM-Konvention; Bytes sind nicht in der Reihenfolge, die die menschenlesbare Form suggeriert.
  • LinkFlags, 32 Bits, die entscheiden, welche optionalen Abschnitte folgen. Bits, die es wert sind, auf den ersten Blick zu kennen: HasLinkTargetIDList, HasLinkInfo, HasName, HasRelativePath, HasWorkingDir, HasArguments, HasIconLocation, IsUnicode, ForceNoLinkInfo, HasExpString, RunInSeparateProcess, HasExpIcon.
  • FileAttributes, 32 Bits, die die NTFS-Attribute des Ziels spiegeln (READONLY, HIDDEN, SYSTEM, DIRECTORY, ARCHIVE).
  • CreationTime / AccessTime / WriteTime, drei 64-Bit-FILETIME-Werte. 100-ns-Ticks seit 1601-01-01 UTC. Momentaufnahmen, die bei der Linkerstellung gemacht wurden; sie aktualisieren sich danach nicht. Eine Abweichung zwischen den FILETIMEs des Headers und den aktuellen Ziel-Metadaten sagt Ihnen, wann der Link selbst zuerst geschrieben wurde, was häufig der nützlichere Zeitstempel ist.
  • FileSize, IconIndex, ShowCommand, HotKey, plus reservierte Bytes, die null sein müssen.

ShowCommand = 7 (minimiert) auf einer .lnk, die auf cmd.exe zeigt, ist ein starker Malware-Indikator. Es lohnt sich, früh zu markieren.

2. LinkTargetIDList (optional)

Vorhanden, wenn HasLinkTargetIDList. Eine längen-präfixierte Sequenz von ItemID-Datensätzen, die von einem Shell-Stammordner (Desktop, Mein Computer, Netzwerk) durch jeden Shell-Namespace-Schritt zum Ziel läuft. Jede ItemID hat ihren eigenen Längenpräfix; die Liste endet mit einem Längen-Null-Terminator.

Die genaue Codierung jeder ItemID hängt von der Art des Shell-Items ab, Laufwerk, Dateisystemeintrag, Systemsteuerungs-Applet, Netzwerkfreigabe, und ist teilweise undurchsichtig für die Format-Spezifikation. Eric Zimmermans LECmd und libyals liblnk implementieren beide die gängigen Shell-Item-Typen; lnkparse3 deckt weniger ab. Wenn der IDList-Walk sauber decodiert, aber LinkInfo fehlt oder ForceNoLinkInfo gesetzt ist, ist die IDList Ihr einziger Pfad zum Ziel, und eine teilweise Decodierung kann immer noch einen nutzbaren Pfadstring produzieren.

3. LinkInfo (optional)

Vorhanden, wenn HasLinkInfo und ForceNoLinkInfo gelöscht ist. Trägt die Informationen, die benötigt werden, um das Ziel aufzulösen, wenn der IDList-Walk fehlschlägt oder fehlt.

  • VolumeID, Laufwerkstyp, Seriennummer, Volumen-Label. Die Seriennummer ist Ihr Korrelationsschlüssel für Wechselmedien.
  • LocalBasePath, der absolute Pfad auf der ursprünglichen Maschine. Enthält normalerweise den Benutzernamen, was ein weiches Attributionssignal ist.
  • CommonNetworkRelativeLink, UNC-Pfad und Netzwerk-Provider-Typ für Verknüpfungen, die auf einer Netzwerkfreigabe lebten.
  • CommonPathSuffix, der Schwanz hinter LocalBasePath oder NetName.

Unicode-Varianten jedes Pfads existieren neben den ANSI-Varianten, vorhanden je nach Version-Flag im LinkInfo-Header. Ein Parser, der nur die ANSI-Varianten liest, wird Nicht-ASCII-Pfade falsch darstellen.

4. StringData (optional)

Eine Sequenz von längen-präfixierten Strings, vorhanden in dieser Reihenfolge, wenn das entsprechende LinkFlag gesetzt ist:

NAME_STRING → RELATIVE_PATH → WORKING_DIR → COMMAND_LINE_ARGUMENTS → ICON_LOCATION

Jeder String ist längen-präfixiert (16-Bit Little-Endian, in Zeichen, nicht Bytes) und als UTF-16LE codiert, wenn IsUnicode gesetzt ist, sonst als die System-ANSI-Codepage. Die Reihenfolge ist absolut wichtig. Ein Parser, der nicht verfolgt, welche LinkFlags gesetzt waren, wird jeden nachfolgenden String falsch lesen. Dies ist der häufigste Fehler in selbstgemachten LNK-Parsern.

Für DFIR: COMMAND_LINE_ARGUMENTS ist, wo die Phishing-Nutzlast-Argumente leben. ICON_LOCATION ist, wo der gefälschte Icon-Pfad lebt. WORKING_DIR offenbart oft USB-Laufwerksbuchstaben und Staging-Ordner.

5. ExtraData-Blöcke (optional, wiederholend)

Null oder mehr ExtraData-Blöcke, jeder (size, signature, payload). Die Signatur wählt den Blocktyp:

SignaturBlock
0xA0000001EnvironmentVariableDataBlock
0xA0000002ConsoleDataBlock
0xA0000003TrackerDataBlock
0xA0000004ConsoleFEDataBlock
0xA0000005SpecialFolderDataBlock
0xA0000006DarwinDataBlock
0xA0000007IconEnvironmentDataBlock
0xA0000008ShimDataBlock
0xA0000009PropertyStoreDataBlock
0xA000000BKnownFolderDataBlock
0xA000000CVistaAndAboveIDListDataBlock

Die Liste endet mit einem 4-Byte-TerminalBlock (size < 0x4). Iterieren, anhand der Signatur routen, die Nutzlast entsprechend decodieren.

Der berühmte in der Forensik ist TrackerDataBlock. Er zeichnet den NetBIOS-Namen der ursprünglichen Maschine und eine Distributed-Link-Tracking-Droid-GUID auf, abgeleitet von der MAC-Adresse. Die Droid ist eine v1-UUID; die letzten sechs Bytes sind die MAC. Das ist, woher die Attributionserfolge kommen.

PropertyStoreDataBlock ist ein serialisiertes Property Store mit beliebigen GUID-Schlüssel-Werten. Trägt manchmal einen autoritativen Pfad, den die LinkInfo nicht hat. EnvironmentVariableDataBlock wird zur Laufzeit aufgelöst, ein wörtliches %TEMP% ist harmloses Rauschen; ein wörtliches %TEMP%\evil.dll nicht. DarwinDataBlock ist der MSI-Installer-Deskriptor; er erscheint in legitimen Verknüpfungen zu MSI-installierten Anwendungen und sieht seltsam aus, wenn Sie ihn noch nie gesehen haben.

Alles zusammenfügen

Ein Parser ist eine Zustandsmaschine, die von LinkFlags gesteuert wird. Jedes Flag schaltet einen nachgelagerten Abschnitt ein oder aus. Die Reihenfolge ist nicht verhandelbar. Die Spezifikation ist streng bei reservierten-müssen-null-sein Bytes, sodass ein einziges korruptes Flag-Bit jede nachfolgende Abschnittslänge korrumpiert.

Referenz-Parser, die es wert sind, Ihre Ausgabe damit zu vergleichen: Eric Zimmermans LECmd, libyals liblnk, lnkparse3 und die Windows-LNK-Parsing-Library. Wenn zwei davon übereinstimmen und ein dritter widerspricht, gewinnt die Spezifikation.

Um all dies an einer echten Datei zu sehen, legen Sie eine in den Parser auf der Startseite, jedes obige Feld wird explizit gerendert.

Weiterführende Literatur