Hexter

Im Inneren von Binär-Dateiformaten: PNG-, ZIP- und Mach-O-Header erklärt

Jedes Dateiformat, das du täglich benutzt, Bild, Archiv, ausführbare Datei, hat eine bis aufs Byte definierte Struktur, und fast nichts von dieser Struktur ist durch die Werkzeuge sichtbar, mit denen du diese Dateien normalerweise öffnest. Die Vorschau zeigt dir ein Bild. Das Archivierungsprogramm zeigt dir entpackte Dateien. Keins von beiden zeigt dir die Bytes, die diese Ergebnisse möglich gemacht haben. Hier ist, wie drei gängige Formate darunter tatsächlich aussehen und wie du sie dir selbst ansiehst.

PNG: eine Folge benannter Chunks

Jede PNG-Datei beginnt mit denselben acht Bytes: 89 50 4E 47 0D 0A 1A 0A. Das sind keine Bilddaten, sondern eine Signatur, bewusst so gewählt, dass sie ein Byte enthält, das unbrauchbar wird, wenn die Datei versehentlich als Text übertragen wird (0x0A und 0x0D sind Zeilenumbruchzeichen, sodass eine kaputte Textmodus-Übertragung sie auf eine leicht erkennbare Weise beschädigt).

Nach der Signatur ist alles in einer PNG ein Chunk, und jeder Chunk folgt derselben vierteiligen Aufteilung:

Diagramm der Struktur eines PNG-Chunks: ein 4-Byte-Längenfeld, ein 4-Byte-Typfeld wie IHDR oder IDAT, die Chunk-Daten selbst und am Ende eine 4-Byte-CRC-Prüfsumme.
Jeder PNG-Chunk, vom ersten (IHDR) bis zum letzten (IEND), folgt derselben Aufteilung aus vier Feldern.
  • Länge (4 Bytes): wie viele Bytes an Daten folgen, ohne Typ oder CRC mitzuzählen.
  • Typ (4 Bytes, immer ASCII): ein Vier-Buchstaben-Code wie IHDR (Bild-Header, immer zuerst), IDAT (komprimierte Pixeldaten, es können mehrere sein) oder IEND (markiert das Ende der Datei).
  • Daten: was auch immer dieser Chunk-Typ vorgibt. Die Daten von IHDR sind Breite, Höhe, Bittiefe und Farbtyp, immer dieselbe feste Aufteilung.
  • CRC (4 Bytes): eine Prüfsumme über Typ und Daten, sodass ein Decoder einen beschädigten Chunk erkennen kann, statt ihn still falsch zu lesen.

Das ist das gesamte Format. Ein PNG-Decoder geht einfach Chunk für Chunk durch, liest die Länge, um zu wissen, wie weit er springen muss, bis er auf IEND trifft.

ZIP: lokale Header, dann ein Verzeichnis am Ende

Ein ZIP-Archiv speichert jede Datei mit ihrem eigenen lokalen Datei-Header, gekennzeichnet durch die Signatur 50 4B 03 04 (das PK am Anfang ist ein Verweis auf Phil Katz, den Schöpfer des Formats). Dieser Header enthält den Dateinamen, die Kompressionsmethode und sowohl die komprimierte als auch die unkomprimierte Größe, unmittelbar gefolgt von den komprimierten Daten der Datei.

Der Teil, der die Leute stolpern lässt: diese Größen und Prüfsummen pro Datei im lokalen Header sind manchmal null, mit den echten Werten später in einem „Data Descriptor" gespeichert, weil ZIP dafür entworfen wurde, Streaming-Schreibvorgänge zu unterstützen, bei denen die endgültige Größe erst bekannt ist, wenn die Daten bereits geschrieben wurden. Die maßgebliche Liste dessen, was tatsächlich im Archiv steckt, lebt in einem zentralen Verzeichnis ganz am Ende der Datei, was auch der Grund ist, warum manche beschädigten oder abgeschnittenen ZIPs so aussehen können, als hätten sie Inhalt, wenn du vom Anfang her scannst, sich aber nicht öffnen lassen, weil das Werkzeug, das du benutzt, zuerst das zentrale Verzeichnis liest und es fehlt oder beschädigt ist.

Mach-O: Magic Number, dann Load Commands

Mach-O ist das Format ausführbarer Dateien, das macOS für kompilierte Binärdateien verwendet. Es beginnt mit einer Magic Number, die einem Loader zwei Dinge auf einmal sagt: ob sie 32-Bit oder 64-Bit ist und welche Byte-Reihenfolge der Rest der Datei verwendet. 0xFEEDFACE ist 32-Bit, 0xFEEDFACF ist 64-Bit, und wenn du die Bytes umgekehrt siehst (0xCEFAEDFE oder 0xCFFAEDFE), heißt das, die Datei wurde in der entgegengesetzten Byte-Reihenfolge zu der Maschine geschrieben, die sie liest.

Nach der Magic Number kommt ein fester Header (CPU-Typ, Dateityp und eine Anzahl an Load Commands), gefolgt von genau so vielen Load Commands nacheinander. Load Commands sind, wie eine Mach-O-Datei alles beschreibt, was der Loader braucht: welche Segmente in den Speicher abgebildet werden, welche dynamischen Bibliotheken gelinkt werden, wo der Einstiegspunkt ist. Es ist eine ähnliche Idee wie bei PNGs Chunks: ein fester Header, dann eine Folge selbstbeschreibender Datensätze, von denen jeder dem Leser sagt, wie viel er überspringen muss, um zum nächsten zu gelangen.

Das dir selbst ansehen

Nichts davon erfordert, einen Parser zu schreiben. Öffne eine beliebige PNG-, ZIP- oder Mach-O-Datei in Hexter und der Analysieren-Tab erkennt das Format automatisch, geht die tatsächliche Chunk- oder Load-Command-Struktur durch und zeigt sie als Baum neben dem rohen Hex, sodass du auf ein Feld klicken und genau sehen kannst, aus welchen Bytes es stammt. Es ist standardmäßig schreibgeschützt, sodass das an einer echten Datei wirklich gefahrlos möglich ist, an einer App-Binärdatei, einem Screenshot, einem heruntergeladenen Archiv, ganz ohne Risiko, sie zu verändern.

Wenn du das Obige lieber von Hand überprüfen willst: öffne eine PNG und prüfe, ob die ersten acht Bytes zur Signatur passen, sieh dir dann die Bytes 8 bis 11 (die Länge des ersten Chunks) und die Bytes 12 bis 15 (seinen Typ, der IHDR lauten sollte) an. Die Übung ist klein genug, um sie in ein paar Minuten zu erledigen, und zugleich eine wirklich nützliche Art, ein Dateiformat zu verstehen, das du dein ganzes Leben lang benutzt, aber nie tatsächlich angeschaut hast.