🧱.doc – das binäre Word-Format
Eine .doc ist kein „Textdokument mit Formatierung“, sondern ein komplettes Dateisystem in einer Datei: Die äußere Hülle ist das Compound File Binary Format ([MS-CFB]), darin liegen Streams, deren Inhalt [MS-DOC] beschreibt. Alle Bytes auf dieser Seite stammen aus einer synthetischen, im Browser erzeugten Beispieldatei (7680 Byte) – mit echtem Header, echter FAT und echtem FIB.
🗄️Ein Dateisystem in der Datei
Datei-Offset Inhalt
0x0000 ┌───────────────────┐
│ Header (512 Byte) │ Signatur, Zeiger,
│ │ DIFAT[0..108]
0x0200 ├───────────────────┤
│ Sektor 0 │ (n + 1) × 512
0x0400 ├───────────────────┤
│ Sektor 1 │
0x0600 ├───────────────────┤
│ … │
└───────────────────┘
DIFAT ─► FAT-Sektoren ─► FAT[n] = nächster Sektor
Header ─► Verzeichnis (128-Byte-Einträge)
└─► Stream: Startsektor + Größe
Stream < 4096 B ─► Mini-Stream
(64-Byte-Minisektoren, Kette in der MiniFAT)- FFFFFFFF = FREESECT – frei
- FFFFFFFE = ENDOFCHAIN – Kettenende
- FFFFFFFD = FATSECT – Sektor gehört zur FAT
- FFFFFFFC = DIFSECT – Sektor gehört zur DIFAT
- 00000000 … FFFFFFFA – normale Sektornummern
🔬Sektor-Visualisierer: der Header
09 00 ist 9.🔬 Header (Offset 0x000–0x1FF)
… ab 0x60 folgt der Rest der DIFAT: 106 × FF FF FF FF (FREESECT) bis 0x1FF.
Magische Bytes D0 CF 11 E0 A1 B1 1A E1 – daran erkennt man jede CFB-Datei (.doc, .xls, .ppt, .msg, vbaProject.bin).
⛓️Sektoren, FAT und MiniFAT
🗺️ Sektorkarte der Beispieldatei (7680 Byte = Header + 14 Sektoren)
Klick auf einen Sektor zeigt seine ersten 64 Byte. Sektor n liegt bei (n + 1) × 512 – der Header ist gewissermaßen „Sektor −1“.
⛓️ Kette verfolgen
Start aus dem Verzeichniseintrag „WordDocument“ (+0x74). Start: Sektor 2.
Sektor 2 liegt bei Datei-Offset 0x0600 ((2 + 1) × 512). Weiter mit Sektor 3.
📋 FAT (Einträge 0–15)
📇Das Verzeichnis (Directory Entries)
| ID | Name | Typ | Farbe | L / R / Kind | Start | Größe | liegt in |
|---|---|---|---|---|---|---|---|
| 0 | Root Entry | Root Storage | ⚫ schwarz | – / – / 2 | 13 | 320 | FAT (Mini-Stream) |
| 1 | WordDocument | Stream | ⚫ schwarz | – / 4 / – | 2 | 4608 | FAT |
| 2 | 1Table | Stream | ⚫ schwarz | 3 / 1 / – | 0 | 33 | MiniFAT |
| 3 | Data | Stream | ⚫ schwarz | – / – / – | 1 | 128 | MiniFAT |
| 4 | \x05SummaryInformation | Stream | 🔴 rot | – / – / – | 3 | 100 | MiniFAT |
| 5 | (frei) | unbenutzt | – | – / – / – | – | – | – |
| 6 | (frei) | unbenutzt | – | – / – / – | – | – | – |
| 7 | (frei) | unbenutzt | – | – / – / – | – | – | – |
Die Einträge eines Storage sind als Rot-Schwarz-Baum verknüpft. Sortiert wird zuerst nach Namenslänge, dann nach Großbuchstaben: Data (4) < 1Table (6) < WordDocument (12) < \x05SummaryInformation (19). Der Präfix \x05 (Steuerzeichen 5) kennzeichnet Property-Set-Streams mit Metadaten wie Titel und Autor.
🔎 Eintrag 1: WordDocument – 128 Byte bei 0x0480
+0x00 Name (UTF-16LE): Bis zu 31 Zeichen plus Null-Terminator, UTF-16LE.
📚Streams in einem echten Word-Dokument
| Name | Inhalt |
|---|---|
| WordDocument | Pflicht. Beginnt mit dem FIB; enthält den Text und die Formatierungsseiten (FKPs). |
| 1Table / 0Table | Tabellen-Stream: Piece Table (CLX), Formatvorlagen (STSH), Schriftarten, Listen, Felder. Welcher der beiden gilt, sagt das FIB-Bit fWhichTblStm. |
| Data | Optional: Bilder und andere Binärdaten, auf die der Text verweist. |
| \x05SummaryInformation | Property Set mit Titel, Autor, Datum, Seitenzahl ([MS-OLEPS]). |
| \x05DocumentSummaryInformation | Weitere Eigenschaften: Firma, Kategorie, benutzerdefinierte Felder. |
| \x01CompObj | Name der Anwendung und Klassen-ID (z. B. „Microsoft Word-Dokument“, Word.Document.8). |
| Macros/ (Storage) | VBA-Projekt, wenn das Dokument Makros enthält ([MS-OVBA]). |
| ObjectPool/ (Storage) | Eingebettete OLE-Objekte, z. B. Excel-Tabellen – jedes wieder ein eigener Storage. |
🪪Der File Information Block (FIB)
WordDocument-Stream
0x000 ┌ FibBase (32 B) ─ wIdent, nFib, Flags
0x020 ├ csw = 14 ┐
0x022 ├ fibRgW ┘ 14 × 16 Bit
0x03E ├ cslw = 22 ┐
0x040 ├ fibRgLw ┘ 22 × 32 Bit (ccpText …)
0x098 ├ cbRgFcLcb = 93
0x09A ├ fibRgFcLcbBlob: 93 × (fc, lcb)
│ … fcStshf, fcPlcfBteChpx …
0x1A2 │ fcClx, lcbClx ─► Piece Table
0x382 └ cswNew (+ FibRgCswNew)
…
0x800 Text (hier: Piece 0, CP1252)
0xA00 Text (hier: Piece 1, UTF-16LE)| Offset | Feld | Bedeutung |
|---|---|---|
| 0x0000 | wIdent | 0xA5EC – Kennung eines Word-Binärdokuments |
| 0x0002 | nFib | 0x00C1 = Word 97 (neuere Versionen tragen nFibNew in FibRgCswNew: 0x00D9 Word 2000, 0x0101 Word 2002, 0x010C Word 2003, 0x0112 Word 2007) |
| 0x0006 | lid | Sprach-ID der Installation, z. B. 0x0407 = Deutsch (Deutschland) |
| 0x000A | Flags | fDot (Vorlage), fComplex (schnellgespeichert), fEncrypted, fWhichTblStm (Bit 9), fObfuscated … |
| 0x0020 | csw | 0x000E – Anzahl 16-Bit-Werte in fibRgW |
| 0x003E | cslw | 0x0016 – Anzahl 32-Bit-Werte in fibRgLw |
| 0x004C | ccpText | Zeichen im Haupttext (fibRgLw, 4. Eintrag) |
| 0x0098 | cbRgFcLcb | Anzahl FC/LCB-Paare: 0x005D (93) bei Word 97 |
| 0x01A2 | fcClx / lcbClx | Position und Länge der CLX (Piece Table) im Table-Stream |
🐞Debugger: Wie Word den Text findet
Signatur prüfen
Die ersten 8 Byte sind D0 CF 11 E0 A1 B1 1A E1 – also eine Compound File (CFB). Ob es ein Word-Dokument ist, zeigt erst der Inhalt.
🧩Piece Table: Text als Liste von Stücken
CP: 0 21 42
├── Piece 0 ─────────┼── Piece 1 ───────────┤
│ "Grüße aus │ "Zweiter Absatz: │
│ VisualWord\r" │ Łódź\r" │
└─────────┬──────────┴──────────┬───────────┘
fc = 0x40001000 fc = 0x00000A00
Bit 30 = 1 → 8 Bit Bit 30 = 0 → UTF-16LE
Position 0x1000/2 = 0x800 Position 0xA00
21 Byte CP1252 42 ByteWarum Stücke? Beim Schnellspeichern (fComplex = 1) hängte Word neuen Text einfach hinten an und ergänzte die Piece Table – statt die Datei umzuschreiben. Gelöschter Text konnte so in der Datei bleiben, ein bekanntes Datenschutzproblem alter .doc-Dateien.
Warum zwei Kodierungen? Text, der vollständig in Windows-1252 passt, speichert Word „komprimiert“ mit 1 Byte pro Zeichen. Zeichen wie Ł oder ź gibt es in CP1252 nicht – dieses Piece steht deshalb in UTF-16LE (2 Byte pro Zeichen).
Sonderzeichen im Text: 0x0D Absatzende, 0x0B Zeilenumbruch, 0x07 Zellen-/Zeilenende in Tabellen, 0x0C Seiten-/Abschnittswechsel, 0x13/0x14/0x15 Feldanfang/-trenner/-ende.
🎨Formatierung: FKPs mit CHPX und PAPX
ChpxFkp (512 Byte)
┌──────────────────────────────────────┐
│ rgfc[0] rgfc[1] … rgfc[crun] │ ← Byte-Positionen (FC)
│ rgb[0] rgb[1] … (je 1 Byte) │ ← Offset/2 zur CHPX
│ … frei … │
│ CHPX: cb │ grpprl (SPRMs) │ ← z. B. sprmCFBold
│ CHPX: cb │ grpprl │
│ crun ◄─┤ letztes Byte
└──────────────────────────────────────┘
PapxFkp: gleich aufgebaut, aber mit BX-Einträgen (13 Byte)
und PAPX = Stilindex + Absatz-SPRMsCHPX (Character Properties eXception) beschreibt, worin sich Zeichen von ihrer Formatvorlage unterscheiden – z. B. fett oder eine andere Schriftgröße. PAPX macht dasselbe für Absätze (Einzug, Ausrichtung, Abstände).
Die eigentlichen Änderungen sind SPRMs (Single Property Modifiers): ein 2-Byte-Code plus Operand. Die Beispieldatei dieser Seite enthält keine FKPs – sie zeigt nur den Weg zum Text.
<w:r><w:rPr><w:b/></w:rPr><w:t>fett</w:t></w:r> – lesbar, aber deutlich größer (deshalb ZIP-komprimiert).