OLE2 / CFB file format
Compound File Binary format — the legacy container behind .doc/.xls/.ppt and .msi installers.
- MIME type
- application/x-ole-storage
- Extensions
- .doc, .xls, .ppt, .msi
- Category
- Documents
Magic-number signature
| 0x00000000 | D0CF11E0A1B11AE1 | ········ | CFB / 'docfile' magic |
About the OLE2 / CFB format
The Compound File Binary format is a complete filesystem packed into one file: it has sectors, an allocation table, a directory tree of named streams and even a second, smaller allocation scheme for tiny streams. Microsoft built it so an application could store many independent pieces inside a single document without rewriting the whole thing on every save, and everything from Word 97 documents to Windows Installer packages to Outlook .msg files still rides on it. Reading one means walking a chain of sector numbers, not scanning forward through bytes, which is why a hex dump of a .doc looks like noise even though the text is in there uncompressed.
Header layout, byte by byte
| Offset | Length | Field | Meaning |
|---|---|---|---|
| 0 | 8 | Header signature | Always D0 CF 11 E0 A1 B1 1A E1. The first four bytes are a deliberate pun — D0CF11E reads as 'docfile' in leetspeak. Nothing about the extension is implied: .doc, .xls, .ppt, .msi and .msg all start with these same eight bytes. |
| 8 | 16 | Header CLSID | Reserved class identifier, and the specification requires it to be all zeros. A non-zero value here is a sign of a hand-built or deliberately malformed file rather than a legitimate variant. |
| 24 | 2 | Minor version | Little-endian, and 0x003E in essentially every file in the wild. It has no functional effect; readers ignore it. |
| 26 | 2 | Major version | 0x0003 or 0x0004, and this one matters: version 3 uses 512-byte sectors and 32-bit stream sizes, version 4 uses 4096-byte sectors and lets a stream exceed 4 GiB. Almost every Office document is version 3. |
| 28 | 2 | Byte order mark | FE FF, meaning little-endian. Big-endian was never actually specified, so this is a constant in practice — a useful second sanity check when you suspect the file has been shifted. |
| 30 | 2 | Sector shift | The base-2 logarithm of the sector size: 0x0009 for 512-byte sectors with major version 3, 0x000C for 4096-byte sectors with version 4. Sector N begins at byte offset (N + 1) times the sector size, because sector numbering starts after the 512-byte header. |
| 32 | 2 | Mini sector shift | Log base 2 of the mini-sector size, mandated as 0x0006 — 64 bytes. The mini stream exists so that a file holding hundreds of tiny property streams does not waste half a kilobyte on each one. |
| 34 | 6 | Reserved | Six bytes that must be written as zero and ignored on read. They exist only to align the fields that follow onto a four-byte boundary. |
| 40 | 4 | Number of directory sectors | Count of sectors holding directory entries. Version 3 files must write zero here and follow the directory chain through the allocation table instead; only version 4 uses the value. |
| 44 | 4 | Number of FAT sectors | How many sectors are given over to the file allocation table. Each FAT sector holds sectorSize divided by four next-sector pointers, so a 512-byte sector maps 128 sectors and the table grows linearly with the document. |
| 48 | 4 | First directory sector | Sector number where the directory chain starts. The first directory entry is always the root, whose name is 'Root Entry' in UTF-16, and whose child tree names every stream in the file — this is the single field to follow when you want to know what is actually inside. |
| 52 | 4 | Transaction signature | Incremented by implementations that support transactional saves, zero everywhere else. In practice it is a useful fingerprint for which application wrote the file. |
| 56 | 4 | Mini stream cutoff size | Mandated as 0x00001000, 4096. A stream shorter than this is stored in the mini stream through the mini FAT; a stream at or above it gets whole sectors of its own. Two different allocation paths in one file, decided by this one number. |
| 60 | 4 | First mini FAT sector | Start of the mini allocation table, or the end-of-chain marker FFFFFFFE when the document has no small streams at all. |
| 64 | 4 | Number of mini FAT sectors | Length of that chain in sectors. Zero is normal for a document made entirely of large streams. |
| 68 | 4 | First DIFAT sector | Where the double-indirect allocation table continues once the 109 slots in the header run out. FFFFFFFE means it never did, which is the case for any file under roughly 7 MB with 512-byte sectors. |
| 72 | 4 | Number of DIFAT sectors | Length of that continuation chain. Position is meaning in the DIFAT: slot i owns a fixed block of FAT indices, so a reader that skips an unreadable slot rather than leaving a placeholder silently misreads every sector number after it. |
| 76 | 436 | DIFAT array | 109 four-byte sector numbers naming the first 109 FAT sectors, filling the header out to exactly 512 bytes. Unused slots hold FFFFFFFF. This is why small compound files need no DIFAT sectors of their own — the header already maps everything. |
Telling it apart from its neighbours
| Easily confused with | How to tell |
|---|---|
| Word document (docx) | The generational split in Office. Both may sit in a folder called 'documents' and both open in Word, but a pre-2007 .doc is D0 CF 11 E0 and a .docx is 50 4B 03 04. The extension is not proof: Office happily saves an old-format file under a .docx name and vice versa, and it sniffs the bytes when opening. Read offset 0. |
| doc/xls/ppt/msi | All four share the same eight-byte magic and cannot be told apart from the header at all — the container says nothing about what it contains. The answer is the directory tree reached through offset 48: a stream named 'WordDocument' means .doc, 'Workbook' or the older 'Book' means .xls, 'PowerPoint Document' means .ppt, and a set of streams with mangled base-64-ish names means a Windows Installer package. |
| msg | An Outlook .msg is a compound file too, with the same magic, so a detector will call it OLE2 and stop. What identifies it is a directory full of streams named __substg1.0_ followed by a property tag and type, plus __properties_version1.0 — the MAPI property storage layout rather than a document layout. |
| SQLite database | Worth pairing because they are the two 'a whole filesystem in one file' formats you actually meet. SQLite opens with the ASCII string 'SQLite format 3' and a NUL; CFB opens with D0 CF 11 E0. Both use fixed-size pages and a free list, but SQLite's format is public, versioned and exhaustively documented, while CFB's was reverse-engineered for a decade before Microsoft published it. |
How these files break
- A broken FAT chain. Because a stream is a linked list of sector numbers rather than a contiguous run, one damaged entry in the allocation table does not truncate a stream — it redirects it into somebody else's sectors. The document opens, and a paragraph in the middle comes out as another paragraph, or as binary garbage, with no checksum anywhere to notice.
- Truncation. The 512-byte header survives, so every tool identifies the file correctly and reports its version and sector size, and then extraction fails because the directory sector named at offset 48 lies past the end of the file. A CFB has no trailer, so there is no cheap end-of-file check the way there is for PNG or ZIP.
- A DIFAT loop. Sector numbers in the double-indirect table are unvalidated, so a corrupt file can point the chain back at itself. A naive parser follows it forever; this is the classic denial-of-service shape for compound-file readers, and the defence is to bound the walk by the number of sectors the file could possibly hold rather than trusting the declared counts at offsets 44 and 72.
- Text-mode transfer. Every CFB field is binary, so ASCII-mode line-ending translation shifts sector boundaries and corrupts the header from offset 24 onward — the magic at offset 0 contains no 0D 0A and survives untouched, which is precisely the trap. The file is still identified as OLE2 and is completely unreadable.
- A version 4 file read as version 3. Major version at offset 26 decides whether sectors are 512 or 4096 bytes. Ignore it and every sector offset is wrong by a factor of eight, which produces a stream of plausible-looking but entirely misaligned data rather than a clean failure.
Where the format stands today
OLE2 is a legacy container that refuses to die, because too much still rides on it. Office moved to the ZIP-and-XML formats in 2007 and pre-2007 documents are now genuinely old, but three things keep compound files in daily circulation: Windows Installer packages, which are still CFB and still how Windows software ships; Outlook .msg files, which are what every 'save this email' button produces; and the enormous archived estate of .doc and .xls in legal, government and enterprise document stores that nobody will ever convert. It is also a persistent security surface — macros in an old .doc, and the format's own complexity, made compound files a favourite malware vector for two decades, which is why mail gateways treat D0 CF 11 E0 with suspicion regardless of extension. For anything new the format has no advantages left: OOXML is a ZIP, is inspectable with ordinary tools, compresses far better and is a published standard, while CFB's spec was only released as [MS-CFB] long after everyone had already reverse-engineered it.
Open OLE2 / CFB files on this site
Every one of these runs in your browser — the file never leaves your machine.
Related formats
Formats that are easy to confuse with OLE2 / CFB — compare their leading bytes.
| Format | First bytes | Extension |
|---|---|---|
| Word document (docx) | 50 4B 03 04 | .docx |
| Excel workbook (xlsx) | 50 4B 03 04 | .xlsx |
| PowerPoint presentation (pptx) | 50 4B 03 04 | .pptx |
| 25 50 44 46 2D |