前面解析MBR,得到每個分區的起始扇區號,以及每個分區的大小;至於該分區的詳細信息,則取決於該分區卷加載的文件系統。比如:簇大小,扇區大小,文件系統類型,該分區使用情況,介質描述,隱含扇區,扇區總數……
解析完MBR,跳轉到引導分區的第一個扇區,即DBR;然后從DBR中的BPB中得到改卷的基本參數信息,定位MFT起始簇,進而定位到文件系統元數據文件位置,對該分區上的所有文件和目錄進行遍歷解析。
下面分步說明:
DBR的作用
簡單的來說,計算機啟動時執行完BOIS的啟動代碼,檢查各硬件設備正常后,JMP到MBR的引導代碼進行執行;然后由MBR引導至活動分區的DBR,再由DBR引導操作系統。如:DBR調用NTLDR,再由NTLDR調用系統內核。
一.FAT32 DBR 和NTFS $BOOT 的統一
每個分區第0號扇區,也就是系統引導扇區DBR(DOS BOOT RECORDER)。不管是FAT32文件系統,還是NTFS文件系統,第一個扇區內容都是DBR。很多做文件系統過濾驅動的開發人員強調,如果是FAT32系統,那么分區開始的第一個扇區是DBR;如果是NTFS系統,那么分區開始的第一個扇區是$BOOT文件。其實,這個說法是基於文件系統特征的,雖然沒有錯,但也混淆了視聽。事實上,在NTFS系統上,$BOOT位於分區開始的2個簇上,也就是16個扇區,8K大小(假設每簇大小占8扇區)。在這16個扇區上,第一個扇區仍然是DBR。所以,從分區卷扇區級上來說,NTFS分區的第一個扇區仍然是DBR。這樣就統一了說法,也便於理解了~
二.讀取DBR,進行解析:
為什么要讀DBR呢,因為DBR存放着關於文件系統的重要參數信息以及系統引導代碼。讀DBR扇區的數據,並進行解析;由前面的分析,至少有兩種方法可以定位到DBR的位置。
關於解析DBR,聲明兩個重載的函數:
void AnalyseDBR(ULONG ulSector); //參數為扇區號
void AnalyseDBR(wchar_t* szVolumeLink); //參數為分區符號鏈接
A. 解析MBR,根據分區表的信息,定位到指定分區的起始扇區,讀取該扇區,按照DBR格式進行解析,上一節已經解析了MBR,分區信息存儲在鏈表結構里,直接定位到第一個分區,讀取DBR,偽代碼如下:
//
// 定位到第一個卷,即C盤
//
{
NextEntry = RemoveHeadList(&NtfsData.Partitions);
PtItem= (PPARTITION_ITEM)CONTAINING_RECORD(NextEntry,PARTITION_ITEM,ForPtChain.ListEntry);
cout<<"\nthe test volume's start sector:"<<PtItem->StartSector<<endl;
ulDbrSector = PtItem->StartSector;
}
// 利用解析MBR的分區表信息,定位到分區開始扇區,解析DBR
AnalyseDBR(ulDbrSector);
詳細代碼見toysNtfs工程。
B. 根據分區符號鏈接,如:_T("\\\\.\\C:"),直接打開分區,進行讀寫;相比解析MBR的方式,這種方法明顯直接而簡單,打開分區以后,第一個扇區就是DBR。解析MBR雖然麻煩,但是靈活;將整個磁盤與分區都聯系起來,更容易理解磁盤結構;甚至經過深入學習,自己開發一個小型的磁盤分區工具,划分一小塊空間,用來存儲自己的秘密文件,該卷的解析方式只有你自己知道(有意不讓windows識別),豈不快哉~
偽代碼如下:(假設打開C:盤)
// 利用分區符號鏈接,打開分區,進行解析
AnalyseDBR(_T("\\\\.\\C:"));
前后兩種方法解析的結果如下:
(相同的結果)
C. DBR在磁盤上的格式和各字段的含義,在FAT文件系統上的DBR和NTFS上的DBR有所不同,下面以NTFS為例:
大概分以下幾個部分:
跳轉指令;
文件系統標志, Oem;
BPB,25字節;
其后類似於擴展BPB的一些字段;
大小為[0x200-0x044]的引導代碼;
結束標志:55AA
用代碼結構表示如下:同樣,只記錄需要關心的若干字段,便於解析NTFS其他部分。
typedef struct _PACKED_BOOT_SECTOR {
UCHAR Jump[3];
UCHAR Oem[8];
BIOS_PARAMETER_BLOCK PackedBpb; //BPB
UCHAR Unused[4];
LONGLONG NumberSectors; //扇區總數
LCN MftStartLcn; //MFT開始簇
LCN Mft2StartLcn;
CHAR ClustersPerFileRecordSegment;
UCHAR Reserved0[3];
CHAR DefaultClustersPerIndexAllocationBuffer;
UCHAR Reserved1[3];
LONGLONG SerialNumber;
ULONG Checksum;
UCHAR BootStrap[0x200-0x044]; //引導代碼
} PACKED_BOOT_SECTOR;
typedef PACKED_BOOT_SECTOR *PPACKED_BOOT_SECTOR;
DBR中的BPB結構如下:
typedef struct BIOS_PARAMETER_BLOCK {
USHORT BytesPerSector;
UCHAR SectorsPerCluster;
USHORT ReservedSectors;
UCHAR Fats;
USHORT RootEntries;
USHORT Sectors;
UCHAR Media;
USHORT SectorsPerFat;
USHORT SectorsPerTrack;
USHORT Heads;
ULONG HiddenSectors;
ULONG LargeSectors;
} BIOS_PARAMETER_BLOCK;
typedef BIOS_PARAMETER_BLOCK *PBIOS_PARAMETER_BLOCK;
大概能見名知意。BPB(bois parameter block) 是一段描述能夠使可執行引導代碼找到相關參數的信息。主要描述磁盤結構的細節,包括每扇區的字節數BytesPerSector,每簇的扇區數SectorsPerCluster;磁盤介質;每磁道扇區數,磁頭數等,均為后續引導代碼提供參數。
關於文件系統的信息,其實在擴展BPB中,也就是BPB之后的若干字段。這些字段中的數據使得NTLDR能夠在啟動時找到主文件表$MFT,進而定位到其他數據文件。
至此,DBR解析完畢,得到該卷上的MFT起始簇,備份MFT起始簇,每簇扇區數,每扇區字節數,介質等信息。經過簡單的計算就能知道起始MFT的扇區號。
三.解析MFT
由於用扇區來描述磁盤空間粒度比較小,處理量比較大,所以引進簇的概念。簇一般是2的倍數,一般NTFS中一個簇占8個連續扇區。基本上所有文件系統用簇來描述分區。
LCN(logical cluster number)邏輯簇號,對卷的第1個簇到最后一個簇進行編號。只要知道LCN號和簇的大小以及NTFS卷在物理磁盤中的絕對扇區就可以對簇進行扇區定位。
VCN(virtual cluster number)虛擬簇號,VCN對特定文件的簇從頭到尾進行編號,表示文件內部的相對位置,方便系統對文件中的數據進行引用。假如一個文件占用m個簇,那么這些簇的VCN就是從0到(m-1)。
NTFS文件系統用文件來記錄所有信息;文件由屬性組成;所以對NTFS文件的讀寫,實際上是對NTFS屬性的讀寫。一個文件對應一條MFT記錄(即文件記錄塊),一個文件記錄塊固定大小為1K,占兩個扇區;所有文件的MFT記錄都有一個文件來管理,就是$MFT,也就是0號MFT記錄所指向的文件。
由前面解析DBR知道MFT起始扇區,那么讀取該扇區內容,就是$MFT文件的文件記錄了。
解析之前,先說說元文件,對文件系統有一個全局的認識。
NTFS文件系統由DBR(本身也是$BOOT的內容)和元文件組成,其他文件和目錄通過文件系統管理。
元數據文件記錄了一些非常重要的文件系統數據,包括用於文件定位和恢復的數據結構、引導程序數據以及整個卷的分配位圖等信息。這些數據被NTFS文件系統理解為文件,並以文件的方式進行管理,統一了思想。
元文件對於用戶是不能直接訪問的,MFT將開頭的16個文件記錄塊保留用於這些元數據文件,除此之外的文件記錄塊才用於普通的用戶文件和目錄。
文件系統16個元文件列表:
0 $MFT //mft本身
1 $MftMirr //mft 元數據文件的鏡像,用於備份恢復
2 $LogFile //文件操作歷史記錄文件
3 $Volume //文件卷信息文件
4 $AttrDef //屬性定義文件
5 $Root(\) //根目錄文件
其它的元文件參照其他資料,不例舉了。
MFT結構
每個文件都有一條MFT記錄,通過讀取該文件的MFT記錄,就可以知道該文件有哪些屬性,各屬性的內容是多少,是否有加密,是否有隱藏存檔屬性,是否有命名的數據流等。
MFT文件記錄由記錄頭和屬性部分組成;一個文件可以有多個屬性,這些屬性又分為屬性頭和屬性內容。這些屬性的標准信息由$AttrDef元文件定義。
屬性又分為常駐屬性(Resident Attr)和非常駐屬性(NonResident Attr)。當文件的屬性內容大於大約700字節時,就要使用非常駐屬性。將屬性內容外掛到磁盤的其他位置,這些位置由runs片來描述。
不同屬性相同的地方在於屬性頭,即要么是常駐屬性頭,要么是非常駐屬性頭;不同屬性的屬性內容各不相同,在磁盤上的分布結構也不同,作用也不同;特別說明,這些屬性是可以擴充的。對各個屬性的解析,則比較繁瑣。
那么解析MFT文件記錄塊的基本框架如下:
ReadSector( MFT索引號, 長度)
{
解析MFT記錄頭;(大小0x38字節)
解析屬性部分
{
Switch(屬性類型)
{
Case: 對不同的屬性做處處理,讀取或修改,break;
Default:
}
}
}
指定一個MFT,解析屬性列表:
關於MFT的詳細結構,請參考文獻資料,解析過程見代碼。
無法說的太詳細,因為NTFS內容很多,結構卻一點也不復雜;展開全部細節,卻又不是只言片語就能說完的,亦屬力所不能及;說的太概念化,又恐讀之無物。那,權當拋磚引玉吧。
程序代碼寫的只是一個框架,在這個框架上就可以填充各個屬性的詳細分析過程。這樣的解析未免不太科學,但是作為學習了解文件系統在磁盤上的底層結構,卻是很簡單有效的。如果要理解NTFS文件系統工作的原理,須跟蹤文件系統IO處理流程,了解IO管理器,磁盤驅動,緩存cache管理等相關知識。
順着這個路徑,熟悉文件的各個屬性,屬性之間的相互作用;用nt4.Src.cntfs代碼,參考部分解析過程。
PS:程序代碼只是為了學習方便而寫的簡單測試框架;本節主要是分析DBR,獲取MFT的起始簇,獲取分區卷的必要參數信息;對指定的一條MFT文件記錄進行定位和解析;如此就可以從MFT中讀取文件的所有內容,包括:文件只讀、隱藏、存檔屬性,創建時間,修改時間,對象ID,數據內容,命名數據流,重解析點等信息。
那么,遺留的問題是,如何獲得MFT索引號?MFT號其實是文件引用號(64位)的低48位,那么如何獲取文件引用號?這里方法就不一了,比如:遞歸解析$Root跟目錄文件,獲取每個索引項的文件引用號,取低48位作為MFT索引號去MFT表中讀取文件數據;第二種方法是,遍歷MFT在磁盤上分布的區域大小,計算出總的MFT記錄數量,然后發送控制碼FSCTL_GET_NTFS_FILE_RECORD來獲取文件引用號。
關於解析:首先要明確目的,才能在解析過程中對關心的文件屬性信息作特殊處理;如果作為學習,那么選擇一個清晰明了的框架還是比較合理的。等熟悉了基本結構,在去深入學習NTFS驅動源碼,調試,在實際應用中開發項目,合理應用,使產品更高效;舉個例子,NTFS文件系統中,遍歷磁盤上的所有文件,並獲取文件在磁盤上的數據存儲信息。如果是普通的遞歸遍歷法,即FindFirstFile,,FindNextFile,那么遍歷速度將比直接遍歷MFT慢25倍左右。
下節具體解析文件的若干屬性(數據屬性,$IndexRoot根目錄屬性);給定文件名通過$ROOT文件查找文件引用號。
參考資料:
《數據恢復技術》 戴士劍、塗彥暉;
《數據重現,文件系統原理精解》 馬林
《windows internal》,4th,Mark E.Russinovich
《windows 內核情景分析》毛德操
相關代碼:
ToysNtfs2.rar