🧱.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

CFB teilt die Datei in gleich große Sektoren. Welcher Sektor zu welchem Stream gehört und in welcher Reihenfolge, steht in der FAT – genau wie beim FAT-Dateisystem auf alten Disketten.
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)
💡 Besondere Sektornummern
  • FFFFFFFF = FREESECT – frei
  • FFFFFFFE = ENDOFCHAIN – Kettenende
  • FFFFFFFD = FATSECT – Sektor gehört zur FAT
  • FFFFFFFC = DIFSECT – Sektor gehört zur DIFAT
  • 00000000 … FFFFFFFA – normale Sektornummern
✅ Warum so kompliziert?
CFB stammt aus der OLE-Welt der frühen 1990er: Programme sollten Teile eines Dokuments ändern können, ohne die ganze Datei neu zu schreiben – mit Speicher, der damals knapp war. Die Kehrseite: Gelöschte Daten können in freien Sektoren liegen bleiben.

⛓️Sektoren, FAT und MiniFAT

Wähle eine Kette und gehe Schritt für Schritt: Jeder Schritt schlägt in der FAT nach, welcher Sektor als Nächstes kommt. WordDocument ist absichtlich fragmentiert – Sektor 6 gehört dem Verzeichnis.

🗺️ Sektorkarte der Beispieldatei (7680 Byte = Header + 14 Sektoren)

H
Header
0x0000

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.

Schritt 1 / 9 · Tasten ← →
FAT[2] = 3

Sektor 2 liegt bei Datei-Offset 0x0600 ((2 + 1) × 512). Weiter mit Sektor 3.

Kette: 2 → 3 → 4 → 5 → 7 → 8 → 9 → 10 → 11 → ENDOFCHAIN

📋 FAT (Einträge 0–15)

[0] FATSECT
[1] 6
[2] 3
[3] 4
[4] 5
[5] 7
[6] ENDOFCHAIN
[7] 8
[8] 9
[9] 10
[10] 11
[11] ENDOFCHAIN
[12] ENDOFCHAIN
[13] ENDOFCHAIN
[14] FREESECT
[15] FREESECT
Sektor 2 (WordDocument) – erste 64 Byte
0600
0610
0620
0630

📇Das Verzeichnis (Directory Entries)

Das Verzeichnis ist selbst ein Stream aus 128-Byte-Einträgen. Eintrag 0 ist immer der Root Entry; seine Daten sind der Mini-Stream.
IDNameTypFarbeL / R / KindStartGrößeliegt in
0Root EntryRoot Storage⚫ schwarz– / – / 213320FAT (Mini-Stream)
1WordDocumentStream⚫ schwarz– / 4 / –24608FAT
21TableStream⚫ schwarz3 / 1 / –033MiniFAT
3DataStream⚫ schwarz– / – / –1128MiniFAT
4\x05SummaryInformationStream🔴 rot– / – / –3100MiniFAT
5(frei)unbenutzt–– / – / ––––
6(frei)unbenutzt–– / – / ––––
7(frei)unbenutzt–– / – / ––––
Root Entry1TableDataWordDocument\x05Summary…Kindlinksrechtsrechts

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

0480
0490
04A0
04B0
04C0
04D0
04E0
04F0

+0x00 Name (UTF-16LE): Bis zu 31 Zeichen plus Null-Terminator, UTF-16LE.

📚Streams in einem echten Word-Dokument

NameInhalt
WordDocumentPflicht. Beginnt mit dem FIB; enthält den Text und die Formatierungsseiten (FKPs).
1Table / 0TableTabellen-Stream: Piece Table (CLX), Formatvorlagen (STSH), Schriftarten, Listen, Felder. Welcher der beiden gilt, sagt das FIB-Bit fWhichTblStm.
DataOptional: Bilder und andere Binärdaten, auf die der Text verweist.
\x05SummaryInformationProperty Set mit Titel, Autor, Datum, Seitenzahl ([MS-OLEPS]).
\x05DocumentSummaryInformationWeitere Eigenschaften: Firma, Kategorie, benutzerdefinierte Felder.
\x01CompObjName 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)

Der FIB steht bei Offset 0 des WordDocument-Streams und ist das Inhaltsverzeichnis des Dokuments: Er enthält vor allem Paare aus Position (fc) und Länge (lcb) für Strukturen im Table-Stream.
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)
OffsetFeldBedeutung
0x0000wIdent0xA5EC – Kennung eines Word-Binärdokuments
0x0002nFib0x00C1 = Word 97 (neuere Versionen tragen nFibNew in FibRgCswNew: 0x00D9 Word 2000, 0x0101 Word 2002, 0x010C Word 2003, 0x0112 Word 2007)
0x0006lidSprach-ID der Installation, z. B. 0x0407 = Deutsch (Deutschland)
0x000AFlagsfDot (Vorlage), fComplex (schnellgespeichert), fEncrypted, fWhichTblStm (Bit 9), fObfuscated …
0x0020csw0x000E – Anzahl 16-Bit-Werte in fibRgW
0x003Ecslw0x0016 – Anzahl 32-Bit-Werte in fibRgLw
0x004CccpTextZeichen im Haupttext (fibRgLw, 4. Eintrag)
0x0098cbRgFcLcbAnzahl FC/LCB-Paare: 0x005D (93) bei Word 97
0x01A2fcClx / lcbClxPosition und Länge der CLX (Piece Table) im Table-Stream

🐞Debugger: Wie Word den Text findet

Vom ersten Byte bis zum fertigen Text in 18 Schritten – Header, FAT, Verzeichnis, FIB, Mini-Stream, CLX und zwei Pieces mit unterschiedlicher Kodierung. Tasten ← → blättern.
Schritt 1 / 18 · Tasten ← →
Datei-Offset

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.

→ D0 CF 11 E0 A1 B1 1A E1
0000
0010
0020
0030

🧩Piece Table: Text als Liste von Stücken

Die Zeichen des Dokuments stehen nicht einfach am Stück im Stream. Die PlcPcd bildet Zeichenpositionen (CP) auf Bytepositionen (FC) ab.
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 Byte

Warum 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

Formatierungen stehen getrennt vom Text in 512-Byte-Seiten (Formatted Disk Pages, FKP) im WordDocument-Stream. Der Table-Stream enthält die Indizes PlcBteChpx und PlcBtePapx, die sagen, welche Seite für welchen Textbereich zuständig ist.
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-SPRMs

CHPX (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.

💡 Vergleich mit .docx
In der .docx steht dieselbe Information direkt am Text: <w:r><w:rPr><w:b/></w:rPr><w:t>fett</w:t></w:r> – lesbar, aber deutlich größer (deshalb ZIP-komprimiert).