🗄️.xls – BIFF8 im Compound File

Die klassische Excel-Datei (Excel 97–2003) ist ein kleines Dateisystem – das Compound File (OLE2). Darin liegt der Stream „Workbook“: eine Folge von Records im Binary Interchange File Format Version 8.

📂Compound File: Dateisystem in der Datei

Sektoren fester Größe, eine FAT als Kettenverzeichnis und ein Verzeichnis mit 128-Byte-Einträgen. Klicke Streams an und verfolge ihre Sektorkette.
1 · Header (die ersten 512 Byte)
0000D0CF11E0A1B11AE10000000000000000
001000000000000000003E000300FEFF0900
002006000000000000000000000001000000
00300100000000000000001000000A000000
004001000000FEFFFFFF0000000000000000
0050FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

Gezeigt: Byte 0–95 von 512; ab 0x4C folgt das DIFAT-Feld (109 Einträge, hier nur Sektor 0, Rest FF FF FF FF).

0x00Signatur D0 CF 11 E0 A1 B1 1A E1 – erkennt jede OLE2-Datei
0x08CLSID reserviert, muss 0 sein
0x18Minor Version laut Spezifikation 0x003E
0x1AMajor Version 3 (512-Byte-Sektoren) oder 4 (4096-Byte-Sektoren)
0x1CByte Order FE FF → little endian
0x1ESector Shift 2^9 = 512 Byte (v3) bzw. 2^12 = 4096 Byte (v4)
0x20Mini Sector Shift 2^6 = 64 Byte
0x22reserviert 0
0x28Anzahl Verzeichnis-Sektoren bei Version 3 immer 0
0x2CAnzahl FAT-Sektoren wie viele Sektoren die FAT belegt
0x30Erster Verzeichnis-Sektor Start der Verzeichniskette
0x34Transaktionssignatur 0
0x38Mini Stream Cutoff 0x1000 = 4096 – kleinere Streams → Mini-Stream
0x3CErster Mini-FAT-Sektor Start der Mini-FAT
0x40Anzahl Mini-FAT-Sektoren
0x44Erster DIFAT-Sektor ENDOFCHAIN, solange 109 FAT-Sektoren reichen
0x48Anzahl DIFAT-Sektoren
0x4CDIFAT[0..108] Nummern der ersten 109 FAT-Sektoren, Rest FREESECT
2 · Verzeichnis und Sektorkette

Workbook: der BIFF8-Record-Strom (bei BIFF5: „Book“). Start-Sektor 2, Größe 4700 Byte → Kette über die FAT.

Kette: 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 12 → 13 → ENDOFCHAIN
Sektor 0 · @512
FAT
FAT → FATSECT
Sektor 1 · @1024
Verz.
FAT → ENDOFCHAIN
Sektor 2 · @1536
Workbook
FAT → 3
#1
Sektor 3 · @2048
Workbook
FAT → 4
#2
Sektor 4 · @2560
Workbook
FAT → 5
#3
Sektor 5 · @3072
Workbook
FAT → 6
#4
Sektor 6 · @3584
Workbook
FAT → 7
#5
Sektor 7 · @4096
Workbook
FAT → 8
#6
Sektor 8 · @4608
Workbook
FAT → 9
#7
Sektor 9 · @5120
Workbook
FAT → 12
#8
Sektor 10 · @5632
MiniFAT
FAT → ENDOFCHAIN
Sektor 11 · @6144
Mini
FAT → ENDOFCHAIN
Sektor 12 · @6656
Workbook
FAT → 13
#9
Sektor 13 · @7168
Workbook
FAT → ENDOFCHAIN
#10
Sektor 14 · @7680
frei
FAT → FREESECT
Sektor 15 · @8192
frei
FAT → FREESECT

Datei-Offset von Sektor n = (n + 1) · 512, weil der Header selbst den ersten 512-Byte-Block belegt. Die Beispieldatei ist erfunden, aber in sich stimmig; der Workbook-Stream ist bewusst fragmentiert (Sektoren 2–9, dann 12–13).

📚 Quelle: [MS-CFB] 2.2 Compound File Header, 2.3 Compound File FAT Sectors, 2.5 Mini FAT, 2.6 Directory Entry; gegengeprüft an einer von LibreOffice erzeugten .xls (Sektoren 512 Byte, Mini 64 Byte, Cutoff 4096).

🧱Aufbau eines Records


 Offset  0      1      2      3      4 …                     4+Länge
        ┌──────┴──────┬──────┴──────┬─────────────────────────┐
        │ Typ (u16 LE)│ Länge (u16) │ Daten (0 … 8224 Byte)   │  ─►  nächster Record
        └─────────────┴─────────────┴─────────────────────────┘
 Beispiel: 09 08 10 00 │ 00 06 05 00 BB 0D CC 07 00 00 00 00 06 00 00 00
           └ BOF 0x0809, 16 Byte  └ vers 0x0600 = BIFF8, dt 0x0005 = Globals …

 Stream "Workbook"
 ├─ Globals-Substream   BOF(dt=0x0005) · CODEPAGE · DATEMODE · FONT/FORMAT/XF · BOUNDSHEET… · SST · EXTSST · EOF
 ├─ Blatt 1             BOF(dt=0x0010) · DIMENSIONS · ROW… · Zellen (LABELSST/RK/NUMBER/FORMULA…) · EOF
 └─ Blatt 2 …           (BOUNDSHEET.lbPlyPos zeigt jeweils auf das BOF des Blatts)
🔁Little Endian
Alle Zahlen stehen mit dem niederwertigen Byte zuerst: der Typ 0x0809 steht als 09 08 in der Datei.
⏭️Überspringbar
Weil jede Länge davorsteht, kann ein Leser unbekannte Records einfach überspringen – so bleiben alte Programme mit neuen Dateien kompatibel.
✂️Max. 8224 Byte
Größere Daten (z. B. eine lange SST) werden in CONTINUE-Records (0x003C) fortgesetzt.

📚 Quelle: [MS-XLS] Abschnitt „Record“; OpenOffice.org „Excel File Format“.

🐞Record-Stream-Debugger

Ein kleiner, synthetischer BIFF8-Stream mit der Tabelle „Umsatz“. Gehe Record für Record vor und zurück (Tasten ← →): Hex, Felder und die rekonstruierte Tabelle.
Schritt 1 / 25 · Tasten ← →
#1 BOF 0x0809Offset 0 · 20 ByteSubstream: Workbook-Globals

Beginn der Workbook-Globals (BIFF8)

00000908100000060500BB0DCC0700000000
001006000000
FeldBytesBedeutung
Record-Typ09 080x0809 = BOF
Länge10 0016 Byte
vers00 060x0600 = BIFF8
dt (Substream-Typ)05 000x0005 = Workbook-Globals
rupBuildBB 0D3515
rupYearCC 071996
bfh (Datei-Historie)00 00 00 000x00000000
sfo (niedrigste BIFF-Version)06 00 00 000x00000006
Rekonstruiertes Blatt „Umsatz“
ABCD
1
2
3
4
5
Shared String Table

Noch nicht gelesen – kommt im Globals-Substream.

Synthetischer Stream (465 Byte), gekürzt um FONT/XF/FORMAT-Records. In eine CFB-Hülle verpackt öffnet LibreOffice ihn mit genau diesen Werten.

📚 Quelle: [MS-XLS] Records BOF, BoundSheet8, SST, LabelSst, Number, RK, Formula sowie Struktur PtgRef; Stream zur Probe in LibreOffice geöffnet.

🔢RK-Zahlen: 8 Byte in 4 quetschen

Viele Zahlen in Tabellen sind Ganzzahlen oder haben zwei Nachkommastellen. BIFF speichert sie im RK-Format mit nur 32 Bit.
Zahl → RK
00000000000000000000011111001111

Variante int×100 · RK = 0x000007CF · im Record (little endian): CF 07 00 00

Wert · 100 = 499 ist ganzzahlig: (Wert·100) · 4 + 3 (fInt + fX100).

RK-Record: 4 Byte statt 8 für NUMBER – der Record ist 10 statt 14 Byte lang.

RK → Zahl
00000000000000101100001110110010
  • Bit 0 fX100 = 0 nicht teilen
  • Bit 1 fInt = 1 obere 30 Bit = Ganzzahl mit Vorzeichen
  • Bits 2–31 = 45292

= 45292

Beispiel 0002C3B2 stammt aus einer echten .xls: die Zahl 45292 (= 01.01.2024 als Datum).

💡 Warum „× 100“?
Geldbeträge wie 4,99 sind als Double nicht exakt darstellbar, 499 aber schon. Mit dem Bit fX100 speichert Excel 499 und teilt beim Lesen durch 100 – verlustfrei und in 4 statt 8 Byte. Mehrere RK-Zellen nebeneinander fasst MULRK (0x00BD) zusammen.

📚 Quelle: [MS-XLS] Strukturen RkNumber und RkRec; Werte 0x0002C3B2, 0x0000025B aus einer realen .xls.

📋Wichtige Record-Typen

TypNameWoBedeutung
0x0809BOFbeideBeginn eines Substreams: Version 0x0600 (BIFF8), Typ 0x0005 Globals oder 0x0010 Arbeitsblatt
0x000AEOFbeideEnde des Substreams, Länge 0
0x0042CODEPAGEGlobalsZeichensatz; in BIFF8 immer 1200 (UTF-16)
0x0022DATEMODEGlobals0 = 1900-Datumssystem, 1 = 1904-System
0x0031FONTGlobalsSchriftart (Höhe in 1/20 pt, Name als Unicode-String)
0x041EFORMATGlobalsZahlenformat-String, z. B. „yyyy\-mm\-dd“ mit Nummer ab 164
0x00E0XFGlobalsZellformat: verweist auf FONT und FORMAT – Zellen tragen einen XF-Index (ixfe)
0x0085BOUNDSHEETGlobalsBlattname + Stream-Offset (lbPlyPos) des Blatt-BOF
0x00FCSSTGlobalsShared String Table: alle Texte der Mappe, einmalig gespeichert
0x00FFEXTSSTGlobalsSprungtabelle in die SST (schneller Zugriff)
0x003CCONTINUEbeideFortsetzung, wenn Daten > 8224 Byte sind
0x0200DIMENSIONSBlattBenutzter Bereich: erste/letzte+1 Zeile und Spalte
0x0208ROWBlattZeileneigenschaften (Höhe, Format)
0x00FDLABELSSTBlattTextzelle: Zeile, Spalte, XF, Index in die SST
0x0203NUMBERBlattZahlzelle mit 8-Byte-IEEE-754-Double
0x027ERKBlattZahlzelle kompakt in 4 Byte (RK-Kodierung)
0x00BDMULRKBlattMehrere RK-Zellen nebeneinander in einer Zeile
0x0006FORMULABlattFormelzelle: zwischengespeichertes Ergebnis + Formel als Token (Ptg) in UPN
0x0207STRINGBlattText-Ergebnis der vorangehenden Formel
0x0201BLANKBlattLeere, aber formatierte Zelle
0x0205BOOLERRBlattWahrheitswert oder Fehlerwert (#DIV/0! …)

📚 Quelle: [MS-XLS] Record Enumeration; Nummern mit einer echten .xls gegengeprüft.

🧓BIFF-Versionen und Grenzen

VersionExcelBOFContainerBesonderheit
BIFF2Excel 2.x0x0009loser Record-Strom1 Blatt pro Datei
BIFF3Excel 3.00x0209loser Record-Strom1 Blatt pro Datei
BIFF4Excel 4.00x0409loser Record-Strom (Arbeitsmappen-Variante BIFF4W)XLM-Makroblätter
BIFF5Excel 5.0 / 950x0809, vers 0x0500Compound File, Stream „Book“Arbeitsmappen, VBA, Texte in Codepage
BIFF8Excel 97–20030x0809, vers 0x0600Compound File, Stream „Workbook“UTF-16, SST, 65 536 Zeilen
⚠️ 65 536 × 256 ist die harte Grenze von .xls
BIFF8 speichert die Zeile als 16-Bit-Zahl (0–65 535) und nutzt für die Spalte 8 Bit (0–255 = A–IV). Wer eine .xlsx mit mehr Zeilen als .xls speichert, verliert den Rest. Excel 5.0/95 (BIFF5) hatte sogar nur 16 384 Zeilen.
✅ Strings: komprimiert oder UTF-16
BIFF8-Texte tragen ein Flag fHighByte: 0 = jedes Zeichen als 1 Byte (Low-Byte von UTF-16, reicht für „Käse“), 1 = echtes UTF-16LE (nötig für „€“ = 0x20AC). Im Debugger siehst du beide Fälle in der SST.

📚 Quelle: OpenOffice.org „Excel File Format“ (BIFF-Versionen, BOF); [MS-XLS] Struktur XLUnicodeRichExtendedString.