The lowest layer of a PST file, the node database (NDB), as described in Microsoft’s [MS-PST] (read 2026-11-12). It stores nodes (identified by NIDs) whose data lives in blocks (identified by BIDs), and finds both through two B-trees. Numbers are little-endian. Unicode files use 64-bit IDs and offsets; ANSI files use 32-bit ones.
Header
| Unicode | ANSI | Size (U / A) | Field |
|---|---|---|---|
0 | 0 | 4 | dwMagic: !BDN (21 42 44 4E). |
4 | 4 | 4 | dwCRCPartial: CRC of the 471 bytes from offset 8. |
8 | 8 | 2 | wMagicClient: SM (53 4D). |
10 | 10 | 2 | wVer: 14 or 15 ANSI; 23 or more Unicode; 37 means written by an Outlook that supports Windows Information Protection. |
12 | 12 | 2 | wVerClient: 19. |
14 | 14 | 1 | bPlatformCreate: 1. |
15 | 15 | 1 | bPlatformAccess: 1. |
16 | 16 | 4 | dwReserved1. |
20 | 20 | 4 | dwReserved2. |
24 | – | 8 | bidUnused (Unicode). |
32 | 28 | 8 / 4 | bidNextP: next page BID to allocate. |
| – | 24 | 4 | bidNextB (ANSI position). |
40 | 32 | 4 | dwUnique: increased every time the header changes, so the CRCs differ. |
44 | 36 | 128 | rgnid: 32 NIDs, one per NID type: the last nidIndex allocated. A new file starts normal folders and other types at 400, search folders at 4000, normal messages at 10000 and associated messages at 8000 (hex). |
172 | – | 8 | qwUnused (Unicode). |
180 | 164 | 72 / 40 | root: ROOT. |
252 | – | 4 | dwAlign (Unicode). |
256 | 204 | 128 | rgbFM: deprecated free map, FF bytes. |
384 | 332 | 128 | rgbFP: deprecated free page map, FF bytes. |
512 | 460 | 1 | bSentinel: 80. |
513 | 461 | 1 | bCryptMethod: see encryption. |
514 | 462 | 2 | rgbReserved. |
516 | – | 8 | bidNextB: next block BID (Unicode position). |
524 | – | 4 | dwCRCFull: CRC of the 516 bytes from offset 8 (Unicode). |
| – | 464 | 8 + 4 | ANSI: ullReserved, dwReserved. |
528 | 476 | 3 | rgbReserved2. |
531 | 479 | 1 | bReserved. |
532 | 480 | 32 | rgbReserved3. |
The CRC is [MS-PST]’s CRC-32: the usual reflected table (77073096,
EE0E612C, …), seeded with 0 and not inverted at the end.
ROOT
| Field | Size (U / A) | Meaning |
|---|---|---|
dwReserved | 4 | |
ibFileEof | 8 / 4 | File size. |
ibAMapLast | 8 / 4 | Offset of the last allocation map page. |
cbAMapFree | 8 / 4 | Free space in all AMaps. |
cbPMapFree | 8 / 4 | Free space in all PMaps (deprecated). |
BREFNBT | 16 / 8 | BID and offset of the root page of the node B-tree. |
BREFBBT | 16 / 8 | The same for the block B-tree. |
fAMapValid | 1 | 0 the AMaps are not valid; 2 valid; 1 valid but deprecated. |
bReserved, wReserved | 1, 2 |
Encryption
bCryptMethod: 00 none; 01 permutation (a
fixed byte substitution table); 02 cyclic (a symmetric cipher keyed by
the low 32 bits of the block’s BID); 10 Windows Information Protection.
Only the data field of data blocks is encoded; padding, trailers, the header,
allocation pages and B-tree pages are not. [MS-PST] calls the two ciphers keyless
obfuscation that is easily decoded.
Node and block IDs
- NID, 32 bits: bits 0–4
nidType, bits 5–31nidIndex. - BID, 64 bits (ANSI 32): bit 0 reserved, bit 1 set for internal blocks (XBLOCKs and subnode blocks), the rest an index, so block BIDs grow by 4. Page BIDs use all the bits and grow by 1.
- BREF: a BID and the byte offset of the block or page.
| nidType | Name | nidType | Name |
|---|---|---|---|
00 | HID (heap ID) | 0B | RECEIVE_FOLDER_TABLE |
01 | INTERNAL | 0C | OUTGOING_QUEUE_TABLE |
02 | NORMAL_FOLDER | 0D | HIERARCHY_TABLE |
03 | SEARCH_FOLDER | 0E | CONTENTS_TABLE |
04 | NORMAL_MESSAGE | 0F | ASSOC_CONTENTS_TABLE |
05 | ATTACHMENT | 10 | SEARCH_CONTENTS_TABLE |
06 | SEARCH_UPDATE_QUEUE | 11 | ATTACHMENT_TABLE |
07 | SEARCH_CRITERIA_OBJECT | 12 | RECIPIENT_TABLE |
08 | ASSOC_MESSAGE | 13 | SEARCH_TABLE_INDEX |
0A | CONTENTS_TABLE_INDEX | 1F | LTP |
Special internal NIDs: 21 message store, 61 named
property map, A1 normal folder template, C1 search folder
template, 122 root folder, 1E1 search management queue,
201 search activity list, 241 reserved, 261
search domain object, 281 search gatherer queue, 2A1
search gatherer descriptor, 2E1 and 301 reserved,
321 search gatherer folder queue.
Pages
Pages are 512 bytes and end with a trailer (16 bytes Unicode, 12 ANSI):
ptype (1), ptypeRepeat (1, the same), wSig
(2, zero for map pages, a computed signature for B-tree and density list pages),
then dwCRC (4) of the page data and the page’s BID; ANSI files put the
BID before the CRC. Map pages use their file offset as their BID.
| ptype | Page |
|---|---|
80 | BBT page (block B-tree) |
81 | NBT page (node B-tree) |
82 | FMap page (deprecated) |
83 | PMap page (deprecated) |
84 | AMap page: one bit per 64 bytes of the following 253,952 bytes |
85 | FPMap page (deprecated) |
86 | Density list page |
The first AMap page is at offset 4400, then every 253,952 bytes.
B-tree pages
Entries fill the page; at the end, before the trailer: cEnt (1),
cEntMax (1), cbEnt (1, entry size), cLevel
(1, 0 for leaves), and in Unicode 4 padding bytes.
| Entry | Fields (Unicode sizes) |
|---|---|
| Intermediate (BTENTRY) | Key (8), BREF of the child page (16). |
| NBT leaf (NBTENTRY) | NID (8, padded), bidData (8), bidSub (8, subnode tree or 0), nidParent (4), padding (4). |
| BBT leaf (BBTENTRY) | BREF (16), cb (2, data size), cRef (2, reference count), padding (4). |
Blocks
A block is its data, padding to a multiple of 64 bytes, and a trailer (16 bytes
Unicode, 12 ANSI): cb (2, data size), wSig (2),
dwCRC (4, of the cb data bytes) and bid, with
bid before dwCRC in ANSI files. Blocks are 64 to 8,192
bytes.
| Block | Layout |
|---|---|
| Data block | The node’s bytes, encrypted as bCryptMethod says. |
| XBLOCK | btype 1, cLevel 1, cEnt (2), lcbTotal (4, total size), then cEnt BIDs of data blocks: for data larger than one block. |
| XXBLOCK | As XBLOCK with cLevel 2; its BIDs point to XBLOCKs. |
| SLBLOCK | btype 2, cLevel 0, cEnt (2), padding (4), then entries: NID (8), bidData (8), bidSub (8). The node’s subnodes. |
| SIBLOCK | btype 2, cLevel 1, then entries: NID (8), BID of an SLBLOCK (8). |
Above this layer, nodes hold heaps, tables and property contexts.