複合文檔的二進制存儲格式研究(word,xls,ppt...)

本文是研究DOC文件格式的第一部分。office中的doc等文件在外層都遵循複合文檔格式,而真正的doc內容主要是(也有其它流和倉庫)作爲複合文檔的一系列流而存在。下面爲轉載的文章正文,講解複合文檔格式。根據下面的講解,可以在doc文件中找到名字爲WordDocument的目錄,從而找到WordDocument

推薦一個查看office文檔結構的工具,用於輔助學習過程:

http://download.csdn.net/detail/shadow20080578/9356333

關於WordDocument流的分析,見:

http://blog.csdn.net/shadow20080578/article/details/50310257

SummaryInformation流:

http://blog.csdn.net/shadow20080578/article/details/50311985

DocumentSummaryInformation流:

http://blog.csdn.net/shadow20080578/article/details/50313095

複合文檔文件格式研究

 

 

複合文檔(Compound Document)是一種不僅包含文本而且包括圖形、電子表格數據、聲音、視頻圖象以及其它信息的文檔。可以把複合文檔想象成一個所有者,它裝着文本、圖形以及多媒體信息如聲音和圖象。目前建立複合文檔的趨勢是使用面向對象技術,在這裏,非標準信息如圖像和聲音可以作爲獨立的、自包含式對象包含在文檔中。Microsoft Windows就是使用這種技術,叫做“OLE2 storage file format”或“Microsoft Office compatible storage file format”。

當然Excel、Word等都是用這種格式存儲的。本文主要研究複合文檔的二進制結構。 

第一章        倉庫與流(Storages and Streams)

第二章        扇區與扇區鏈(Sectors and Sector Chains)

第三章        複合文檔頭(Compound Document Header)

第四章        扇區配置(Sector Allocation)

第五章        短流(Short-Streams)

第六章        目錄(Directory)

第七章        Excel文件實例剖析

 

 

第一章   倉庫與流

 

複合文檔的原理就像一個文件系統(文件系統:如FAT與NTFS)。複合文檔將數據分成許多流(Streams),這些流又存儲在不同的倉庫(Storages)裏。將複合文檔想象成你的D盤,D盤用的是NTFS(NT File System)格式,流就相當於D盤裏的文件,倉庫就相當於D盤裏的文件夾。

       流和倉庫的命名規則與文件系統相似,同一個倉庫下的流及倉庫不能重名,不同倉庫下可以有同名的流。每個複合文檔都有一個根倉庫(root storage)。

例:

 


 

 

第二章   扇區與扇區鏈

 

2.1 扇區與扇區標識

 

所有的流又分成更小的數據塊,叫做數據扇區(sectors)。Sectors 可能包含控制數據或用戶數據。

整個文件由一個頭(Header)結構以及其後的所有Sectors組成。Sectors的大小在頭中確定,且每個Sectors的大小都相同。

以下爲示意圖:

HEADER

SECTOR 0

SECTOR 1

SECTOR 2

SECTOR 3

SECTOR 4

SECTOR 5

SECTOR 6

Sectors 簡單的以其在文件中的順序列舉,一個扇區的索引(從0開始)叫做扇區標識(SID:sector identifier)。SID是一個有符號的32位的整型值。

如果一個SID的值非負,就表示真正存在的那個Sector;如果爲負,就表示特殊的含義。下表給出有效的特殊SID:

SID   Name                   Meaning                                           

–1   Free SID             空閒sector,可存在於文件中,但不是任何流的組成部分。

–2    End Of Chain SID      SID鏈的結束標記 (見2.2節)

–3   SAT SID                此Sector用於存放扇區配置表(SAT)(見4.2節)

–4   MSAT SID          此Sector用於存放主扇區配置表(MSAT)(見4.1節)

 

2.2 扇區鏈與扇區標識鏈

 

用於存儲流數據的所有Sectors的列表叫做扇區鏈(Sector Chain)。這些Sectors可以是無序的。因此用於指定一個流的Sectors的順序的SID數組就稱爲SID chain。一個SID chain總是以End Of Chain SID(-2)爲結束標記。

例:一個流由4個Sector組成,其SID鏈爲[1, 6, 3, 5, –2]。

 

 

流的SID鏈是通過扇區配置表構建的(見4.2節),但短流和以下兩種內部流除外:

1.主扇區配置表,其從自身構建SID鏈(每個扇區包含下一個扇區的SID)。

2.扇區配置表,其通過主扇區配置表構建SID鏈。

 

 

第三章   複合文檔頭

3.1 複合文檔頭的內容

 

複合文檔頭在文件的開始,且其大小必定爲512字節。這意味着第一個Sector的開始相對文件的偏移量爲512字節。

複合文檔頭的結構如下:

Offset    Size        Contents                                           

0            8            複合文檔文件標識:D0H CFH 11H E0H A1H B1H 1AH E1H

8            16          此文件的唯一標識(不重要, 可全部爲0)

24          2            文件格式修訂號 (一般爲003EH)

26          2            文件格式版本號(一般爲0003H)

28          2            字節順序規則標識(見3.2)::FEH FFH = Little-Endian

FFH FEH = Big-Endian

30          2            複合文檔中sector的大小(ssz),以2的冪形式存儲, sector實際大小爲s_size

= 2ssz 字節(一般爲9即512字節, 最小值爲7即128字節)

32          2            short-sector的大小(見5.1),以2的冪形式存儲, short-sector實際大

                            小爲s_s_size = 2sssz 字節(一般爲6即64字節,最大爲sector的大小)

34          10          Not used

44          4            用於存放扇區配置表(SAT)的sector總數

48          4            用於存放目錄流的第一個sector的SID (見6)

52          4            Not used

56          4            標準流的最小大小(一般爲4096 bytes), 小於此值的流即爲短流。

60          4            用於存放短扇區配置表(SSAT)的第一個sector的SID (見5.2),

或爲–2 (End Of Chain SID)如不存在。

64          4            用於存放短扇區配置表(SSAT)的sector總數

68          4            用於存放主扇區配置表(MSAT)的第一個sector的SID (見4.1),

或爲–2 (End Of Chain SID) 若無附加的sectors。

72          4            用於存放主扇區配置表(MSAT)的sector總數

76          436        存放主扇區配置表(MSAT)的第一部分,包含109個SID。

3.2 字節順序(Byte Order)

 

文件數據的二進制存儲有兩種方法Little-Endian 和 Big-Endian,但實際應用中只使用Little-Endian方法即:低位8字節存放在地址的低位,高位8字節存放在地址的高位。

例:一個32位的整數13579BDFH(轉爲十進制即324508639),以Little-Endian存放爲DFH 9BH 57H13H,以Big-Endian 存放爲 13H 57H 9BH DFH。(H下標表示十六進制)

 

3.3 扇區偏移量

       從頭中的信息可以計算出一個sector的偏移量(offset),公式爲:

sec_pos(SID) = 512 + SID ∙ s_size = 512 + SID ∙ 2 ssz

例:ssz = 10 and SID = 5:

sec_pos(SID) = 512 + SID ∙ 2 ssz = 512 + 5 ∙ 210 = 512 + 5 ∙ 1024 = 5632.

 

第四章   扇區配置

4.1 主扇區配置表

 

主扇區配置表(MSAT:master sector allocation table)是一個SID數組,指明瞭所有用於存放扇區配置表(SAT:sector allocation table)的sector的SID。MSAT的大小(SID個數)就等於存放SAT的sector數,在頭中指明。

MSAT的前109個SID也存放於頭中,如果一個MSAT的SID數多餘109個,那麼多出來的SID將存放於sector中,頭中已經指明瞭用於存放MSAT的第一個sector的SID。在用於存放MSAT的sector中的最後一個SID指向下一個用於存放MSAT的sector,如果沒有下一個則爲End Of Chain SID(-2)。

存放MSAT的sector的內容:(s_size表示sector的大小)

Offset            Size                   Contents                         

0                   s_size-4       MSAT的(s_size-4) / 4個SID的數組

s_size-4       4                   下一個用於存放MSAT的sector的SID,或-2(已爲最後一個)

 

最後一個存放MSAT的sector可能未被完全填滿,空閒的地方將被填上Free SID(-1)。

 

例:一個複合文檔需要300個sector用於存放SAT,頭中指定sector的大小爲512字節,這說明一個sector可存放128個SID。MAST有300個SID,前109個放於頭中,其餘的191個將要佔用2個sector來存放。此例假定第一個存放MSAT的sector爲sector 1,則sector 1包含127個SID。第128個SID指向一個用於存放MSAT的sector,假定爲sector 6,則sector 6包含剩下的64個SID(最後一個SID爲-2,其他的值爲-1)。

 

4.2 扇區配置表

 

 

       扇區配置表(SAT:sector allocation table)是一個SID數組,包含所有用戶流(短流除外)和內部控制流(the short-stream container stream, 見5.1, the short-sector allocation table, 見5.2, and the directory, 見7)的SID鏈。SAT的大小(SID個數)就等於複合文檔中所存在的sector的個數。

       SAT的建立就是通過按順序讀取MSAT中指定的sector中的內容。

存放SAT的sector的內容:(s_size表示sector的大小)

Offset            Size               Contents                             

0                   s_size            SAT的s_size / 4個SID的數組

                                                                 

       當通過SAT爲一個流創建SID鏈時,SAT數組的當前位置(array index)表示的就是當前的sector,而該位置存放的SID則指向下一個sector。

       SAT可能在任意位置包含Free SID(-1),這些sector將不被流使用。如果該位置包含End Of Chain SID(-2)表示一個流的結束。如果sector用於存放SAT則爲SAT SID(-3),同樣用於存放MSAT則爲MSAT SID(-4)。

       一個SID鏈的起點從用戶流的目錄入口(directory entry,見6.2節)或頭(內部控制流)或目錄流本身獲得。

例:一個複合文檔包含一個用於存放SAT的sector(sector 1)和2個流。

Sector 1的內容如下圖:

 

 

在位置1其值爲-3,表明Sector 1是SAT的一部分。

其中一個流爲內部目錄流,假定頭中指定其開始爲Sector 0,SAT中位置0的值爲2,位置2的值爲3,位置3 的值爲-2。因此目錄流的SID鏈爲[0, 2, 3, –2],即此目錄流存放於3個sector中。

目錄中包含一個用戶流的入口假定爲sector 10,從圖中可看出此流的SID鏈爲[10, 6, 7, 8, 9, –2]。

 

第五章   短流

5.1 短流存放流

 

當一個流的大小小於指定的值(在頭中指定),就稱爲短流(short-stream)。

短流並不是直接使用sector存放數據,而是內含在一種特殊的內部控制流——短流存放流(short-stream container stream)中。

       短流存放流象其他的用戶流一樣:先從目錄中的根倉庫入口(root storage entry)獲得第一個使用的sector,其SID鏈從SAT中獲得。然後此流將其所佔用的sectors分成short-sector,以便用來存放短流。此處也許較難理解,我們來打個比方:既然流組成符合文檔,而短流組成短流存放流,這兩者是相似的。把短流存放流當作複合文檔,那麼短流對應流,short-sector對應sector,唯一的不同是複合文檔有一個頭結構,而短流存放流沒有。short-sector的大小在頭中已經指定,因此可根據SID計算short-sector相對於短流存放流的偏移量(offset)。公式爲:

short_s_pos(SID) = SID ∙ short_s_size = SID ∙ 2 sssz

例:sssz = 6 and SID = 5:

short_s_pos(SID) = SID ∙ 2 sssz = 5 ∙ 26 = 5 ∙ 64 = 320.

 

5.2 短扇區配置表

 

       短扇區配置表(SSAT:short-sector allocation table)是一個SID數組,包含所有短流的SID鏈。與SAT很相似。

       用於存放SSAT的第一個sector的SID在頭中指定,其餘的SID鏈從SAT中獲得。

存放SSAT的sector的內容:(s_size表示sector的大小)

Offset           Size               Contents                                   

0                   s_size            SSAT的s_size / 4個SID的數組

                                                                

 

SSAT的用法與SAT類似,不同的是其SID鏈引用的是short-sector。

 

第六章   目錄

 

 

6.1 目錄結構

 

 

目錄(directory)是一種內部控制流,由一系列目錄入口(directory entry)組成。每一個目錄入口都指向複合文檔的一個倉庫或流。目錄入口以其在目錄流中出現的順序被列舉,一個以0開始的目錄入口索引稱爲目錄入口標識(DID: directory entry identifier)。

如下圖所示:

DIRECTORY ENTRY 0

DIRECTORY ENTRY 1

DIRECTORY ENTRY 2

DIRECTORY ENTRY 3

       目錄入口的位置不因其指向的倉庫或流的存在與否而改變。如果一個倉庫或流被刪除了,其相應的目錄入口就標記爲空。在目錄的開始有一個特殊的目錄入口,叫做根倉庫入口(root storage entry),其指向根倉庫。

       目錄將每個倉庫的直接成員(倉庫或流)放在一個獨立的紅黑樹(red-black tree)中。紅黑樹是一種樹狀的數據結構,本文僅簡單介紹一下,詳細情況請參考有關資料。

建構一個Red-Black tree的規則:

1. 每個節點(node)的顏色屬性不是紅就是黑。

2. 根節點一定是黑的。

3. 如果某個節點是紅的,那它的子節點一定是黑的。

4. 從根節點到每個葉節點的路徑(path)必須有相同數目的黑節點。

  ex:                 B               (用圖形來解說第4點,從根節點

                   /     /             到最底層的node,你會發現每個

                  B       B            path都恰好有3個black node)

                 / /    /   /        

                B   B  R     B

               /      / /   / /

              R      B   B R   R

                    / /

                   R   R

注意並不總是執行上述規則。安全的方法是忽略節點的顏色。

例:以第一章中的圖爲例

1.根倉庫入口描述根倉庫,它不是任何倉庫入口的成員,因此無需構建紅黑樹。

2.根倉庫的所有直接成員(“Storage1”, “Storage2”, “Stream1”, “Stream2”, “Stream3”, 和 “Stream4”)將組成一棵紅黑樹,其根節點的DID存放於根倉庫入口中。

3.倉庫Storage1只有一個成員Stream1,Stream1構成一棵紅黑樹,此樹只有一個節點。Storage1的目錄入口包含Stream1的DID。

4. 倉庫Storage2包含3個成員“Stream21”, “Stream22”, 和“Stream23”。這3個成員將構建一棵紅黑樹,其根節點的DID存放於Storage2的目錄入口中。

 

這種存放規則將導致每個目錄入口都包含3個DID:

1.在包含此目錄入口的紅黑樹中,此目錄入口的左節點的DID。

2.在包含此目錄入口的紅黑樹中,此目錄入口的右節點的DID。

3.若此目錄入口表示一個倉庫,則還包含此倉庫的直接成員所組成的另一顆紅黑樹的根節點的DID。

在構建紅黑樹的過程中,一個節點究竟作爲左還是右,是通過比較其名字來判斷的。一個節點比另一個小是指其名字的長度更短,如長度一樣,則逐字符比較。

規定:左節點<根節點<右節點。

 

6.2 目錄入口

 

       一個目錄入口的大小嚴格地爲128字節,計算其相對目錄流的偏移量的公式爲:dir_entry_pos(DID) = DID ∙ 128。

目錄入口的內容:

Offset    Size        Contents                                                  

0            64          此入口的名字(字符數組), 一般爲16位的Unicode字符,

以0結束。(因此最大長度爲31個字符)

64          2            用於存放名字的區域的大小,包括結尾的0。

(如:一個名字右5個字符則此值爲(5+1)∙2 = 12)

66          1            入口類型: 00H = Empty       03H = LockBytes (unknown)

01H = User storage       04H = Property (unknown)         02H = User stream 05H = Root storage

67          1            此入口的節點顏色: 00H = Red    01H = Black

68          4            其左節點的DID (若此入口爲一個user storage or stream)    若沒有左節點就爲-1。

72          4            其右節點的DID (若此入口爲一個user storage or stream),    若沒有右節點就爲-1。

76          4            其成員紅黑樹的根節點的DID (若此入口爲storage), 其他爲-1。

80          16          唯一標識符(若爲storage)(不重要, 可能全爲0)

 

96          4            用戶標記(不重要, 可能全爲0)

100        8            創建此入口的時間標記。大多數情況都不寫。

108        8            最後修改此入口的時間標記。大多數情況都不寫。

116            4            若此爲流的入口,指定流的第一個sector或short-sector的SID,若此爲根倉庫入口,指定短流存放流的第一個sector的SID,其他情況,爲0。  120        4             若此爲流的入口,指定流的大小(字節)若此爲根倉庫入口,指定短流存放流的大小(字節)其他情況,爲0。

124        4            Not used

時間標記(time stamp) 是一個符號的64位的整數,表示從1601-01-01 00:00:00開始的時間值。此值的單位爲10-7秒。

當計算時間標記是要注意閏年。

例:時間標記值爲01AE408B10149C00H

計算步驟                           公式                                  結果                  

轉爲十進制                                 t0 =  121,105,206,000,000,000

化成秒的餘數                     rfrac = t0 mod 107              rfrac = 0

化成秒的整數                    t1 = t0 / 107                        t1 =  12,110,520,600

化成分的餘數                     rsec = t1 mod 60                 rsec = 0

化成秒的整數                     t2 = t1 / 60                         t2 =  201,842,010

化成小時的餘數           rmin = t2 mod 60                rmin = 30

化成小時的整數          t3 = t2 / 60                         t3 =  3,364,033

化成天的餘數                     rhour = t3 mod 24               rhour = 1

化成天的整數                     t4 = t3 / 24                         t4 =  140,168

距1601-01-01的整年 ryear = 1601 + t4含的年     ryear = 1601 + 383 = 1984

到1984年還剩的天數  t5 = t4 – (1601-01-01           t5 = 140,168 – 139,887 = 281

到1984-01-01的天數)

距1984-01-01的月數 rmonth = 1 + t5含的月數    rmonth = 1 + 9 = 10

到10月還剩的天數             t6 = t5 – (1984-01-01           t6 = 281 – 274 = 7

到1984-10-01的天數)

10月最終天數            rday = 1 + t6                       rday = 1 + 7 = 8

 

第七章 Excel文件實例剖析

 

這章我們以一個Excel文件作爲實例來分析其二進制結構。看實例永遠是最好的學習方法,呵呵。

1.複合文檔頭

首先,讀取此文件頭,假定此Excel文件的頭(512字節)內容如下:
00000000H D0 CF 11 E0 A1 B1 1A E1 00 00 00 00 00 00 00 00
00000010H 00 00 00 00 00 00 00 00 3B 00 03 00 FE FF 09 00
00000020H 06 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00
00000030H 0A 00 00 00 00 00 00 00 00 10 00 00 02 00 00 00
00000040H 01 00 00 00 FE FF FF FF 00 00 00 00 00 00 00 00
00000050H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000060H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000070H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000080H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000090H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000000A0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000000B0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000000C0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000000D0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000000E0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000000F0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000100H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000110H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000120H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000130H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000140H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000150H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000160H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000170H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000180H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000190H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000001A0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000001B0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000001C0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000001D0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000001E0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000001F0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

1)前8個字節是固定的標識,表示這是一個複合文檔文件。

00000000H D0 CF 11 E0 A1 B1 1A E1 00 00 00 00 00 00 00 00

2)接着16個字節是唯一標識,其後的4個字節表示修訂號和版本號,可不用管它。

00000000H D0 CF 11 E0 A1 B1 1A E1 00 00 00 00 00 00 00 00

00000010H 00 00 00 00 00 00 00 00 3B 00 03 00 FE FF 09 00

3)接下來的2個字節是字節順序(Byte Order)標識符,總是FEH FFH。

00000010H 00 00 00 00 00 00 00 00 3B 00 03 00FE FF 09 00

4)接着的2個字節表示sector的大小,再2個字節表示short-sector的大小。分別是2^9=512字節和2^6=64字節。

00000010H 00 00 00 00 00 00 00 00 3B 00 03 00 FE FF09 00

00000020H 06 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00

5)接着的10個字節無有效數據,可忽略。

00000020H 06 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00

6)接着的4個字節表示用於存放扇區配置表(SAT)的sector總數,此例爲1個。

00000020H 06 00 00 00 00 00 00 00 00 00 00 0001 00 00 00

7)接着的4個字節表示用於存放目錄流的第一個sector的SID,這裏爲sector 10。

00000030H 0A 00 00 00 00 00 00 00 00 10 00 00 02 00 00 00

8)接着的4個字節無有效數據,可忽略。

00000030H 0A 00 00 00 00 00 00 00 00 10 00 00 02 00 00 00

9)接着的4個字節表示標準流的最小大小,這裏爲00001000H = 4096字節。

00000030H 0A 00 00 00 00 00 00 00 00 10 00 00 02 00 00 00

10)接着的4個字節表示用於存放短扇區配置表(SSAT)的第一個sector的SID,其後4個字節表示用於存放短扇區配置表(SSAT)的sector總數,這裏SSAT從sector 2開始,並只佔用1個sector。

00000030H 0A 00 00 00 00 00 00 00 00 10 00 0002 00 00 00

00000040H 01 00 00 00 FE FF FF FF 00 00 00 00 00 00 00 00

11)接着的4個字節表示用於存放主扇區配置表(MSAT)的第一個sector的SID,其後的4個字節表示用於存放主扇區配置表(MSAT)的sector總數,這裏SID爲-2,說明沒有附加的sector用於存放MSAT。

00000040H 01 00 00 00 FE FF FF FF 00 00 00 00 00 00 00 00

12)最後的436個字節包含MSAT的前109個SID。只有第一個SID有效,因爲上面已經說了SAT只佔用1個sector。從這裏可看出爲sector 0。其他的SID標記爲Free SID值爲-1。

00000040H 01 00 00 00 FE FF FF FF 00 00 00 0000 00 00 00

00000050H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

00000060H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

…… ……

2. 主扇區配置表

 

頭中已經包含整個MSAT,其SID鏈爲[0,-2]。

 

3. 扇區配置表

 

在此例中扇區配置表僅佔用sector 0,它開始於文件偏移量爲00000200H = 512字節處。假定內容如下:
00000200H FD FF FF FF FF FF FF FF FE FF FF FF 04 00 00 00
00000210H 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00
00000220H 09 00 00 00 FE FF FF FF 0B 00 00 00 FE FF FF FF
00000230H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000240H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000250H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000260H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000270H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000280H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000290H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000002A0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000002B0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000002C0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000002D0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000002E0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000002F0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000300H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000310H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000320H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000330H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000340H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000350H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000360H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000370H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000380H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000390H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000003A0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000003B0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000003C0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000003D0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000003E0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000003F0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF

因此可構建SAT的SID數組爲:
Array index       0    1    2     3     4    5    6    7    8    9    10     11    12   …
SID array     | –3 | –1 | –2 |  4 |  5 |  6 |  7 |  8 |  9 | –2 |  11 |  –2|  –1| … |

可看出sector 0被標記爲SAT SID(-3),sector 1和sector 12及其後的所有sector都未被使用。

4. 短扇區配置表

 

在頭中我們知道SSAT從sector 2開始只佔用一個sector。從SAT中可看出位置2的值爲-2,表示結束,故SSAT的SID鏈爲[2, –2]。
開始於文件偏移量爲00000600H = 1536字節處。
假定內容如下:

00000600H 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
00000610H 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00
00000620H 09 00 00 00 0A 00 00 00 0B 00 00 00 0C 00 00 00
00000630H 0D 00 00 00 0E 00 00 00 0F 00 00 00 10 00 00 00
00000640H 11 00 00 00 12 00 00 00 13 00 00 00 14 00 00 00
00000650H 15 00 00 00 16 00 00 00 17 00 00 00 18 00 00 00
00000660H 19 00 00 00 1A 00 00 00 1B 00 00 00 1C 00 00 00
00000670H 1D 00 00 00 1E 00 00 00 1F 00 00 00 20 00 00 00
00000680H 21 00 00 00 22 00 00 00 23 00 00 00 24 00 00 00
00000690H 25 00 00 00 26 00 00 00 27 00 00 00 28 00 00 00
000006A0H 29 00 00 00 2A 00 00 00 2B 00 00 00 2C 00 00 00
000006B0H 2D 00 00 00 FE FF FF FF 2F 00 00 00 FE FF FF FF
000006C0H FE FF FF FF 32 00 00 00 33 00 00 00 34 00 00 00
000006D0H 35 00 00 00 FE FF FF FF FF FF FF FF FF FF FF FF
000006E0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000006F0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000700H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000710H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000720H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000730H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000740H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000750H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000760H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000770H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000780H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
00000790H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000007A0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000007B0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000007C0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000007D0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000007E0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
000007F0H FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
因此可構建SSAT的SID數組爲:

 

 

從sector 54開始所有的short-sectors都未被使用。

 

5.目錄

 

頭中指明用於存放目錄流的第一個sector爲sector 10。目錄總是存放於sector中,而不是short-sector中。故從SAT構建SID鏈爲[10, 11, –2]。sector 10的偏移量爲00001600H = 5632, sector 11的偏移量爲00001800H = 6144。此例中sector的大小爲512字節,因此每個sector包含4個目錄入口(一個目錄入口128字節),故此目錄共包含8個入口。

 

<1>根倉庫入口(Root Storage Entry)

第一個目錄入口總是根目錄入口,假定其內容如下:

 

00001600H 52 00 6F 00 6F 00 74 00 20 00 45 00 6E 00 74 00

00001610H 72 00 79 00 00 00 00 00 00 00 00 00 00 00 00 00

00001620H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00001630H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00001640H 16 00 05 00 FF FF FF FF FF FF FF FF 01 00 00 00

00001650H 10 08 02 00 00 00 00 00 C0 00 00 00 00 00 00 46

00001660H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00001670H 00 00 00 00 03 00 00 00 80 0D 00 00 00 00 00 00

1)前64個字節是此入口名字的字符數組(爲16位的字符, 以第一個00結束),

       可看出此入口的名字是“Root Entry”。

00001600H 52 00 6F 00 6F 00 74 00 20 00 45 00 6E 00 74 00

00001610H 72 00 79 00 00 00 00 00 00 00 00 00 00 00 00 00

00001620H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00001630H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

2)接着2個字節表示用於存放上述字符數組的有效區域的大小,這裏是22字節,只有10個有效字符。

00001640H 16 00 05 00 FF FF FF FF FF FF FF FF 01 00 00 00

3)接着1個字節表示入口類型,根倉庫入口故爲05H。

00001640H 16 00 05 00 FF FF FF FF FF FF FF FF 01 00 00 00

4)接着1個字節表示入口節點顏色,這裏爲紅色,打破了根倉庫入口爲黑的規定。

00001640H 16 00 05 00 FF FF FF FF FF FF FF FF 01 00 00 00

5)接着4個字節表示其左節點的DID,再接着4個字節表示右節點的DID。對根倉庫入口來說都是-1。

00001640H 16 00 05 00 FF FF FF FF FF FF FF FF 01 00 00 00

6)接着4個字節表示根倉庫入口的成員構建的紅黑樹的根節點的DID,此例爲1。

00001640H 16 00 05 00 FF FF FF FF FF FF FF FF01 00 00 00

7)接着16個字節表示唯一標識符,說明這是一個倉庫,其後4字節表示用戶標識,再接着是兩個時間標記,各8字節,表明此倉庫的創建時間和最後修改時間。這些數據都可忽略。

00001650H 10 08 02 00 00 00 00 00 C0 00 00 00 00 00 00 46

00001660H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00001670H 00 00 00 00 03 00 00 00 80 0D 00 00 00 00 00 00

8)接着4個字節表示短流存放流的第一個sector的SID,其後4字節表示短流存放流的大小。此例中第一個sector爲sector 3,其大小爲00000D80H = 3456字節。

00001670H 00 00 00 00 03 00 00 00 80 0D 00 00 00 00 00 00

9)最後4個字節無有效數據,可忽略。

00001670H 00 00 00 00 03 00 00 00 80 0D 00 0000 00 00 00

<1>第二個目錄入口

第二個目錄入口(DID 1)假定其內容如下:

 

00001680H 57 00 6F 00 72 00 6B 00 62 00 6F 00 6F 00 6B 00

00001690H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

000016A0H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

000016B0H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

000016C0H 12 00 02 00 02 00 00 00 04 00 00 00 FF FF FF FF

000016D0H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

000016E0H 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

000016F0H 00 00 00 00 00 00 00 00 51 0B 00 00 00 00 00 00

 

用顏色標記的爲重要數據。入口的名字是“Workbook”,它表示一個流。其左節點的DID是2,右節點的DID是4。其第一個sector的SID是0,其大小是00000B51H = 2897字節,小於4096字節,故其存放於短流存放流中。

<3>剩下的目錄入口

剩下的目錄入口可按上面的方法讀取,下面給出目錄:

 

 

 

從根倉庫入口的第一個成員的DID(此爲1)可以找到根倉庫的所有成員。入口1有2個子節點,DID 2 和 DID 4,DID 2又有一個子節點DID 3,DID 3和DID 4都沒有子節點了。因此根倉庫包含DID 1、2、3、4這4個成員。

<4>流的SID鏈

短流存放流是存儲在sector中的,小於4096字節的用戶流(爲短流)存儲在短流存放流中,並使用SSAT構建SID鏈。

下表給出所有流的情況:

DID  流名                                配置表   SID 鏈                       

0       Root Entry (短流存放流)        SAT              [3, 4, 5, 6, 7, 8, 9, –2]

1       Workbook                        SSAT           [0, 1, 2, 3, 4, 5, …, 43, 44, 45, –2]

2   <01H>CompObj                  SSAT            [46, 47, –2]

3      <01H>Ole                                SSAT            [48, –2]

4  <05H>SummaryInformation    SSAT          [49, 50, 51, 52, 53, –2]

<5>流的讀取

 

短流存放流讀取其SID鏈中的所有sector,此例中將按順序讀取sector 3,4,5,6,7,8,9,故此流的大小爲512×7=3584字節。但是隻有前3456字節有效(根倉庫入口中指定的)。這3456字節被分成大小爲64字節的short-sector,一共54個。

現在我們來讀取流“<01H>CompObj”,其SID鏈爲[46, 47, –2],此流是一個短流。其數據存放在short-sector 46和short-sector 47中。short-sector 46在短流存放流中的偏移量爲2944字節,short-sector 47的偏移量爲3008字節。

參考文獻:

Daniel Rentz. Microsoft Compound Document File Format. 2006-Dec-21.

轉自:http://club.excelhome.net/dispbbs.asp?boardid=2&replyid=765438&id=227502&page=1&skin=0&Star=1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章