翻譯《文件系統取證分析》第13章

第13章 NTFS 數據結構

這是關於NTFS的第三章亦是最後一章,這裏我們將分析它的數據結構。前兩章寫了它的基本概念和怎麼去解析它。對許多人來說,目前爲止所涉及的知識已經足夠了,但我們中的其他人想知道更加多的知識。本章的組織方式是我們先了解數據結構的基礎元素,與其他的文件系統章節不同, 本章的編寫是爲了在章節11“NTFS 概念”和章節12“NTFS 解析”之後閱讀。章節的第一部分可以同時於章節11閱讀,但是之後的部分必須在完成章節12並明白了各種的屬性。在我們開始之前,記得這是非官方發佈的NTFS說明書。這裏面介紹的數據結構來自 Linux NTFS 組,正如我們將看到,它們與磁盤上存在的內容相匹配。我們可能無論如何不知道其中的一些附加的標記值或者細節。

基本概念

在這一節中,我們研究NTFS數據結構的基本概念。在第一小節,我們將研究使大型數據結構更可靠的設計特性。接下來,我們討論MFT entry和屬性頭的數據結構。

修正值

在我們查看任何指定的NTFS數據結構之前,我們需要去討論一種用於提高可靠性的存儲技術。NTFS將修正值合併到長度超過一個扇區的數據結構中。使用修正值,當數據結構寫入磁盤時,大型數據結構中每個扇區的最後兩個字節將替換爲簽名值。稍後將通過驗證所有相關扇區是否包含一致的簽名值來驗證數據的完整性。注意這裏修正值僅僅用在數據結構並不用在包含文件內容的扇區。

圖13.1. 一個多扇區數據結構,具有原數值然後將修正值應用到每個扇區的最後兩個字節.

使用修正的數據結構具有標識當前16位簽名值的頭字段和一個包含原始數值的數組。當數據結構寫到磁盤的時候,簽名值會加1,每個扇區的最後兩個字節將複製到數組,而簽名值會寫到每個扇區的最後兩個字節。讀數據結構的時候,操作系統應驗證每個扇區的最後兩個字節是否等於簽名值,並將原始數值從數組中取出來替換到數據。圖13.1展示了一個數據結構及其實際值,然後是寫入磁盤的版本。在第二個數據結構, 每個扇區的最後兩個字節已替換爲 0x0001。
修正程序用與檢測扇區和數據結構是否損壞,如果多個扇區數據結構中僅僅一個扇區被寫入了,這個修正值將不同於簽名值,操作系統將知道這個數據壞了。我們剖析樣本文件系統時,首先需要替換籤名值。

MFT Entries(File Records)

正如在第11章和第12章已經討論過的,主文件表(MFT)是NTFS的核心,對於每一個文件和目錄都有一個entry。MFT entries是一個固定大小幷包含一些小字段。至今爲止,這些entries的大小爲1024字節, 但是這個大小其實是在啓動扇區定義的。每一個MFT entry使用修正值,所以在磁盤上版本數據結構每個扇區最後兩個字節被替換爲修正值。參考前一節一段修正值的說明。MFT entry的數據結構字段如下表 13.1所示。

    表13.1。MFT entry的基本數據結構    
    Byte Range    Description                 Essential
    0–3         Signature ("FILE")             No
    4–5         Offset to fixup array             Yes
    6–7         Number of entries in fixup array     Yes
    8–15         $LogFile Sequence Number (LSN)        No
    16–17         Sequence value                 No
    18–19         Link count                 No
    20–21         Offset to first attribute         Yes
    22–23         Flags (in-use and directory)         Yes
    24–27         Used size of MFT entry             Yes
    28–31         Allocated size of MFT entry         Yes
    32–39         File reference to base record         No
    40–41         Next attribute id             No
    42–1023     Attributes and fixup values         Yes

標準的簽名值是"FILE",但如果chkdsk在其中發現錯誤的話一些entries也可能是"BAAD"。接下來兩個字段是修正值,其數組在42個字節之後,偏移值相對entry的開始。
LSN用於文件系統日誌,這在第12章“應用程序”分類那部分討論過。文件系統進行元數據更新時的日誌記錄,可以更快地修復損壞的文件系統。
無論entry分配與否,序列值都會增加,這是操作系統決定的。變量“link count”顯示這個MFT entry包含多少個目錄。如果文件創建了硬鏈接,每一個鏈接都會令這個數字加1。
我們使用相對於entry開頭的偏移值找到文件的第一個屬性。所有其他屬性都跟隨着第一個屬性。我們通過屬性頭中的size字段前進來找到它們。文件末尾標記0xfffffff存在於最後一個屬性之後。如果一個文件需要多於一個MFT entry,則其他條目將在MFT entry中舉有基 entry的 文件引用(不通順)。
字段flags僅有兩個值。當這個entry可用的時候,0x01位被設置,如果這個entry是目錄則0x02位被設置。
讓我們繼續來看原始MFT entry。要查看這個表,我們將使用 The Sleuth Kit(TSK)的icat來看$MFT文件的$DATA屬性,該條目是0。記住,我們可以通過在MFT entry地址後面添加屬性類型ID來指定TSK的任何屬性。在本例中,$DATA屬性的類型爲128。

# icat –f ntfs ntfs1.dd 0-128 | xxd
0000000: 4649 4c45 3000 0300 4ba7 6401 0000 0000 FILE0...K.d.....
0000016: 0100 0100 3800 0100 b801 0000 0004 0000 ....8...........
0000032: 0000 0000 0000 0000 0600 0000 0000 0000 ................
0000048: 5800 0000 0000 0000 1000 0000 6000 0000 X...........`...
[REMOVED]
0000496: 3101 b43a 0500 0000 ffff ffff 0000 5800 1..:..........X.
0000512: 0000 0000 0000 0000 0000 0000 0000 0000 ................
[REMOVED]
0001008: 0000 0000 0000 0000 0000 0000 0000 5800 ..............X.

這個輸出是小端序的,所以我們需要將它的數字順序反過來。我們看到"FILE"簽名標誌,然後第4第5展示修正數組在MFT entry的第48(0x0030)個字節。第6到7個字節展示這個數組舉有三個值。第16到17個字節展示MFT entry序號爲1,意思是這個entry是第一次使用。第18到19字節展示"link count"是1,於是我們明白它僅僅包含一個名字。第20到21個字節展示第一個屬性在偏移56(0x0038)個字節處。
標誌值在第22到23個字節處,這個entry在使用中(0x0001)。基礎entry值在第32到39處爲0,告訴我們這是基entry,並且第40到41字節展示下一個屬性ID是6。因此,我們應該期待屬性ID是1到5。
修正數組從第48個字節開始。前兩個字節是簽名值,即0x0058。下兩個字節是原始值,用來替換籤名值。我們看每個扇區最後兩個字節,第510到511和1022到1023個字節處,屏棄惡看到每一個都有0x0058。去處理entry,我們從修正數組中取出0x0000來替換這些值。跟着修正數組的是在字節56處的第一個屬性。這個文件屬性結尾在第504字節,結尾標記爲0xffffffff。此時後面已經沒有任何屬性了。
如果你想用TSK去查看任何MFT entry,你可以使用屬於 icat 的 dd 命令去跳過開頭而到正確的定位。你可以設置設置塊大小爲1024,即每個MFT entry的大小。舉個例子,去查看entry 1234 你可以使用如下命令
# icat -f ntfs ntfs1.dd 0 | dd bs=1024 skip=1234 count=1 | xxd

Attribute Header

一個MFT entry用屬性來填充,並且每個屬性含有相同的數據結構體頭,我們現在來研究它。圖13.2展示了一個經典的文件數據報和頭定位。常駐和非常駐屬性有輕微的不同因爲非常駐屬性需要保存run信息。
    圖 13.2. 有不同頭位置的典型文件。

前16字節和同類型的屬性都一樣,包含字段如表 13.2
    表 13.2 屬性的前16字節數據結構
    Byte Range     Description             Essential
    0–3         Attribute type identifier     Yes
    4–7         Length of attribute         Yes
    8–8         Non-resident flag         Yes
    9–9         Length of name             Yes
    10–11         Offset to name             Yes
    12–13         Flags                 Yes
    14–15         Attribute identifier         Yes

這些值提供了屬性的基本信息,包括類型,大小和名稱定位。大小用來找MFT entry下一個屬性,如果它是最後一個,那麼它的下一個將是0xffffffff。當屬性爲非常駐,那麼non-resident標記就會被設置爲1。壓縮爲(0x0001),加密爲(0x4000),散爲(0x8000)。屬性ID是在當前MFT entry中是唯一的值。偏移相對屬性的頭。常駐屬性的字段如下表13.3
    表 13.3 常駐屬性數據結構
    Byte Range    Description             Essential
    0–15         General header (see Table 13.2)    Yes
    16–19         Size of content         Yes
    20–21         Offset to content         Yes
屬性內容這些值簡單地告訴我們大小和位置(相對於屬性的開頭),亦可以叫它流。讓我們看一個例子。當我們預先切開MFT entry, 我們將看到屬性開始於第56個字節。我將在那裏獲得屬性,並重置輸出端的偏移,這樣就可以更加容易確定屬性頭偏移。
0000000: 1000 0000 6000 0000 0000 1800 0000 0000 ....`...........
0000016: 4800 0000 1800 0000 305a 7a1f f63b c301 H.......0Zz..;..

$STANDARD_INFORMAITION輸出顯示屬性類型在前4個字節,爲16(0x10)。第4到7個字節顯示大小爲96個字節(0x60)。第8個字節顯示這是一個常駐屬性(0x00),第9個字節顯示它沒有名稱(0x00)。標誌和ID值是0,在第12到第13字節和第14到第15字節。第16到19字節表明屬性72(0x48)字節大小。第20到第21字節表明它的位置是在屬性頭開始的24字節(0x18)。屬性中的報告長度爲24字節偏移和72字節的屬性長度等於總共96字節。
非常駐屬性含有不同的數據結構因爲它需要描述一個簇run中的任意數值。屬性字段在如下的表 13.4。
    表 13.4 非常駐屬性數據結構
    Byte Range    Description                         Essential
    0–15         General header (see Table 13.2)             Yes
    16–23         Starting Virtual Cluster Number (VCN) of the runlist     Yes
    24–31         Ending VCN of the runlist                 Yes
    32–33         Offset to the runlist                     Yes
    34–35         Compression unit size                     Yes
    36–39         Unused                             No
    40–47         Allocated size of attribute content             No
    48–55         Actual size of attribute content             Yes
    56–63         Initialized size of attribute content             No

回顧第8章“文件系統分析”,我們給邏輯文件地址定義了一個不同的名稱叫VCN。當需要多個MFT entries來描述單個屬性時,將使用VCN的開始和結束編號。例如一個$DATA屬性非常零碎,並且runs不能放進單一個MFT entry,它將分配第二個MFT entry。第二塊entry將包含一個$DATA屬性,在第一個條目的結束VCN之後,開始VCN等於。我們將在$ATTRIBUTE_LIST部分看到一個例子。壓縮單元大小描述在第11章有描述,僅用於壓縮屬性。
    圖 13.3 第一字節在run顯示長度字段爲1個字節,偏移字段爲兩個字節.

數據runlist中的偏移是相對屬性的開頭。runlist的格式非常有效單有點令人困惑。它的長度可變,但至少爲一個字節。數據結構的第一個字節被組織成高4位和低4位(也稱爲半字節)。4個最低有效位包含runlist長度字段中的字節數,該字段位於頭字節之後。4個高位有效位包含run偏移字段的字節數,該字段位於長度字段之後。我們可以看圖13.3的一個例子。第一個字節顯示run長度字段是1字節和run偏移字段是兩個字節。
這些值以簇大小爲單位,並且偏移字段相對前一個偏移是一個有符號值。例如,屬性中第一個run的偏移是相對文件系統的開始,第二個run的偏移則是相對上一個偏移。負數它的最高有效位是1,如果要將該值插入計算器以轉換該值,則必須根據需要添加儘可能多的1。將把它組成一個完整的32或者64位數。例如,如果這個值是0xf1,則需要在轉換器中輸入0xfffffff1。
要查看非常駐屬性,我們返回之前分析的entry,並進一步查看$DATA屬性。屬性內容如下所示,偏移值相對屬性的開始。
0000000: 8000 0000 6000 0000 0100 4000 0000 0100 ....`.....@.....
0000016: 0000 0000 0000 0000 ef20 0000 0000 0000 ......... ......
0000032: 4000 0000 0000 0000 00c0 8300 0000 0000 @...............
0000048: 00c0 8300 0000 0000 00c0 8300 0000 0000 ................
0000064: 32c0 1eb5 3a05 2170 1b1f 2290 015f 7e31 2...:.!p..".._~1
0000080: 2076 ed00 2110 8700 00b0 6e82 4844 7e82 v..!.....n.HD~.

開始的4字節顯示屬性類型是128(0x80),第二組4字節標識其總大小是96字節(0x60)。第8個字節是1,說明這個是非常駐屬性,第9個字節是0,顯示名稱屬性長度是0,因此這是個默認的$DATA屬性,並非一個ADS。第12到13字節是標誌,值爲0,意思是這個屬性沒有加密或壓縮。
非常駐信息從第16字節開始,第16到23字節顯示這個run的起始VCN是0。run的結束VCN在第24到31字節,值爲8431(0x20ef)。字節32到33顯示runlist的偏移是相對開始的64字節(0x0040)。第40到47字節,48到55,56到63,分別爲“分配的”,“實際的”,和“初始化的”在空間中的總大小,並且它們的值都相等,都是8634368字節(0x0083c000)。
在第64字節,我們最終獲得runlist。我將再一次複製相關的輸出:
0000064: 32c0 1eb5 3a05 2170 1b1f
回顧第一個字節中的高和低4位,表示了其他字段的大小。第64字節的低4位表示run的長度字段有兩個字節,高4位表示偏移字段佔用3個字節。爲了確定run的長度,我們檢查第65到66字節,給我們7872簇(0x1ec0)。接下來的3個字節,字節67到69,用於表示偏移,簇342709(0x053ab5)。因此,第一個run開始於簇342709,連續7872簇。
下一個run位於於上一個run之後,即第70字節開始。我們可以看到長度字段是1字節,偏移字段是2字節。長度值在字節71,爲112(0x70)。偏移值在字節72到73,爲7963(0x1f1b)。這個偏移是一個有符號值,相對上一個偏移,所以我們把7963和342709相加,得到350672。因此第二個run位於簇350672和連續112簇。我會把剩下的runlist留給你來解析。

Standard File Attributes

前一節概述瞭如何處理一個MFT entry和屬性頭。每個屬性頭指向可以找到內容的一個常駐或非常駐位置。本節說明如何處理每個不同的屬性內容類型。

$STANDARD_INFORMAITION 屬性

$STANDARD_INFORMAITION 屬性的標識符是16,總是常駐幷包含文件或者目錄的基本元數據。它存在於每一個文件和目錄,通常是第一個屬性,因爲它有最低的類型標識符。它的字段(有必要)在表13.5。
    表13.5 $STANDARD_INFORMATION屬性的數據結構
    Byte Range    Description                     Essential
    0–7                Creation time                     No
    8–15              File altered time                 No
    16–23         MFT altered time                 No
    24–31         File accessed time                 No
    32–35         Flags (see Table 13.6)                 No
    36–39         Maximum number of versions             No
    40–43         Version number                     No
    44–47         Class ID                     No
    48–51         Owner ID (version 3.0+)             No
    52–55         Security ID (version 3.0+)             No
    56–63         Quota Charged (version 3.0+)             No
    64–71         Update Sequence Number (USN) (version 3.0+)    No

四個時間值以100納秒爲單位自1601年1月1日的UTC時間差保存。同樣的時間字段也存在於$FILE_NAME屬性,但這是Windows在查看屬性時候顯示,這些是更新的。ID值用於應用程序級的功能或安全。安全ID值是$Secure文件的索引,不同於Windows SID值。標誌值在表13.6中給出。
    Table 13.6. $STANDARD_INFORMATION屬性的標誌位值
    Flag Value    Description                         Essential
    0x0001         Read Only                         No
    0x0002         Hidden                             No
    0x0004         System                             No
    0x0020         Archive                         No
    0x0040         Device                             No
    0x0080         #Normal                         No
    0x0100         Temporary                         No
    0x0200         Sparse file                         No
    0x0400         Reparse point                         No
    0x0800         Compressed                         No
    0x1000         Offline                         No
    0x2000         Content is not being indexed for faster searches    No
    0x4000         Encrypted                         No

很多標誌和FAT文件系統中的一樣,在那裏可以找到對它們的描述。加密和稀疏標誌在屬性頭中給出。因此我認爲它們在這個定位中並非必須。這是可以商榷的,因爲其他人可能會聲稱此標誌是必須的,而MFT entry頭值則不是必需的。
讓我們看看$STANDARD_INFORMATION屬性。我們可以查看屬性並通過使用 icat 指定屬性類型。這將刪除標準頭自動提供給我們內容部分。
# icat -f ntfs ntfs1.dd 0-16 | xxd
0000000: 305a 7a1f f63b c301 305a 7a1f f63b c301 0Zz..;..0Zz..;..
0000016: 305a 7a1f f63b c301 305a 7a1f f63b c301 0Zz..;..0Zz..;..
0000032: 0600 0000 0000 0000 0000 0000 0000 0000 ................
0000048: 0000 0000 0001 0000 0000 0000 0000 0000 ................
0000064: 0000 0000 0000 0000 ........

屬性的內容部分在$MFT文件的前8個字節,顯示給我們創建時間,它的值和其他4個時間字段一樣。字節32到35是標誌值,0x00000060,包括了隱藏和系統屬性位。字節36到39和40到43顯示這個文件版本字段還未被使用,44到47字節展示了類ID爲0。它的自身ID在字節48到51處,也是0,安全ID在字節52到55處爲1。剩餘值都是0,這在$MFT之中並不奇怪,因爲它通常不應用任何用戶的配額,並且大多數系統並沒有更新日誌記錄的功能,所以不會分配USN。

$FILE_NAME Attribute

$FILE_NAME 屬性的標識符是48,它有兩個作用。它放在一個MFT entry內,存儲了文件名和父目錄信息,它在目錄索引中使用。在MFT entry中使用的時候,它不包含任何必需信息,在一個目錄索引中使用的時候除外。
一個標準文件或者目錄,它將是第二個屬性並且總是常駐的。如果一個文件需要多個MFT entries,$ATTRIBUTE_LIST屬性將在$STANDARD_INFORMATION屬性和$FILE_NAME屬性之間出現。$FILE_NAME屬性的字段如下表13.7所示。
    表13.7 $FILE_NAME屬性數據結構
    Byte Range    Description                     Essential
    0–7         File reference of parent directory        No
    8–15         File creation time                 No
    16–23         File modification time                 No
    24–31         MFT modification time                 No
    32–39         File access time                 No
    40–47         Allocated size of file                 No
    48–55         Real size of file                 No
    56–59         Flags (see Table 13.6)                 No
    60–63         Reparse value                     No
    64–64         Length of name                     Yes / No
    65–65         Namespace (see Table 13.8)             Yes / No
    66 +         Name                         Yes / No
當$FILE_NAME屬性用於目錄索引中的時候,最後三個名稱字段是必需的,但在文件的MFT entry中使用此屬性時則不是必需的。標誌字段和$STANDARD_INFORMATION裏面的值一樣,它們都在之前列出來了。
命名空間字節標識接下來的name字段遵循的規則。其數值見表13.8
    表13.8 $FILE_NAME命名空間字段值
    Name space value    Description
    0             POSIX: 名稱區分大小寫,並允許除'/'和NULL字符之外的所有的Unicode字符
    1             Win32: 名稱不區分大小寫,允許絕大多數的Unicode字符,除了特殊值,如'/', '\', ':', '>', '<', and '?'。
    2             DOS: 名稱不區分大小寫,全大寫,並且無特殊字符。長度必須小於等於8個字符,擴展名必須小於等於3個字符。
    3             Win32 & DOS: 當一個原始名已符合DOS命名空間並且不需要兩個名稱的時候使用。
要查看$FILE_NAME屬性,我們將再次看$MFT和指定的屬性類型48。
# icat -f ntfs ntfs1.dd 0-48 | xxd
0000000: 0500 0000 0000 0500 305a 7a1f f63b c301 ........0Zz..;..
0000016: 305a 7a1f f63b c301 305a 7a1f f63b c301 0Zz..;..0Zz..;..
0000032: 305a 7a1f f63b c301 0040 0000 0000 0000 0Zz..;...@......
0000048: 0040 0000 0000 0000 0600 0000 0000 0000 .@..............
0000064: 0403 2400 4d00 4600 5400 ..$.M.F.T.

前8個字節是文件參考號,其中高兩個字節是順序號和低6字節是MFT entry。因此父目錄是MFT entry 5,並且順序是5,這是根目錄的入口。接下來的8個字節是創建時間,它和屬性裏面其它3個時間值一樣。
字節40到47和48到55分別是文件佔用大小和實際大小。它們的值都是16,384字節(0x4000)。實際上,這個文件的$DATA屬性是8,634,360字節,所以這顯然不準確。許多文件將這些大小設置爲0,但是它用於目錄索引的時候它是準確的。
標誌值在56到57字節,設置爲0x0006, 這是隱藏和系統標誌。它們和我們看到$STANDARD_INFORMATION中的一樣。字節64顯示這個名稱有4字母長,字節65顯示它的命名空間爲3,這是符合Win32&DOSde 。我們可以看到名稱在字節66處開始,使用UTF-16編碼的Unicode表示。名字是“$MFT”。
最後一個例子,考慮一個具有兩個$FILE_NAME屬性的文件,因爲Windows必須有一個DOS名存在。這個文件有$FILE_NAME屬性,同時有一個DOS命名空間和一個Win32命名空間。我們將不會詳細分析它們的細節,但輸出如下:
# icat -f ntfs ntfs1.dd 5009-48-2 | xxd
0000000: 3920 0000 0000 0300 00b6 89a9 086a c401 9 ...........j..
0000016: 00b6 89a9 086a c401 00b6 89a9 086a c401 .....j.......j..
0000032: 00b6 89a9 086a c401 0000 0000 0000 0000 .....j..........
0000048: 0000 0000 0000 0000 2020 0000 0000 0000 ........ ......
0000064: 0b01 3500 3700 3300 3900 3800 3400 3000 ..5.7.3.9.8.4.0.
0000080: 3800 6400 3000 3100 8.d.0.1.

需要注意的是字節65顯示這個命名空間是1,即Win32。這個entry中的名字是"57398408d01."。現在我們將查看下一個$FILE_NAME屬性,但是它的屬性標識是3:
# icat -f ntfs ntfs1.dd 5009-48-3 | xxd
0000000: 3920 0000 0000 0300 00b6 89a9 086a c401 9 ...........j..
0000016: 00b6 89a9 086a c401 00b6 89a9 086a c401 .....j.......j..
0000032: 00b6 89a9 086a c401 0000 0000 0000 0000 .....j..........
0000048: 0000 0000 0000 0000 2020 0000 0000 0000 ........ ......
0000064: 0802 3500 3700 3300 3900 3800 3400 7e00 ..5.7.3.9.8.4.~.
0000080: 3100 1.

這個屬性有個命名空間在65字節處是2,即DOS。它在這個entry中的名字是"573984~1."。

$DATA Attribute

$DATA屬性最容易理解因爲它沒有本地結構。在頭之後,只有與文件內容相對應的原始內容。它的標識類型是128並且無最大最小大小。如果內容超過700字節,它可能是一個非常駐屬性。對於大多數文件,這是MFT entry中的最後一個屬性。注意,除了索引屬性外,目錄可以包含$DATA屬性

$ATTRIBUTE_LIST Attribute

$ATTRIBUTE_LIST 屬性存在於MFT entry中用來顯示其他屬性可以位於何處。一個文件如果屬性頭放不進一個MFT entry的時候,它包含了一個列表是每一個文件或者目錄屬性的入口。它的屬性類型標識是32,並且每一個列表入口有下面表13.9的字段。
    表13.9 $ATTRIBUTE_LIST屬性的列表入口數據結構。
    Byte Range     Description                         Essential
    0–3         Attribute type                         Yes
    4–5         Length of this entry                     Yes
    6–6         Length of name                         Yes
    7–7         Offset to name (relative to start of this entry)     Yes
    8–15         Starting VCN in attribute                 Yes
    16–23         File reference where attribute is located         Yes
    24–24         Attribute ID                         Yes
當需要多個MFT entries來描述單個屬性時,使用起始VCN值。出現這個情況的時候,附加entries將有非0的起始VCN值。屬性頭將顯示它有一個非0的起始VCN。
讓我們查看一個含有一個$ATTRIBUTE_LIST屬性的文件。
# icat -f ntfs ntfs1.dd 5009-32 | xxd
0000000: 1000 0000 2000 001a 0000 0000 0000 0000 .... ...........
0000016: 9113 0000 0000 0800 0000 0000 0000 0000 ................
0000032: 3000 0000 2000 001a 0000 0000 0000 0000 0... ...........
0000048: 9113 0000 0000 0800 0300 0000 0006 0000 ................
0000064: 3000 0000 2000 001a 0000 0000 0000 0000 0... ...........
0000080: 9113 0000 0000 0800 0200 0200 502d 40bc ............P-@.
0000096: 8000 0000 2000 001a 0000 0000 0000 0000 .... ...........
0000112: 3713 0000 0000 1200 0000 0000 1000 0000 7...............
0000128: 8000 0000 2000 001a 2014 0000 0000 0000 .... ... .......
0000144: ad13 0000 0000 0800 0000 0000 0000 0000 ................

前4個字節顯示第一個entry的類型,即16(0x10),因此是$STANDARD_INFORMATION屬性。字節4到5顯示這個列表entry長度是32字節(0x0020)和字節16到31顯示這個屬性是位於MFT entry 5,009(0x1391)中,這是我們目前正在查看的。
接下來的兩個entries在字節32和64開始,用於$FILE_NAME屬性,它的標識是48(0x30)。這些屬性也位於當前MFT entry。
字節96是第一個$DATA屬性的第一個entry的開始。字節104到111顯示這個$DATA屬性用於該屬性的VCN 0,字節112到117顯示屬性位於MFT entry的4,919(0x1337)。
$DATA屬性的第二個entry在字節128處開始。我們可以看出它們是同一個$DATA屬性的一部分,因爲它們的ID值都是0。字節136到143告訴我們第二個entry開始於VCN 5,152(0x1420)。換句話說,$DATA屬性在第一個entry已經有足夠的空間在MFT entry中描述前面的5,152簇了。剩下的簇 run 存儲在MFT entry 5,037(0x13ad)中的$DATA屬性中,我們可以第144到149字節處看到。
圖13.4顯示這個文件的概要。它有一個$STANDARD_INFORMATION和兩個$FILE_NAME屬性位於基礎MFT entry 5,009 並且$DATA屬性的頭定位在 4,919和5,037。
    圖13.4 樣本映像中屬性列表的入口布局。

 

回顧第12章,非基礎MFT entries將不具有標準的$FILE_NAME和$STANDARD_INFORMATION屬性。我們可以通過查看本例中的一個entry來驗證這一點。運行istat輸出一個非基礎entry 4919 輸出如下:
# istat –f ntfs ntfs1.dd 4919
MFT Entry Header Values:
Entry: 4919 Sequence: 18
Base File Record: 5009
$LogFile Sequence Number: 66117460
Allocated File
Links: 0
[REMOVED]
Attributes:
Type: $DATA (128-0) Name: $Data Non-Resident size: 5787792
929409 929410 929411 929412 929413 929414 929415 929416
[REMOVED]

這個MFT entry僅有一個$DATA屬性,並且我們可以看到這個頭顯示基礎記錄在 5,009入口處。"link count"是0,因爲沒有名稱指向它。

$OBJECT_ID Attribute

$OBJECT_ID屬性有一個類型標識爲64,並且儲存一個文件的128位全局對象標識可以使用的地址到文件代替它的名稱。它允許一個文件去找到它的名字被修改的事件。"\$Extend\$ObjId"索引根據文件的對象ID排序幷包含文件的引用地址每一個文件都可以找到。這個屬性僅包含4個字段,並且常規僅僅第一個是被定義的,表13.10 是給出來的字段。
    表 13.10 $OBJECT_ID屬性數據結構
    Byte Range     Description             Essential
    0–15         Object ID             Yes
    16–31         Birth volume ID         No
    32–47         Birth object ID         No
    48–63         Birth domain ID         No
許多分配了對象ID的文件只有第一個值,並且屬性大小是16字節,$VOLUME文件包含一個$OBJECT_ID屬性,它展示如下:
# icat -f ntfs img.dd 3-64 | xxd
0000000: fe24 b024 e292 fe47 95ac e507 4bf5 6782 .$.$...G....K.g.

$REPARSE_POINT Attribute

$REPARSE_POINT屬性有一個屬性標識是192,它是文件的重新解析點。重解析點用於卷的符號鏈接,連接和裝載點。微軟定義一些$REPARSE_POINT屬性內容,但也可以開發特定於應用程序的屬性。連接和裝載點的內容結構在表13.11中。
    表 13.11 $REPARSE_POINT屬性連接和裝載點的數據結構。
    Byte Range     Description                         Essential
    0–3         Reparse type flags                     Yes
    4–5         Size of reparse data                     Yes
    6–7         Unused                             No
    8–9         Offset to target name (relative to byte 16)         Yes
    10–11         Length of target name                     Yes
    12–13         Offset to print name of target (relative to byte 16)     Yes
    14–15         Length of print name                     Yes
連接和裝載點的類型標記將設置 0xa0000000 標記。這裏我們看到一個重解析點鏈接到"C:\windows":
# icat -f ntfs ntfs2.dd 167-192 | xxd
0000000: 0300 00a0 2800 0000 0000 1c00 1e00 0000 ....(...........
0000016: 5c00 3f00 3f00 5c00 6300 3a00 5c00 7700 \.?.?.\.c.:.\.w.
0000032: 6900 6e00 6400 6f00 7700 7300 0000 1200 i.n.d.o.w.s.....

字節8到9顯示到目標名稱的偏移是0字節,所以它開始在字節16。它的長度在字節10到11處,我們可以看到它是28字節(0x1c)。使用Unicode字符,我們看到目標名稱是"\??\C:\windows."

索引屬性和數據結構

前面的章節涵蓋了適用於所有文件的屬性和概念。本節重點關注索引特有的數據結構和屬性。回想一下,索引背後基本概念是排序樹中的數據結構。該樹有一個或者多個結點,並且每個結點含有一個或者多個索引入口。樹根位於$INDEX_ROOT屬性中,其他結點索引記錄位於$INDEX_ALLOCATION屬性。$BITMAP屬性用來管理索引記錄的申請狀態。
在本節,我們從外部開始學習。我們將從屬性開始,然後描述它們共同的數據結構。

$INDEX_ROOT Attribute

$INDEX_ROOT屬性總是常駐的,它的類型標識符爲144。它總是索引樹的根並且只能存儲一個索引入口的小列表。$INDEX_ROOT屬性有一個16字節的頭,跟着是結點頭和索引鏈表入口。關於這個可以在圖13.5中看到。結點頭將在"索引結點頭數據結構"部分中描述,索引入口將在"索引入口數據結構"部分中描述。在這裏,我們將關注在$INDEX_ROOT頭。

圖 13.5. $INDEX_ROOT屬性中的頭和結點頭和索引入口的內部佈局。

表 13.12 $INDEX_ROOT屬性頭的數據結構
    
Byte Range    Description                                 Essential
    0–3         Type of attribute in index (0 if entry does not use an attribute)    Yes
    4–7         Collation sorting rule                             Yes
    8–11         Size of each index record in bytes                     Yes
    12–12         Size of each index record in clusters                     Yes
    13–15         Unused                                     No
    16+ Node header (see Table 13.14)                             Yes
索引入口包含數據結構標識屬性類型,它們是如何排序的,和$INDEX_ALLOCATION屬性中每一個index record的大小。字節8到11是大小的值,單位是字節,字節12是簇數或大小的對數。"$Boot File"部分更加詳細地描述這種編碼方式。注意這個index record大小也在引導扇區種給出。
要查看$INDEX_ROOT屬性的內容,我們使用icat並提供144類型:
# icat -f ntfs ntfs1.dd 7774-144 | xxd
0000000: 3000 0000 0100 0000 0010 0000 0400 0000 0...............
0000016: 1000 0000 a000 0000 a000 0000 0100 0000 ................
[REMOVED]

字節0到3表示索引中的屬性是屬性類型48(0x30),即$FILE_NAME屬性,字節8到11顯示每個index record將是4096個字節。

$INDEX_ALLOCATION Attribute

大目錄不能把它的所有索引放在常駐$INDEX_ROOT屬性裏面,所以它們需要一個非常駐$INDEX_ALLOCATION屬性。$INDEX_ALLOCATION屬性有index record填充。一個index record有一個靜態的大小並在排序樹種包含一個結點。index record的大小在$INDEX_ROOT屬性頭種定義和在引導扇區中定義,但通常大小是4096字節。$INDEX_ALLOCATION屬性有類型標識符爲160並且和$INDEX_ROOT屬性同時存在。
每個index record開始用特定的數據結構頭,跟着是一個結點頭和一個index入口鏈表。在$INDEX_ROOT屬性中結點頭和index入口的數據結構都是一樣的。第一個index record在屬性的0字節開始。我們可以在圖13.6中看到這個,$INDEX_ALLOCATION屬性有兩個index record。
    圖13.6 $INDEX_ALLOCATION屬性和它的記錄頭,結點頭和索引入口在內部的佈局。

index record頭的值在下面的表13.13
    表13.13 index record頭的數據結構
    Byte Range    Description                         Essential
    0–3         Signature value ("INDX")                 No
    4–5         Offset to fixup array                     Yes
    6–7         Number of entries in fixup array             Yes
    8–15         $LogFile Sequence Number (LSN)                 No
    16–23         The VCN of this record in the full index stream        Yes
    24+         Node header (see Table 13.14)                 Yes
前4個字段幾乎和MFT entry中的字段相同,但簽名值不同。參考本章開始討論的修正數組。

VCN值在字節16到23確定在樹中的標識。$INDEX_ALLOCATION屬性由多個index record填充,按照順序。VCN值在頭部標識這個index record放到大緩衝中。當一個index entry指向它的孩子結點,它在結點的index record頭中使用VCN地址。
讓我們看$INDEX_ALLOCATION屬性的內容從同一個目錄。
# icat –f ntfs ntfs1.dd 7774-160 | xxd
0000000: 494e 4458 2800 0900 4760 2103 0000 0000 INDX(...G`!.....
0000016: 0000 0000 0000 0000 2800 0000 f808 0000 ........(.......
[REMOVED]

我們可以在第一行看到簽名值"INDX", 字節4到5和6到7顯示修復記錄值。字節16到23顯示這個index record是VCN 0 緩衝。結點頭開始在字節24處,$INDEX_ALLLOCATION屬性是8192字節大小,所以有空間給其他的index record。它在字節4,096處開始:

[REMOVED]
0004096: 494e 4458 2800 0900 ed5d 2103 0000 0000 INDX(....]!.....
0004112: 0400 0000 0000 0000 2800 0000 6807 0000 ........(...h...
0004128: e80f 0000 0000 0000 3b00 0500 6900 c401 ........;...i...
[REMOVED]

我們看到簽名值"INDX"並且字節4,112到4,119展示給我們這個VCN 4(在文件系統中每一簇1,024字節)。我們將在討論結點頭和index 入口之後回到這個例子。

$BITMAP Attribute

在之前的部分,我們看一個$INDEX_ALLOCATION屬性有兩個4,096字節的index record。它可能有一些index record還未被使用。$BITMAP屬性用來跟蹤在$INDEX_ALLOCATION屬性中的每一個index record分配。通常Windows僅僅分配我們需要的index record,但在刪除多個文件後,或者由於每個簇大於一個index record,一個目錄可能有不需要的記錄。注意這個屬性也用來跟蹤$MFT分配了哪些MFT entry。
$BITMAP屬性的標識符爲176並用字節組成,並且每一位對應一個index record。我們可以用icat來查看前一個目錄的$BITMAP屬性。

# icat -f ntfs ntfs1.dd 7774-176 | xxd
0000000: 0300 0000 0000 0000 ........

我們可以在0字節處看到值0x03,二進制的0000 0011。因此,index record0和1已經分配了。

Index Node Header Data Structure(索引結點頭數據結構)

目前爲止,我們已經看了$INDEX_ROOT和$INDEX_ALLOCATION屬性,並且它們有相同的頭數據即結點頭和索引入口列表。在這一節,我們描述結點頭數據結構。這個頭出現在$INDEX_ROOT和每一個index record數據結構和用於顯示這個索引入口列表的開始和結束。這個頭的字段在下面的表13.14。

表 13.14 索引結點頭的數據結構。

    Byte Range     Description                                         Essential
    0–3         Offset to start of index entry list (relative to start of the node header) Yes
    4–7         Offset to end of used portion of index entry list (relative to start of the node header)    Yes
    8–11         Offset to end of allocated index entry list buffer (relative to start of the node header)    Yes
    12–15         Flags                                                 No

一個$INDEX_ROOT結點中的索引入口在緊接着結點頭之後,但是因爲修正值,索引入口在一個索引緩衝中可能不存在。當我們看已經刪除的index入口剩餘數據,我們將檢查緩衝中已用部分結束和分配緩衝結束之間的數據。
flags字段只有一個標誌,當列表中的entry指向子結點時將設置0x01標誌。每個index entry都存在這樣的標誌。
讓我們仔細查看上前面的屬性。$INDEX_ROOT屬性有下面的數據:

# icat -f ntfs ntfs1.dd 7774-144 | xxd
0000000: 3000 0000 0100 0000 0010 0000 0400 0000 0...............
0000016: 1000 0000 a000 0000 a000 0000 0100 0000 ................
[REMOVED]

結點頭從字節16開始,字節16到19顯示列表從16字節(0x10)開始,即32字節,到160(0xa0)字節結束,字節176。在這種情況下,分配空間和使用空間一樣,因爲它們來自同一個$INDEX_ROOT屬性,它是常駐並且儘可能小。字節28處有0x01標誌,所以這是這個節點的子結點(定位到$INDEX_ALLOCATION)。
現在讓我們再次查看$INDEX_ALLOCATION屬性:

# icat –f ntfs ntfs1.dd 7774-160 | xxd
0000000: 494e 4458 2800 0900 4760 2103 0000 0000 INDX(...G`!.....
0000016: 0000 0000 0000 0000 2800 0000 f808 0000 ........(.......
0000032: e80f 0000 0000 0000 2100 0000 0600 0000 ........!.......
[REMOVED]

前24字節是index record頭,結點頭在此之後開始。字節24到27顯示這個index entry列表開始在字節偏移40(0x28)處。注意這是相對結點頭開始的,所以我們需要加上24,得到64。這個案列index entry列表不是立即跟着index entry列表頭因爲修正記錄數據在index entry列表頭和實際列表之間。回顧index record頭的字節4到5表明這個修正數組定位在偏移40(0x28)。
字節28到31表示到列表最後一個entry偏移量爲2,296(0x8f8)字節,字節32到35表明分配列表緩衝偏移結束在4,072(0x0fe8)字節。因此,緩衝中有1,776字節沒被使用可能包含來自存儲在這些結點中的名稱的文件數據,字節36到39表明標誌是0,所以這個結點沒有子結點。

Generica Index Entry Data Structure(通用索引項數據結構)

目前爲止,我們已經討論了NTFS索引的一般概念,唯一缺少的是對索引項的討論。從這點,數據結構將特定於索引類型,但是這是所有index entry的通用結構,我將在本節描述。
index entry標準字段在表 13.15所示。

表 13.15 通用index entry數據結構。

Byte Range     Description
    0–7         Undefined
    8–9         Length of this entry
    10–11         Length of content
    12–15         Flags (see Table 13.16)
    16+         Content

entry的最後8字節,開始在$INDEX_ALLOCATION(8字段邊界僅當標誌被設置)子結點的一個VCN。

開始的8字節被用來存儲特定的index entry數據。字節8到9定義這個index entry多大,字節10到11是index entry內容的長度,開始於字節16。內容可以是任何數據。flags字段的值在如下的表13.16。

表13.16 index entry標誌字段的標記值

    Value         Description
    0x01         Child node exists
    0x02         Last entry in list

當一個index entry有子結點,那麼標誌位爲0x01,那麼子結點的VCN地址在index entry的最後8字節處。回顧每個index entry都有一個VCN。當標誌位爲0x02的時候,這個就是列表中最後一個entry。

Directory Index Entry Data Structure(目錄Index Entry數據結構)

用於文件名的目錄索引有一個特定index entry數據結構。它使用上一節中概述的基本模板。幷包括一個文件引用地址和一個$FILE_NAME屬性。每一個index entry的字段在表 13.17裏面所示。

    表13.17, 目錄index entry數據結構。

    Byte Range     Description                     Essential
    0–7         MFT file reference for file name         Yes
    8–9         Length of this entry                 Yes
    10–11         Length of $FILE_NAME attribute             No
    12–15         Flags (see Table 13.16)             Yes
    16+         $FILE_NAME Attribute (if length is > 0)     Yes

    Last 8 bytes of entry, starting on an 8-byte boundary VCN of child node in $INDEX_ALLOCATION(field exists only if flag is set)

文件引用值指向index entry相對的MFT entry。兩個標誌值應用於這個entry,0x01表示它是子結點,0x02表示這是列表中的最後一個結點。
現在讓我們查看$INDEX_ROOT和$INDEX_ALLOCATION屬性的剩餘部分,我們已經對這些屬性進行了部分剖析。$INDEX_ROOT屬性的內容如下:

# icat -f ntfs ntfs1.dd 7774-144 | xxd
0000000: 3000 0000 0100 0000 0010 0000 0400 0000 0...............
0000016: 1000 0000 a000 0000 a000 0000 0100 0000 ................
0000032: c51e 0000 0000 0500 7800 5a00 0100 0000 ........x.Z.....
0000048: 5e1e 0000 0000 0300 e03d ca37 5029 c401 ^........=.7P)..
0000064: 004c c506 0202 c401 e09a 2a36 5029 c401 .L........*6P)..
0000080: d0e4 22b5 096a c401 0004 0000 0000 0000 .."..j..........
0000096: 7003 0000 0000 0000 2120 0000 0000 0000 p.......! ......
0000112: 0c02 4d00 4100 5300 5400 4500 5200 7e00 ..M.A.S.T.E.R.~.
0000128: 3100 2e00 5400 5800 5400 0000 0000 0300 1...T.X.T.......
0000144: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000160: 1800 0000 0300 0000 0400 0000 0000 0000 ................
[REMOVED]

我們已經處理了開始的32字節,因爲開始16字節是$INDEX_ROOT頭,第二個16字節是結點頭。字節32到37表示這個entry是MFT entry 7,877(0x1ec5)。字節40到45表示index entry的大小是120(0x78)字節,所以它將結束於我們輸出的152字節。字節26到27表示這個屬性大小是90字節(0x5a)。標誌位在字節28處表示entry最後8字節是子結點的地址, 這是我們所期望的,因爲結點頭中標誌顯示有一個子結點。

$FILE_NAME屬性位於包含子結點VCN的index entry的最後,字節48到137和字節144到151,是0簇。我們可以看到文件名爲"MASTER~1.txt"。字節152到175包含一個空index entry和標誌位在字節164是3,表示這是列表中的結尾並且它包含子結點。字節168到175包含子結點的地址,VCN是4。我們看到$INDEX_ALLOCATION屬性中的兩個index record。圖13.7表示一個這個屬性的圖形佈局。

    圖 13.7 我們的$INDEX_ROOT示例目錄佈局

$INDEX_ALLOCATION屬性裏面的每一個index record都可以同樣的方式重複處理。相反,我們將在結點頭之後看到一些有趣的數據。我們之前已經看到index entries在index record 0 以偏移2,296結束,但是在結點中還有分配的 1,776 字節。因此,可能會刪除掉文件名信息 。我們可以在未使用的區中尋找合法的index enties。字節偏移2,400處具有以下內容:

0002400: be1e 0000 0000 0600 6800 5400 0000 0000 ........h.T.....
0002416: 5e1e 0000 0000 0300 908b bf37 5029 c401 ^..........7P)..
0002432: 004c c506 0202 c401 e09a 2a36 5029 c401 .L........*6P)..
0002448: 30a7 6410 9c4a c401 003c 0000 0000 0000 0.d..J...<......
0002464: 003a 0000 0000 0000 2120 0000 0000 0000 .:......! ......
0002480: 0903 7000 7300 6100 7000 6900 2e00 6400 ..p.s.a.p.i...d.
0002496: 6c00 6c00 6500 0000 2513 0000 0000 0b00 l.l.e...%.......

這是文件中的一個index entry在MFT entry 7,870(0x1ebe),並且這個文件的名稱是psapi.dll。由於索引重排的性質,我們無法判斷這個文件是被刪除了還是因爲添加或者刪除了其他文件而被移動到一個不同的entry位置。我們只能在查看所有其他索引項後才能判斷。TSK顯示所有entries,並將其留給用戶來確定未分配的原因。不過,Autopsy將過濾TSK輸出,只顯示唯一的名稱。

圖13.8 顯示$INDEX_ROOT的index entries和$INDEX_ALLOCATION的index records關係。我們可以看到root結點的索引有兩個entries。任何文件的名稱比"MASTER~1.TXT小的index record在VCN 0,而名稱大的index record在VCN 4。VCN 0中的index record尾部包含未分配的空間,並且我們可以找到文件"psapi.dll"的數據。注意這個名稱大於"MASTER~1.TXT",所以當它保存到這個結點,這像這個根結點中的一個不同的入口。
    
圖 13.8 解剖樣本中的索引entries佈局。

File System Metadata Files(文件系統元數據文件)

現在我們已經研究了文件和索引可以具有各種屬性,接下來我們將研究文件系統元數據文件。它們中的大多數使用普通的文件屬性,但是一部分也會有它們自己的屬性。這些屬性在以下各章節裏面描述。

$MFT File

文件$MFT位於主文件表入口0並且在本章開始的時候已經討論過在每一個文件中非常重要。在第12章"istat"輸出一個$MFT文件在我們的例子影像。它在$MFT中有標準屬性和$DATA屬性。
$MFT的一個唯一屬性是$BITMAP屬性,用於管理MFT entries的分配狀態。它是按字節組織的,當一位設置爲1表示entry被分配。否則當此位設置爲0表示entry未被分配。我們可以用icat並指定屬性類型176來查看$BITMAP屬性。

# icat -f ntfs ntfs1.dd 0-176 | xxd
0000000: ffff 00ff ffff ffff ffff ffff ffff ffff ................
0000016: ffff ffff ffff ffff ffff ffff ffff ffff ................
[REMOVED]

我們看到這裏除了第3個字節很多位都被設置爲1。這個字節對應MFT entries的16到23。

$Boot File

$Boot文件位於MFT entry 7,並且它的$DATA屬性包含引導扇區和引導代碼。這個屬性總是在0扇區開始,啓動扇區數據結構位於這裏。其它扇區被用於啓動代碼。啓動扇區的字段在表 13.18 中顯示。

    表 13.18. 引導扇區數據結構
Byte Range    Description                         Essential
0–2         Assembly instruction to jump to boot code         No (unless it is the bootable filesystem)
3–10         OEM Name                        No
11–12         Bytes per sector                     Yes
13–13         Sectors per cluster                     Yes
14–15        Reserved sectors (Microsoft says it mustbe 0)        No
16–20         Unused (Microsoft says it must be 0)            No
21–21        Media descriptor                    No
22–23        Unused (Microsoft says it must be 0)            No
24–31        Unused (Microsoft says it is not checked)        No
32–35        Unused (Microsoft says it must be 0)            No
36–39        Unused (Microsoft says it is not checked)        No
40–47        Total sectors in file system                Yes
48–55        Starting cluster address of MFT                Yes
56–63        Starting cluster address of MFT Mirror $DATA attribute    No
64–64        Size of file record (MFT entry)                Yes
65–67        Unused                            No
68–68        Size of index record                    Yes
69–71        Unused                            No
72–79        Serial number                        No
80–83        Unused                            No
84–509        Boot code                        No
510–511    Signature (0xaa55)                    No

這些字段不用來對應FAT文件系統中的引導扇區BIOS Parameter Block(BPB)字段。微軟文檔標識它們其中的部分必須是0爲文件系統掛載的時候用,但它們依然考慮非必須的數值,因爲它們在文件系統功能中不需要,並且微軟可能決定不檢查這些數值。我驗證如果這些數值非0,則Windows XP並不掛載磁盤。

引導扇區最重要的數值是每一個扇區和簇的大小。如果這些,我們將不能標識任何數據的位置。其次重要的數值是MFT的開始位置和每一個MFT entry的大小。目前爲止,MFT entries 總是1024字節,但是這個字段存在,以便將來可以輕易改變大小。還需要注意的是,$DATA屬性和$MFTMirr地址已經給出。它允許恢復工具確定$MFT entry的備份副本在哪裏,以便檢測定位MFT。
字段顯示MFT entry和index record大小有指定的格式。如果數值大於0, 它表示簇數量用到每一個數據結構。如果數值小於0,表示它是2的對數字節在每一個數據結構。要計算字節數,取負數的絕對值(也就是正數)並作爲2的次冪。舉個例子,如果值是-10,那麼數據結構的大小就是2的10次冪即1024字節。當簇大小大於單個MFT entry或者index record的時候出現這種情況。
讓我們仔細分析引導扇區。在第12章我們看到"istat"輸出$Boot文件,並且現在我們可以使用"icat"取查看$DATA屬性(類型128)。

# icat –f ntfs ntfs1.dd 7-128 | xxd
0000000: eb52 904e 5446 5320 2020 2000 0202 0000 .R.NTFS .....
0000016: 0000 0000 00f8 0000 3f00 ff00 3f00 0000 ........?...?...
0000032: 0000 0000 8000 8000 4060 1f00 0000 0000 ........@`......
0000048: b53a 0500 0000 0000 10d8 0700 0000 0000 .:..............
0000064: 0100 0000 0400 0000 947c 2250 8422 5004 .........|"P."P.
0000080: 0000 0000 fa33 c08e d0bc 007c fbb8 c007 .....3.....|....
0000096: 8ed8 e816 00b8 000d 8ec0 33db c606 0e00 ..........3.....
[REMOVED]
0000448: 6d70 7265 7373 6564 000d 0a50 7265 7373 mpressed...Press
0000464: 2043 7472 6c2b 416c 742b 4465 6c20 746f Ctrl+Alt+Del to
0000480: 2072 6573 7461 7274 0d0a 0000 0000 0000 restart........
0000496: 0000 0000 0000 0000 83a0 b3c9 0000 55aa ..............U.
[REMOVED]

在第一行我們看到OEM名稱是"NTFS",跟着是一些ASCII空格(0x20)。這是Windows訪問的標準名稱。字節11到12顯示每一個扇區的字節數是512(0x200)。字節13顯示每個扇區兩簇,所以每簇是1024字節。字節40到47顯示文件系統中的總數值是2,056,256(0x001f6040),即表示文件系統有1GB這麼大。字節48到55顯示MFT的起始簇是342,709(0x00053ab5)和字節56到64顯示MFT mirror的$DATA屬性的起始簇是514,064(0x0007d810)。
字節64顯示每個MFT entry的大小。回想這個數值依賴它是正數還是負數。在這個案例,它是1,所以代表MFT entry中的簇數是1024字節。字節68是用於目錄的index record的大小,此值爲4,即說明每4簇一個index record。
字節72到79是文件系統的序列號,0x04502284 50227C94。剩下的字節包含引導代碼並且字節510到511是0xAA55簽名,如同我們看FAT文件系統時一樣。

$AttrDef File

$AttrDef文件系統元文件的MFT entry號碼爲4,它定義文件系統屬性名和標識。此文件的$DATA屬性包含一個entry列表,它的字段如表 13.19所示。
    
表 13.19. $AttrDef entries數據結構。
    Byte Range        Description            Essential
    0–127            Name of attribute         Yes
    128–131        Type identifier            Yes
    132–135         Display rule             No
    136–139         Collation rule            No
    140–143        Flags (see Table 13.20)        Yes
    144–151        Minimum size            No
    152–159        Maximum size            No

    Table 13.20. $AttrDef entry標誌字段標誌數值。
    Value            Description
    0x02            Attribute can be used in an index
    0x04            Attribute is always resident
    0x08            Attribute can be non-resident

如果屬性沒有任何大小限制,最小大小將爲0,並且最大大小將爲0xffffffffffffffff。標誌字段可以設置圖13.20所示的值。
屬性在索引中時使用排序規則。決定它該如何排序。示例圖像中$AttrDef文件的$DATA屬性包含以下內容:

# icat -f ntfs ntfs1.dd 4-128 | xxd
0000000: 2400 5300 5400 4100 4e00 4400 4100 5200 $.S.T.A.N.D.A.R.
0000016: 4400 5f00 4900 4e00 4600 4f00 5200 4d00 D._.I.N.F.O.R.M.
0000032: 4100 5400 4900 4f00 4e00 0000 0000 0000 A.T.I.O.N.......
0000048: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000064: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000096: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000112: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000128: 1000 0000 0000 0000 0000 0000 4000 0000 ............@...
0000144: 3000 0000 0000 0000 4800 0000 0000 0000 0.......H.......
[REMOVED]

我們可以看到第一個屬性定義爲$STANDARD_INFORMATION屬性。在字節128到131處我們可以看到這個屬性類型爲16(0x10)。在字節140到143處是標誌位,顯示這個entry總是常駐的。字節144到151顯示這個屬性的最小的大小是48字節(0x30),並且最大大小爲72字節(0x48)。

$Bitmap File

在MFT entry 6號處是$Bitmap文件,有一個$DATA屬性用來管理簇的分配狀態。位圖數據由1字節數值組成,每個字節的最低有效位對應於前一個字節的最高有效位對應的簇之後的簇。

舉例來說,考慮兩個字節的二進制數值爲00000001和00000011。第一個字節最低有效位是1對應0簇。字節中的下7位(向後從右到左)全是0,所以我們知道簇1到7都還沒有被分配。第二個字節有兩個最低有效位都是1,對應簇8和9。像你可以看到的一樣,你讀這個通過查找最低有效位,從右到左向後移動,然後轉到右邊的下一個字節來讀取此值。
要確定給定簇的分配狀態,需要確定它位於bitmap的哪個字節。這是通過將簇地址除以8並忽略其餘部分來完成。舉個例子,簇5將在位圖的 0字節,簇18將在位圖的第二個字節。去找字節中位對應的簇,我們檢驗餘數。例如,當我們用5除以8,餘數是5,18除以8,餘數是2。圖 13.9  是計算簇5和簇74在位圖中的情況。

    圖 13.9 在位圖中尋找第5和第74個簇的分配狀態例子。

帶着位圖中位組織的知識,讓我們回到示例文件系統。我們可以用"icat"檢測$DATA屬性的內容:
# icat –f ntfs ntfs1.dd 6-128 | xxd
0000000: ffff ffff ffff ffff ffff ff3f 0000 0000 ...........?....
0000016: 0000 f0ff ffff ffff ffff ffff ffff ffff ................
0000032: ffff ffff ffff ffff ffff ffff ffff ffff ................
0000048: ffff ffff ffff ffff 0300 0000 ffff ffff ................
0000064: ffff ffff ffff ffff ffff ffff ffff ffff ................
0000080: ffff ffff ffff ffff ffff ffff ffff ffff ................
[REMOVED]

我們看到這裏初始位都是1,這是有意義的,因爲引導扇區位於文件系統的前8KB中,所以我們希望分配這些字節。在輸出的字節11中,我們看到值0x3f,二進制是00111111。這個字節相對文件系統中的簇88到95,並且6個最低有效位都是1,這表示都是已經分配了。第7和第8位是0,對應簇94和簇95.我們也可以看到下6個字節都是0,所以這些位的所有簇都未被分配。在字節18我們看到值0xf0,二進制是 1111 0000。

$Volume File

$Volume文件在MFT entry 3處,並且它有兩個唯一屬性。本章節將對它們進行描述。

$VOLUME_NAME Attribute

$VOLUME_NAME屬性的類型標識是96 應該只分配給$Volume文件。它僅僅包含volume的名稱,用UTF16的Unicode編碼。它的內容從我們的示例映像中顯示如下:
# icat -f ntfs ntfs1.dd 3-96 | xxd
0000000: 4e00 5400 4600 5300 2000 4400 6900 7300 N.T.F.S. .D.i.s.
0000016: 6b00 2000 3200 k. .2.

我們看到這個文件系統的卷標名稱是"NTFS Disk 2."

$VOLUME_INFORMATION Attribute

第二對於$Volume文件的個唯一屬性是$VOLUME_INFORMATION屬性,類型標識是112。這個屬性包含文件系統的版本。它的字段如表 13.21所示。

    表 13.21. $VOLUME_INFORMATION屬性數據結構。
    Byte     Range Description         Essential
    0–7     Unused                 No
    8–8     Major version             Yes
    9–9     Minor version             Yes
    10–11 Flags (see Table 13.22)         No

Windows NT系統的文件系統版本是 1.2。Windows 2000 的文件系統版本是 3. 0。Windows XP 的文件系統是 3.1。標誌顯示錶 13.22 應用這個數據結構。

    表 13.22. $VOLUME_INFORMATION標誌字段標誌數值。
    Flag     Description
    0x0001     Dirty
    0x0002     Resize $LogFile (file system journal)
    0x0004     Upgrade volume next time
    0x0008     Mounted in NT
    0x0010     Deleting change journal
    0x0020     Repair object IDs
    0x8000     Modified by chkdsk

$VOLUME_INFORMATION屬性在我們的示例文件系統有如下的內容:
# icat -f ntfs ntfs1.dd 3-112 | xxd
0000000: 0000 0000 0000 0000 0301 0000 ............

第8和第9字節告訴我們這個文件系統版本是3.1,即XP。這個標誌被設置爲0。

$ObjId File

正如我們在第12章中看到,可以使用文件的對象ID而不是名稱來尋址。這允許文件改名後依然可以找到該文件。文件\$Extend\$ObjId有一個名爲$O的索引,將文件的對象ID與它的MFT entry關聯起來。文件$ObjId通常不位於保留的MFT entry。
索引有典型的$INDEX_ROOT和$INDEX_ALLOCATION屬性,並且它的index entries的字段如下表 13.23。

    表 13.23. $ObjId index entries 數據結構。
    Byte Range     Description            Essential
    0–1         Offset to file information     Yes
    2–3         Size of file information     Yes
    4–7         Unused                 No
    8–9         Size of index entry         Yes
    10–11         Size of object ID (16-bytes)     Yes
    12–15         Flags (see Table 13.16)     Yes
    16–31         Object ID             Yes
    32–39         File reference             Yes
    40–55         Birth volume ID         No
    56–71         Birth object ID         No
    72–87         Birth domain ID         No

標誌字段有標準的值,當子結點存在的時候爲 0x01,當它是最後一個entry的時候數值是0x02。這裏一些$INDEX_ROOT 屬性index entriies, 當結點頭被刪除:
0000000: 2000 3800 0000 0000 5800 1000 0000 0000 .8.....X........
0000016: fe24 b024 e292 fe47 95ac e507 4bf5 6782 .$.$...G....K.g.
0000032: 0300 0000 0000 0300 0000 0000 0000 0000 ................
0000048: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000064: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000080: 0000 0000 0000 0000 2000 3800 0000 0000 ........ .8.....
0000096: 5800 1000 0000 0000 a162 3d5e cdda d811 X........b=^....
0000112: 883c 00b0 d01d e93f a400 0000 0000 0100 .<.....?........
0000128: fe24 b024 e292 fe47 95ac e507 4bf5 6782 .$.$...G....K.g.
0000144: a162 3d5e cdda d811 883c 00b0 d01d e93f .b=^.....<.....?
0000160: 0000 0000 0000 0000 0000 0000 0000 0000 ................

我們從第8個字節看到entry是88字節(0x58)大小,並且字節16到31顯示了16位的對象ID。字節32到37顯示這個對象ID的MFT entry是3。這是$OBJECT_ID屬性的index entry剖析"$OBJECT_ID Attribute"段。這個entry的其餘ID字段是0,下一個entry從字節88開始。

$Quota File

文件\$Extend\$Quota給用戶未來配額。它並不位於保留的MFT entry。它包含兩個索引同時使用標準$INDEX_ROOT和$INDEX_ALLOCATION屬性來保存這些index entries。$O索引關聯將SID與所有者ID關聯,$O索引關聯所有者ID到配額信息。$O索引的index entry字段如下表 13.24。
    表 13.24. $Quota的$O index entries數據結構。
    Byte Range     Description             Essential
    0–1         Offset to owner ID (OFF)     Yes
    2–3         Length of owner ID         Yes
    4–7         Unused                 No
    8–9         Size of index entry         Yes
    10–11         Size of SID (L)         Yes
    12–15         Flags (see Table 13.16)     Yes
    16–(16+L-1)     SID                 Yes
    OFF+         Owner ID             Yes
這個index entry的標誌值像我們看到的文件名的一樣。當它是一個子結點的時候設置0x01,當它是最後一個entry的時候設置0x02。如果一個孩子存在,最後8字節將被用來作爲這個孩子的VCN。
下面是$O索引中的第一個index entry:

0000000: 1c00 0400 0000 0000 2000 0c00 0000 0000 ........ .......
0000016: 0101 0000 0000 0005 1200 0000 0401 0000 ................
0000032: 1c00 0400 0000 0000 2000 0c00 0000 0000 ........ .......
0000048: 0101 0000 0000 0005 1300 0000 0301 0000 ................
[REMOVED]

字節0到1顯示所有者ID位於entry的開始偏移28(0x1c)處,並且字節2到3顯示所有者ID是4字節長。字節8到9顯示index entry是32字節(0x20),字節10到11顯示SID是12字節(0x0c)。字節16到27包含SID,字節28到31包含所有者ID,是260(0x0104)。第二個entry在列表開始的32字節處,並且它的所有者ID在字節60到63,是259(0x0103)。
$Q索引映射所有者ID到所有者配額信息。它的index entry值如下表 13.25所示。

    表 13.25. $Quota中的$Q索引數據結構。
    Byte Range     Description                 Essential
    0–1         Offset to quota information         Yes
    2–3         Size of quota information         Yes
    4–7         Unused                     No
    8–9         Size of index entry             Yes
    10–11         Size of owner ID (4 bytes)         Yes
    12–15         Flags (see Table 13.16)         Yes
    16–19         Owner ID                 Yes
    20–23         Version                 No
    24–27         Quota flags (see Table 13.26)         Yes
    28–35         Bytes charged to user             Yes
    36–43         Time of last charge             No
    44–51         Threshold value (a soft limit)         Yes
    52–59         Hard limit value             Yes
    60–67         Exceeded time                 Yes
    68–79         SID                     Yes

如果index entry是一個子結點那麼標誌位設爲0x01,如果是最後一個entry就設置位0x02。配額標誌的值在表 13.26處。
    
表 13.26. $Q index entry的標誌位數值字段。
    Flag         Description
    0x00000001     Default limits being used
    0x00000002     Limit reached
    0x00000004     ID deleted
    0x00000010     Tracking data usage
    0x00000020     Enforcing data usage
    0x00000040     Usage tracking requested
    0x00000080     Create log when threshold is met
    0x00000100     Create log when limit is met
    0x00000200     Out of date
    0x00000400     Corrupt
    0x00000800     Pending deletes

下面是一個來自我們用於$O索引的同一個映像中的index entry。出自於$INDEX_ALLOCATION屬性,頭部的數據被移除了。這實際來自entry列表裏面並對應所有者ID在之前的例子中顯示。
0000000: 1400 3c00 0000 0000 5000 0400 0000 0000 ..<.....P.......
0000016: 0301 0000 0200 0000 0100 0000 0028 0500 .............(..
0000032: 0000 0000 401b 7c3c 7751 c401 ffff ffff ....@.|<wQ......
0000048: ffff ffff ffff ffff ffff ffff 0000 0000 ................
0000064: 0000 0000 0101 0000 0000 0005 1300 0000 ................
0000080: 1400 3c00 0000 0000 5000 0400 0000 0000 ..<.....P.......
0000096: 0401 0000 0200 0000 0100 0000 0094 6602 ..............f.
0000112: 0000 0000 90fe 8bdf d769 c401 ffff ffff .........i......
0000128: ffff ffff ffff ffff ffff ffff 0000 0000 ................
0000144: 0000 0000 0101 0000 0000 0005 1200 0000 ................

字節0到1顯示配額信息的偏移是20字節(0x14),字節2到3顯示配額信息佔用60字節。字節16到19顯示所有者ID 259(0x0103),即我們看到的$O 索引中第二個entry。字節24到27有配額標誌,我們看到這個用戶有默認限制。字節28到35顯示用戶帳號僅僅有337920字節(0x052800)。如果你繼續解析下一個entry,它依然包含在內。

$LogFile File

文件$LogFile在MFT的第二個entry,它用來作爲NTFS日誌。它有標準文件屬性並保存日誌數據在$DATA屬性中。很不幸地,額外數據細節我們並不知道。不過我們將取出一部分內容,以大致瞭解其內容。
日誌被組織成4096字節的頁面。前兩個用於個重啓動區,它們每一頁都有"RSTR"簽名:

# icat -f ntfs ntfs1.dd 2 | xxd | grep RSTR
0000000: 5253 5452 1e00 0900 0000 0000 0000 0000 RSTR............
0004096: 5253 5452 1e00 0900 0000 0000 0000 0000 RSTR............

這個數據結構中的其他值很多都爲0, 並且僅僅字符串"NTFS"用Unicode。在第二個重啓動數據結構之後偏移8192處就是記錄,它們的每一個開始都帶着簽名"RCRD"
# icat –f ntfs ntfs1.dd 2 | xxd | grep RCRD
0008192: 5243 5244 2800 0900 0050 2500 0000 0000 RCRD(....P%.....
0012288: 5243 5244 2800 0900 0050 2500 0000 0000 RCRD(....P%.....
[REMOVED]

顯示保留數據屬性可以從日誌文件中找到,我用ASCII字符串"My new file, can you see it?"建立一個叫"C:\log-test.txt"的文件,在日誌文件中我們可以看到如下所示:
2215936: 5243 5244 2800 0900 f93b 9403 0000 0000 RCRD(....;......
[REMOVED]
2217312: 3801 1800 0000 0000 ec12 0000 0000 0000 8...............
2217328: a14d 0500 0000 0000 4d79 206e 6577 2066 .M......My new f
2217344: 696c 652c 2063 616e 2079 6f75 2073 6565 ile, can you see
2217360: 2069 743f 0000 0000 0000 0000 0000 0000 it?............
2217376: 0000 0000 0000 0000 0000 0000 0000 0000 ................
[REMOVED]
2217808: 0003 0000 0094 7c22 5010 0000 004e 5446 ......|"P....NTF
2217824: 5320 4469 736b 2032 0043 3a5c 6c6f 672d S Disk 2.C:\log-
2217840: 7465 7374 2e74 7874 0000 1500 2e00 2e00 test.txt........
2217856: 5c00 2e00 2e00 5c00 2e00 2e00 5c00 6c00 \.....\.....\.l.
2217872: 6f00 6700 2d00 7400 6500 7300 7400 2e00 o.g.-.t.e.s.t...
2217888: 7400 7800 7400 0300 4300 3a00 5c00 6000 t.x.t...C.:.\.`.

我們在記錄內部可以看到文件中的文本。當我們保存文件之後,記錄中我們一樣可以看到文件和磁盤名"NTFS Disk 2.",我修改字符串一樣包含"This is my update.."這個數據晚點將在日誌文件:
2248704: 5243 5244 2800 0900 f24b 9403 0000 0000 RCRD(....K......
[REMOVED]
2250800: ec12 0000 0000 0000 a14d 0500 0000 0000 .........M......
2250816: 2020 5468 6973 2069 7320 6d79 2075 7064 This is my upd
2250832: 6174 652e 0000 0000 0000 0000 0000 0000 ate.............
2250848: 0000 0000 0000 0000 0000 0000 0000 0000 ................

這個記錄僅僅包含新的內容,不包含正在更新的文件的路徑名。此記錄中可能有一個引用指向標識文件名的另一個記錄。

$UsrJrnl File

更新日誌屬於應用程序類別,並在對文件進行更改時記錄。這些更改記錄在文件\$Extend\$UsrJrnl名叫$J的$DATA屬性中,並不在保留的MFT entry。$J的$DATA屬性是稀疏的,並且它包含一個不同大小的數據結構列表,稱爲更改日誌條目。還有一個名爲$Max的$DATA屬性,包含關於用戶日誌的最大設置的信息。
像我們在第12章中討論,對於存儲和檢索數據的文件系統目標而言,這些數據並不是必須的。因此,此表中的"Essential"列指數據對於提供文件更改日誌的目標是否必不可少。$J中entries的數據結構具有表13.27中給出的字段。
    
表 13.27. $UsrJrnl 中的 $J 屬性條目的數據結構。
    Byte Range    Description                                 Essential
    0–3         Size of this journal entry                         Yes
    4–5         Major version                                 Yes
    6–7         Minor version                                 Yes
    8–15         File reference of file that caused this entry                 Yes
    16–23         Parent directory file reference for file that caused this entry        No
    24–31         USN for entry                                 Yes
    32–39         Timestamp                                 Yes
    40–43         Flags for type of change (see Table 13.28)                 Yes
    44–47         Source information                             No
    48–51         Security ID (SID)                             No
    52–55         File attributes                             No
    56–57         Size of file name                             Yes
    58+         File name                                 Yes

字節40到43包含改變日誌條目的理由。這個字段設置標誌位,並可以一個或者多個理由。表13.28中是這些數值的定義。
    
表 13.28. $J 條目中更改類型字段數值。
    Value         Description
    0x00000001    The default $DATA attribute was overwritten
    0x00000002    The default $DATA attribute was extended
    0x00000004    The default $DATA attribute was truncated
    0x00000010    A named $DATA attribute was overwritten
    0x00000020    A named $DATA attribute was extended
    0x00000040    A named $DATA attribute was truncated
    0x00000100    The file or directory was created
    0x00000200    The file or directory was deleted
    0x00000400    The extended attributes of the file were changed
    0x00000800    The security descriptor was changed
    0x00001000    The name changed—change journal entry has old name
    0x00002000    The name changed—change journal entry has new name
    0x00004000    Content indexed status changed
    0x00008000    Changed basic file or directory attributes
    0x00010000    A hard link was created or deleted
    0x00020000    Compression status changed
    0x00040000    Encryption status changed
    0x00080000    Object ID changed
    0x00100000    Reparse point value changed
    0x00200000    A named $DATA attribute was created, deleted, or changed
    0x80000000    The file or directory was closed

源數值在字節44到47通常是0,但如果操作系統造成而不是用戶則可以位非0。讓我們查看一個小型的更改日誌。爲了找到日誌的MFT entry,我們檢查文件系統\$Extend元數據內容目錄,即MFT entry 11。
# fls -f ntfs ntfs3.dd 11
r/r 25-144-2: $ObjId:$O
r/r 24-144-3: $Quota:$O
r/r 24-144-2: $Quota:$Q
r/r 26-144-2: $Reparse:$R
r/r 27-128-3: $UsnJrnl:$J
r/r 27-128-4: $UsnJrnl:$Max

我們看到它是MFT entry 27。我們用"icat"來顯示$J 屬性內容:
# icat –f ntfs ntfs3.dd 27-128-3 | xxd
0000000: 5000 0000 0200 0000 1c00 0000 0000 0100 P...............
0000016: 0500 0000 0000 0500 0000 0000 0000 0000 ................
0000032: 3000 e2b9 eb69 c401 0001 0000 0000 0000 0....i..........
0000048: 0201 0000 2000 0000 1400 3c00 6600 6900 .... .....<.f.i.
0000064: 6c00 6500 2d00 3000 2e00 7400 7800 7400 l.e.-.0...t.x.t.

前4個字節告訴我們這個更改日誌entry大小80字節,字節8到13告訴我們改entry是針對MFT entry 28(0x1c)的。字節24到31告訴我們這個entry的USN爲0N,字節40到43顯示原因標誌是0x00000001,即用來覆蓋默認$DATA屬性。最後我們看到這個entry是針對文件"file-0.txt"的。$Max屬性包含常規更改日誌管理信息。它的字段在下面的表13.29給出。
    
表 13.29 $UsrJrnl中的$Max屬性的數據結構。
    Byte Range     Description         Essential
    0–7         Maximum size         Yes
    8–15         Allocation size     Yes
    16–23         USN ID             Yes
    24–31         Lowest USN         Yes
這個信息像對研究沒有幫助,但爲了完整起見,$Max屬性的內容如下:

# icat –f ntfs ntfs3.dd 27-128-4 | xxd
0000000: 0000 8000 0000 0000 0000 1000 0000 0000 ................
0000016: 4057 7491 eb69 c401 0000 0000 0000 0000 @Wt..i..........

總結

NTFS中有很多數據結構和指針,讓手動分析困難起來。在本章中,我們研究了已知的常見數據結構。應該強調的是它們並非來自官方規範,但已經證明它們是可靠的。可能有一些尚未看過的數值或者標誌選項。

參考書目

Linux NTFS Project. NTFS Documentation, 1996-2004. http://linuxntfs.sourceforge.net/ntfs/index.html.
Solomon, David and Mark Russinovich. Inside Windows 2000. 3rd ed. Redmond: Microsoft
Press, 2000.
See also the Bibliography section of Chapter 11.

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