PST解析程序

目录
1.整体结构的说明 2
1.1Node Database (NDB) Layer 2
1.2.Lists, Tables, and Properties (LTP) Layer 3
1.3.Messaging Layer 4
2.Header的解析 4
2.1编码方式 5
2.2 root结构 5
3.数据块的获取 7
3.1 NBT 8
3.2 BBT 9
3.3 解析数据块里的数据 12
4.PC的实现及数据存储 13
4.1 HN 14
4.1.1 封装HNBlock类 14
4.2 BTH 14
4.2.1 BTHHEADER 15
4.2.2 BTHNode(非PST结构) 15
4.3 Properties 17
5.构建MailStore对象 17
5.1 MailStore的 PC 17
5.2 MailStore的 EntryID 18
6.TC结构的实现及数据存储 19
6.1 TC_INFO 20
6.2 TCRowMatrix 20
7.构建MailFolder对象 22
8.构建message对象 24
8.1 message对象的结构 24
8.2 message对象里相关内容存储位置 25
8.3 附件 26

1.整体结构的说明
测试程序:
PST 文件是 OUTLOOK 个人文件夹文件,它是独立的、结构化的二进制文件,不需要任何的外部依赖。每一个PST文件代表一个消息存储器,包含有任意层次结构的文件夹对象Folder objects,下面又包含消息对象Message objects,消息对象下面又包含附件对象Attachment objects。文件对象、消息对象、附件对象都存储在属性中,包含有特定邮件项目的所有信息。

PST文件在逻辑上分为三层:
NDB (Node Database) layer
LTP(Lists, Tables, and Properties) layer
Messaging layer

1.1Node Database (NDB) Layer
NDB层代表数据库结点,是PST文件最低层的存储结构。NDB层包括头部(header)、文件分配信息(fileallocation information)、块(blocks)、结点(nodes)、两颗B树:结点B树和块B树(the Node BTree (NBT)and the Block BTree (BBT).)
NBT包含了指向PST文件中所有可访问结点的指针,B树的高效搜索方式能够快速的定位任意结点。每一个结点由四个属性集组成:NID,parent NID, data BID(指向与该结点关联的数据块), subnodeBID.
BBT包含了指向PST文件中所有数据块的指针,每一个块也是由四个属性集组成:BID,IB, CB, and CREF。
其中IB是该块在文件内的偏移量,CB是存储在该数据块内的字节数,CREF是该数据块的引用次数。
NBT and BBT树根的位置的都能从PST文件头部相关字段获得。
1.2.Lists, Tables, and Properties (LTP) Layer
LTP层是构建NDB结构顶部的一个高级概念,LTP层的核心元素是属性上下文thePropertyContext (PC) 和 表上下文TableContext(TC).每一个PC或TC也存放在一个单一的结点中,LTP层使用NIDS去标识PC和TC。
为了有效地实现PC和TC,LTP层在每个NDB节点顶层采用以下两种类型的数据结构。
(1).Heap-on-Node(HN)
HN是在结点之上实现的一个堆数据结构,HN能够将一个结点的数据流细分为小的、大小可变的片段,它的主要用途是将各种字符串存储到数据块中。
(2).BTree-on-Heap(BTH)
BTH是HN内部的一个数据结构。HN提供快速访问B-树的方法,而BTH提供一种快速搜索数据的方法。PC就被实现为BTH。
1.3.Messaging Layer
Messaginglayer包含有让LTB结构和NDB结构组合起来被解释为文件对象、消息对象、附件对象和属性的高级规则和业务逻辑。该层的规则使得修改后的PST文件能够被成功的读取。
注:1.本解析程序需BlowSnow的环境下编译运行。
2.本解析说明可以配合doc/[MS-PST].pdf文件一起查阅。在doc/[MS-PST].pdf文件中可以查到pst的全部结构的说明、所有字段和相关图表。
3.PST文件的字节序用的是小端模式。
4.测试解析程序部分变量名和文档中的变量名不同,但意思相同。

2.Header的解析
Header结构位于PST文件的开头(绝对文件偏移量0),包含关于PST文件的元数据,以及访问NDB层数据结构的根信息。详情查看[MS-PST].pdf 的2.2.2.6-HEADER章节
2.1编码方式
Header结构的部分字段的大小受编码方式的影响,应先读取pst文件的编码方式。读取偏移量为10,大小为2字节的wVer的值。如果文件是ANSI PST文件,那么这个值必须是14或15;如果文件是Unicode PST文件,那么这个值必须是23。
Outlook 1997-2002版本的pst文件类型,2003版本以上的默认编码方式为Unicode。
outlook 2019可以把ANSI编码的pst文件成转换Unicode编码,文件类型为ANSI的情况暂未处理,本解析目前只能解析Unicode编码的pst文件。
2.2 root结构
root结构在Header结构中偏移量为180(Unicode编码)处开始,大小为72个字节
root结构如图所示:

可以从root中取出BREFNBT和BREFBBT两个字段,这两个字段均是BREF结构。
BREF结构如图所示:

取出BREFNBT和BREFBBT的ib,即文件内偏移量,就能找到NBT和BBT。
NBT包含了指向PST文件中所有可访问结点的指针,B树的高效搜索方式能够快速的定位任意结点。每一个结点由四个属性集组成:NID,parent NID, data BID(指向与该结点关联的数据块), subnodeBID.
BBT包含了指向PST文件中所有数据块的指针,每一个块也是由四个属性集组成:BID,IB, CB, and CREF。
NBT和BBT是接下来数据块获取的必经之路。
3.数据块的获取
用已知的NID来得到bid。
已知NID就可以找到该块的所有数据,可以先找MailStore的NID(NID=0x21),这是我们第5节-构建MailStore对象所需的一个NID。现在我们先封装一个类BlockBO(非PST的结构),用NID来初始化BlockBO就可以获取全部的数据(包括该NID节点的子节点)。
3.1 NBT
用待解析的NID在NBT内用B+树方式搜索,NBT的ib指向的是BTPAGE结构。
BTPAGE结构:

当BTPAGE.cLevel字段不等于0时,把BTPAGE.rgentries字段的所有字节拷贝给BTPAGE.cEnt个BTENTRY结构,BTENTRY结构中的ib又是指向一个BTPAGE结构,继续进入这个BTPAGE结构递归查找。
当BTPAGE.cLevel字段等于0时,把BTPAGE.rgentries字段的所有字节拷贝给BTPAGE.cEnt个NBTENTRY结构,再用待解析的NID去匹配这个每一个NBTENTRY结构中的nid,一定有一个NBTENTRY结构能匹配上,不然就是出错了。拿出匹配上的NBTENTRY结构,取出其中的bidData和bidSub(注:一定要把bidsub一起取出来,不然后面会少数据了,再回来拿就会很麻烦)。
注:BTENTRY,NBTENTRY结构在[MS-PST].pdf的2.2.2.7.7—Btrees章节。

3.2 BBT
用已知的bid来得到数据。
在上面一节取出来的bidData和bidSub(bidSub=0则说明没有子块,就无需再再查找子块的数据了)都是一个bid,现在可以用已得到bid来访问数据,bidData是一个普通bid,bidSub则会有一些不同的处理,在后面的处理会再讲。
BBT的ib也是指向一个BTPAGE结构,当BTPAGE.cLevel字段不等于0时,做法和NBT的处理是一样的,生成新的BTPAGE结构,递归查找。但当当BTPAGE.cLevel字段等于0时,则是把BTPAGE. rgentries拷贝进BTPAGE.cEnt个BBTENTRY结构,用得到的bid来匹配每个BBTENTRY.BREF.bid,也一定能有一个BBTENTRY结构能匹配上。
依据匹配上的BBTENTRY结构的BREF.ib来找到这个这个数据块。根据数据块的bid的第二个位的值(假如这个值等于Internal)可以来判断这个数据块是内部数据块还是外部数据块。
数据块Block的结构:

数据块data的数据结构类型判断表:

1)若Internal=0,则是外部数据块,用CryptPermute函数解码data即可,data的大小记录在BBTENTRY.cb里。
CryptPermute函数需依赖前面第一节读到的HEADER.bCryptMethod值,来对应不同种解码方式。

2)若Internal=1,则是内部数据块(bidSub只能是内部bid),可能包含多条数据。读出data的的第一个字节type和第二个字节level,用来判断data的数据类型。
2.1)若为普通bid:
2.1.1)type =1, level=1: data为XBLOCK结构。
XBLOCK里有个rgbid字段,存放的是多个bid的数组,取出这些bid,再进行BBT获取数据即可。(下一次BBT就只会得到普通数据块或外部数据块了)。

2.1.2)type =1, level=2: data为XXBLOCK结构。
XXBLOCK里有个rgbid字段,存放的是多个bid的数组,取出这些bid,再进行BBT获取数据即可。(下一次BBT就只会得到普通数据块或外部数据块了)。

2.1.3)type =2:普通数据块,不做处理。

2.2)若为bidSub:
2.2.1)level=0:data为SLBlock结构
SLBlock结构里有个SLENTRY结构的数组,SLENTRY结构又存放了bid,取出这些bidData和bidSub,递归进行BBT处理。

2.2.2)level=1:data为SIBLOCK结构
SIBLOCK结构里有个SIENTRY结构的数组,SLENTRY结构又存放了bid,取出这些bidData,BBT处理获取data(应只有一条数据),这个data又是为SLBlock结构,再进行上面 2.2.1)的处理。

注:BBTENTRY结构也在[MS-PST].pdf的2.2.2.7.7—Btrees章节,XXBLOCK结构、XBLOCK结构、SLBlock结构、SLENTRY结构、SIBlock结构在2.2.2.8.3.2-Data Tree章节。

3.3 解析数据块里的数据

获取数据块的图解:

图的第一和第二列表示NBT,它通过根结构中的BREFNBT结构进行访问。在本例中,NBT由一个包含许多顶级节点的2级BTree组成。在第二列中,顶部的节点包含一个数据BID (bidData)和一个子节点BID (bidSub),而底部的节点只包含一个数据BID,没有子节点。
在图例中,有两种箭头符号。单箭头表示可以通过BREF结构直接访问的数据(包含目标文件的绝对偏移量);带有“BBT”的双箭头表示需要使用BBT搜索来查找与Bid相关的数据块来间接访问的数据。
顶部节点的bidData直接指向一个数据块,该数据块包含与此节点关联的外部最终用户数据。
此外,顶部节点还包含一个子节点,它指向一个2级的子节点BTree。第1级SIBLOCK扩展到许多不同的第0级slblock(为了简单起见,图中只显示了一个slblock)。每个SLBLOCK还包含许多内部子节点(第4列)。在本例中,内部子节点指向单个数据块(第5列)。子节点可以递归地包含任意数量的子节点级别,以创建子节点的层次树。
第二个顶级节点(第2列中的底部节点)是一个具有一个XBLOCK的数据树的示例,它包含一个指向包含最终用户数据的几个数据块的bid数组。
图解详情在[MS-PST].pdf的3.1 Sample Node Database (NDB)章节。
4.PC的实现及数据存储
PropertyContext的初始化需要建立在BlockBO的基础上的,PC的实现就是一个BTH。BTH实现在HN之上,用BlockBO里的数据来初始化PC的HN,用HN里的hidUserRoot(本质是个Hid, 在[MS-PST].pdf-2.3.1.1 HID章节)来访问BTH的数据。
¬¬4.1 HN
把PC结构BlockBO的data的传递进来,一个BlockBO有几条数据,在HN里就创建几个HNBlock对象(非PST的结构),一条数据对应一个HNBlock对象,只有第一个HNBlock对象里才存放了hidUserRoot。
4.1.1 封装HNBlock类
封装HNBlock类专门来处理传入HN结构里的data。
HNBlock类有一个HDR的结构(包含三种HDR的结构)和一个HNPAGEMAP结构,HDR里面存放了块内偏移量ibHnpm和hidUserRoot。块内偏移量ibHnpm指向的是HNPAGEMAP结构,HNPAGEMAP结构里有存放这个一个分配表rgibAlloc。分配表rgibAlloc存放的是HNPAGEMAP.cAlloc+1个块内偏移量。是准备为Hid的来获取数据用的。每个Hid对应数据是从块内偏移量rgibAlloc[hid.hidIndex-1]到块内偏移量rgibAlloc[hid.hidIndex]所对应的数据。
HN结构以及HN以下的结构都在[MS.PST].pdf文件的2.3.1-HN (Heap-on-Node)章节。
4.2 BTH
BTH由两个结构实现,分别是BTHHEADER、BTHNode。
4.2.1 BTHHEADER
BTHHEADER结构的初始化需要用到上面HN结构中的第一个HNBlock对象的HDR结构中的hidUserRoot。hidUserRoot(本质是个Hid)所对应的数据即是一个BTHHEADER结构。
BTHHEADER结构中有几个字段hidRoot、bIdxLevels、cbkey、cbEnt,是接下来初始化BTHNode所必需的。
BTHHEADER结构图:

BTHHEADER结构详情在[MS.PST].pdf文件的2.3.2.1 BTHHEADER章节。
4.2.2 BTHNode(非PST结构)
BTHNode是解析BTH树节点以及子节点的所有数据,存入Properties数组当中。方便后面直接访问取出。
BTHHEADER. bIdxLevels指的BTH的深度。
1) bIdxLevels>0
BTH树的所有叶节点的结构BTHIndexNode:

Key的大小在BTHHEADER.cbkey指出。hidNextLevel是下一层BTH的HID。
通过hidNextLevel可以把整个BTH树的所有叶节点用递归遍历方式全部取出了,用每个叶节点的hidNextLevel(hid)所对应的数据来初始化BTHDataNode就可以了。
2) bIdxLevels=0
BTH无子节点,直接用BTHHEADER.hidRoot(本质是hid)所对应的数据来初始化BTHDataNode就可以了。
BTHDataNode是一个如图所示的数组:

Key的大小在BTHHEADER.cbkey指出,data的大小在BTHHEADER. cbEnt指出。直接把数据用内存拷贝即初始化完成了。
4.3 Properties
Properties是整个PC的属性数组,用的方式存储,用栈遍历PC.BTH.BTHNode中所有的BTHDataNode结构存入到Properties中。
但是Properties的访问要特别注意,Properties.value的数据类型要进行转换,我们可以封装一个ExchangeProperty类来处理,转换的具体规则可以查看[MS-PST].pdf-2.1.2 Properties和[MS-OXCDATA].pdf-2.11.1 Property Data Types章节。
5.构建MailStore对象
MailStore中有一个PC结构和一个EntryID结构。
5.1 MailStore的 PC
PC的初始化需要BlockBO的数据,然而BlockBO需要有Nid才能找到数据。所有就先从PST特殊的nid表中得到MailStore的Nid是0x21。
特殊的nid表:

5.2 MailStore的 EntryID
用Nid(0x21)初始化好了PC结构之后,再去获取MailStore对象的EntryID。
消息存储PC的Properties中必定包含的几个属性如图所示:

可以看出key为0x35e0的属性中存放着MailFolder的EntryID,去MailStore.Properties获取这个key对应的value来初始化EntryID结构。
EntryID和Nid存在映射,通过MailFolder.EntryID就能找到MailFolder.Nid。这个映射通过很简单的内存拷贝就能实现,直接取出EntryID的最后四个字节就是Nid了。
EntryID和Nid映射关系图:

6.TC结构的实现及数据存储
现在已经通过MailStore对象中得到MailFolder的nid,这个nid可以来MailFolder的pc了。但是MailFolder还有用到一个很复制的结构TC,所以我们先实现这个TC。
TC是通过HN、BTH、TC_INFO、TCRowMatrix(非PST结构)这几个结构来实现的。
TC结构中的HN和BTH,TC的HN和4.1PC的HN的实现是一模一样的。TC的BTH也是通过本文4.2 PC的BTH的实现方式来实现的,但不同的是PC的BTH.BTHHEADE的初始化Hid是直接用的hidUserRoot,而TC里的BTH.BTHHEADE的初始化Hid要是从TC_INFO里取出来的。
6.1 TC_INFO
先用传进来的nid,初始化TC的BlockBO,然后取出BlockBO的数据,来初始化HN结构。HN中所有hidUserRoot(Hid)所对应的数据即是TC的TC_INFO结构。
TC_INFO结构图:

TC_INFO结构中的hidRowIndex(本质是个Hid),即是用来初始化TC.BTH的Hid。而hnidRows可能是个Nid,也可能是Hid,但不管是Nid还是Hid所对应的数据就是一个TCRowMatrix结构。
注:TC_INFO结构的详情在[MS-PST].pdf-2.3.4.1 TCINFO章节。
6.2 TCRowMatrix
TCRowMatrix就是TC的数据行矩阵,存放TC的实际数据。
依据TC_INFO.hnidRows的前五位,判断出TC_INFO.hnidRows是Hid还是Nid。然后取出其数据,初始化TCRowMatrix。
行矩阵包含TC的行和列的实际数据。数据在物理上按行排列;每一行包含每一列的数据。行矩阵中的每一行数据大小相同,排列方式相同,TCINFO标头结构中的rgib[TCI_bm]值指定了每一行的大小。然而,在许多情况下,行矩阵大于8千字节,因此不能放入单个数据块中,这意味着使用数据树将行矩阵存储在单独的数据块中。这意味着行数据是跨两个或多个数据块分区的,需要特殊的处理注意事项。TC的设计规定每个数据块必须存储整数行,这意味着行不能跨越两个块,每个块必须从一个新行开始。这还意味着,为了让客户机访问行矩阵中的特定行,它首先计算一个块中适合多少行,然后计算行数据所在块中的行索引。计算第n行的块索引和行索引的一般公式如下:

除了最后一个块之外,每个块的大小都必须是8192字节。如果没有,则认为文件已损坏。块的大小在公式中由sizeof(块)指定。
下图说明了如何组织行矩阵中的数据:

除了显示行矩阵的数据组织之外,该图还说明了RowIndex中的行与行矩阵中的行数据之间的关系。从这两个结构之间的虚线交叉可以看出,行矩阵数据是无序的,这使得搜索效率很低。RowIndex是使用dwRowID索引的嵌入式BTH实现的,它提供了主搜索键来查找行矩阵中的特定行。
还值得注意的是,由于不允许部分行,数据块的末尾可能有未使用的空间。
注:行矩阵的详情在[MS-PST].pdf-2.3.4.4 Row Matrix章节。

7.构建MailFolder对象
在PST体系结构中,单个根文件夹对象存在于消息存储的顶部,任意复杂的文件夹对象层次结构从该位置向下延伸,为所有消息传递对象提供结构化存储。
在LTP级别,文件夹对象是使用四个LTP结构表示的组合实体。具体来说,每个文件夹对象由一个PC组成,其中包含直接关联的属性,使用文件夹对象,并使用三个TC获取关于文件夹对象的内容、层次结构和其他相关信息。
前面我们通过MailStore对象获取的Nid,在这里把这个Nid的NID_TYPE改成相对应的值,即可得到这1个PC和3个TC的Nid了。

Hierarchy Table(HC)存放的是子文件的Nid、Contents Table(CT)存放的是message对象的Nid。
下图通过说明每个元素之间的相互关系将所有文件夹对象概念链接在一起:

前面的示例演示了文件夹对象的各个元素如何一起工作来表示文件夹对象层次结构。层次结构的等效“树视图”显示在右侧。在层次结构的顶部是文件夹对象1,它包含3个消息对象和2个子文件夹对象。PC包含与文件夹对象1相关的所有属性,其中层次结构表(HT)包含关于2个子文件夹对象的信息:文件夹对象2和文件夹对象3。然而,文件夹对象中的3个消息对象的信息存储在contents表(CT)中。虽然没有显示,但FAI目录表包含属于文件夹对象1的FAI消息对象。有关FAI消息对象的更多信息,请参见[MS-OXCMSG].pdf- 1.3.2章节。此外,文件夹对象1的HT的RowIndex包含必要的NID映射,该映射允许从文件夹对象1导航到文件夹对象2和3。这种关系递归地应用于文件夹对象2和文件夹对象3,并最终应用于文件夹对象4,如上图所示。注意使用层次结构表节点中的nidParent字段指向父文件夹对象的NID。还要注意,所有箭头最终都指向文件夹对象PC节点,可以用不同的nid_type替换该节点的NID,以访问其他TC。
注:所有根文件夹的下结构说明都在[MS-PST].pdf-2.4.4 Folders章节。
8.构建message对象
8.1 message对象的结构
遍历所有的MailFolder对象,取出每个MailFolder对象Contents Table(CT)存储的Nid,每一个Nid初始化一个message对象。
获取这个Nid对应的BlockBO,BlockBO里的数据和message对象的关系图如下:

前面的关系图演示了消息对象节点的各个组件。数据块包含消息对象PC,它包含与此消息对象关联的属性。消息对象的子节点可以包含许多对象,如:接收表TC、可选附件表TC、可选附件对象PCs,以及来自消息对象PC的无法直接放入消息对象PC堆中的可变大小数据。子节点BTree包含一个使用内部nid(即仅在消息对象节点内惟一)标识的子节点数组。每个子节点的内容主要由NID_TYPE标识。下表列出了可以在消息对象节点的子节点中找到的NID_TYPEs。
8.2 message对象里相关内容存储位置
一个message对象就是一封邮件,而邮件里的正文、标题、发件人等存储在message对象的PC.Properties里,按照不同的key值可以在PC.Properties找出相应的信息。但在[MS-PST].pdf没有找到相应的key值表,而是在[MS-PST].pdf-3.13 Sample Message Object章节的样例中看到不同的key值分别对应一些什么数据(样例的key值有8位,我们自取前面四位),接收人的信息在Recipient TC里(获取数据时,记得先用ExchangeProperty类处理数据在获取出来),详情看[MS-PST].pdf-2.4.5.3 Recipient Table章节。
8.3 附件
附件对象列表不为空则是有附件,附件名存储在Nid_TYPE为NID_TYPE_ATTACHMENT_TABLE(0X11)的TC里,附件内容存储在Nid_TYPE为NID_TYPE_ATTACHMENT(0x05)的PC.Properties里key为0x3701的数据里(附件内容的key值未在网上或书上查到相关内容,是自己多次实践出来key为0x3701的)。

你可能感兴趣的:(c++)