磁盘分区表恢复原理
purpleroc · 2016/01/13 19:36
Author: [email?protected]
Email: [email?protected]
0x00 引子
實在是詞窮想不到要怎么寫題目了,就把vs中的工程名當(dāng)題目吧。
這篇文,主要講講MBR、DBR、NTFS、FAT32結(jié)構(gòu)等等諸如此類的東西,以及在數(shù)據(jù)恢復(fù)中,我們可以從現(xiàn)有的被破壞了的磁盤中獲取到哪些有利于我們進(jìn)行數(shù)據(jù)恢復(fù)的信息。
不知道是最近沒休息好還是其他原因,總覺得靜不下心、集中不了注意力,也不知道從什么時候開始,瀏覽網(wǎng)頁只需要幾秒鐘,查找資料也從來不會耐心看完文章,總是一翻到底,用最快的速度去搜索、定位自己要找的內(nèi)容。但通常來說,網(wǎng)上有的與你問題相同的解決方案并不多。而人們寫文章往往不是奔著主題去的,而是和寫論文、寫書一樣,先把種種后面要用到的概念堆砌起來,然后再來慢慢的說解決方案。當(dāng)然,其實這樣也挺好的,但作為一個目的驅(qū)動者,我更喜歡看需要用到的時候再講的內(nèi)容。
于是,這篇文就這么來寫吧,免得你看完NTFS和FAT32文件系統(tǒng)就不想看下去了。
0x01 背景
事情是這樣的,這幾天在測試TrueCrypt解密的時候,碰到這么一種情況:
用TrueCrypt做整盤加密系統(tǒng)時,TrueCrypt會重寫磁盤的MBR區(qū)域,將原本的MBR加密保存到其他位置,啟動過程中通過了TrueCrypt的密碼驗證后再在內(nèi)存中恢復(fù)原先的MBR并引導(dǎo)進(jìn)入系統(tǒng)
這么做沒問題,用我的解密程序解完后也能夠正常掛載并訪問,可另一種情況來了:
如果加密的磁盤中,并不是只有一個c盤,而還有其他分區(qū)
這時,我的解密工具便不能直接掛載了,雙擊出現(xiàn):
圖1:
Winhex打開看了看,解密后的數(shù)據(jù)都是正確的,也看到了DBR:
圖2:
可還是打不開,為啥呢?猜測是分區(qū)表被破壞了。那怎么修復(fù)呢?瞬間想到了大一那會兒幫別人修電腦,搞壞分區(qū)表,花了一晚上找數(shù)據(jù)(而且還沒找全)的黑歷史,畢竟那會兒不知道用重建分區(qū)表的功能,也不理解原理,也就那次之后,呆圖書館看了蠻多數(shù)據(jù)恢復(fù)的書籍,去了解原理。
于是對解密后的文件做了個鏡像,用數(shù)據(jù)恢復(fù)軟件Diskgenuis打開,搜索并重建分區(qū)表,得到結(jié)果如下:
圖3:
發(fā)現(xiàn)他很神奇的把兩個分區(qū)找回來了,如果點保存,再去winhex中看,就能當(dāng)磁盤來分析了。
所以,我的目的是想知道他是怎么恢復(fù)分區(qū)表的!
想自己寫個程序,能修復(fù)鏡像文件中損壞了的分區(qū)表,并且能夠當(dāng)做vhd文件形式被win7以上的系統(tǒng)直接加載,并且在加載完之后,恢復(fù)到原有形態(tài)。
于是,憋了一周,邊寫邊找資料,終于把程序?qū)懲陙韺戇@文章了。
要恢復(fù)分區(qū)表,首先得從損壞了分區(qū)表的磁盤里找出分區(qū)信息,再用這些信息來生成分區(qū)表。
那問題是,磁盤中會有哪些信息呢?
0x02 尋址方式(CHS/LBA)
在用正常磁盤做講解前先來了解下磁盤的兩種尋址方式,一種是CHS(cylinder head sector)尋址方式、一種是LBA(Logical Block Addressing)邏輯塊尋址方式。其中CHS(尋址方式)在分區(qū)表中使用24個bit位(前10位表示cylinder、中間8位表示head、后6位表示sector),其最大尋址空間為8GB,后因為其滿足不了要求了,人們給他擴(kuò)充到28bit,但尋址空間也只有128G,面對現(xiàn)有的動輒上TB的硬盤還是無能為力。LBA是用來取代CHS的,LBA是一個整數(shù),常見有32bit和64bit,32bit最大尋址空間達(dá)到2TB。
不管CHS(尋址方式)也好,還是LBA(尋址方式)也好。磁盤存儲尋址都需要通過cylinder、head、sector這三個變量來實現(xiàn)。
從上面我們了解到的信息就是,有兩種尋址方式,而且可以相互轉(zhuǎn)換,再然后呢,歸根到底其實就是用的CHS方式。
這一塊的詳細(xì)的介紹以及轉(zhuǎn)換方式我就不說了,有興趣的可以百度百度,這里也提供一個鏈接:
blog.csdn.net/haiross/art…
0x03 主引導(dǎo)記錄MBR
接著,來看看正常的磁盤中的MBR,所謂MBR即Main Boot Record 主引導(dǎo)記錄區(qū),位于整個硬盤的0磁道0柱面1扇區(qū)(也可以用LBA描述成0扇區(qū))。總共占512字節(jié),通常也就是1個扇區(qū)的大小。其重要作用就是負(fù)責(zé)從BIOS手中接過引導(dǎo)權(quán),再去找可引導(dǎo)的分區(qū),并將權(quán)限交給可引導(dǎo)的DBR(Dos Boot Record),完成系統(tǒng)啟動。
MBR雖然占了一個扇區(qū),但其Boot_Code部分只占了446個字節(jié)。其余64個字節(jié)為DPT(Disk Partition Table硬盤分區(qū)表),對就是我們要恢復(fù)的東西,最后2個字節(jié)就是傳說中的標(biāo)志位55AA了。于是,他的結(jié)構(gòu)體大體如下:
#!cpp typedef struct MBR_T {UCHAR boot_code[446];PartTableRecord partition[4];UCHAR sign[2]; }MBR; 復(fù)制代碼對照著結(jié)構(gòu)體,我們看看winhex中的截圖:
圖4:
黃色區(qū)域就是boot_code所占用的446個字節(jié),紅色部分就是DPT,藍(lán)色就是標(biāo)志位了。
0x04 磁盤分區(qū)表DPT
既然找到了DPT,那肯定是要分析清楚,它是干嘛用的,里面都有些什么信息呢?
直接用winhex的模板看看先:
圖5:
桌面太小,截圖不完全,但也大體知道了里面會有些什么信息,順便翻出結(jié)構(gòu)體如下:
#!cpp typedef struct PartTableRecord_t {BYTE byIsBoot; //引導(dǎo)分區(qū) 1B 1B 80(引導(dǎo)分區(qū)),00(非引導(dǎo)分區(qū))BYTE byStartHead; //起始磁頭 1B 2BBYTE byStartSector; //起始扇區(qū) 1B 3B BYTE byStartCylinder; //起始柱面 1B 4BBYTE byPartType; //分區(qū)類型 1B 5B 07(NTFS),05(擴(kuò)展分區(qū)),0B(FAT32)BYTE byEndHead; //結(jié)束磁頭 1B 6BBYTE byEndSector; //結(jié)束扇區(qū) 1B 7BBYTE byEndCylinder; //結(jié)束柱面 1B 8BDWORD dwStartSector; //開始扇區(qū) 4B 12B DWORD dwTotalSector; //分區(qū)扇區(qū)數(shù) 4B 16B 最大2T Byte } PartTableRecord; 復(fù)制代碼恩,每個分區(qū)表占用16個字節(jié),而MBR中只留了64個字節(jié),這也是為什么一塊硬盤最多只能創(chuàng)建4個主分區(qū)的原因了。多了放不下。那,為啥我們可以看到比四個還多的分區(qū)呢?因為擴(kuò)展分區(qū)里面可以創(chuàng)建邏輯分區(qū),這里個數(shù)不定,只要你盤符夠,想創(chuàng)建多少就創(chuàng)建多少。
從結(jié)構(gòu)體后的注釋語句也可以知道16字節(jié)中每一位分別代表什么含義。這里需要注意的是,表示分區(qū)位置和大小的地方有兩個,我們可以通過起始磁頭、扇區(qū)、柱面和結(jié)束磁頭、扇區(qū)、柱面來得到分區(qū)位置和大小,也可以直接通過LBA模式記錄的開始扇區(qū)和分區(qū)扇區(qū)數(shù)來獲取到分區(qū)的位置和大小。
那,問題來了,他們哪個是有用的?
在第二節(jié)中尋址方式里講過,CHS能記錄的最大的分區(qū)是8.4GB,超過這個大小,就無力了。那這時候32位的LBA自然就派上用場了。
我做了個實驗,把所有分區(qū)表中的CHS記錄全部清零,再用winhex加載,還是能夠正常識別。于是,我決定了,后面所有尋址方式均以LBA方式來說。
再回到winhex中看分區(qū)信息,來對照DPT一一理解。
圖6:
上圖可以看出,這塊硬盤總共有5個分區(qū)。其中主分區(qū)三個,擴(kuò)展分區(qū)1個,邏輯分區(qū)2個(邏輯分區(qū)是在擴(kuò)展分區(qū)里面的)。也就是MBR中64個字節(jié)除了主分區(qū)就是擴(kuò)展分區(qū)。
根據(jù)DPT中的起始扇區(qū)以及扇區(qū)大小,就可以得到上圖中每個分區(qū)的大小、1st sector(起始扇區(qū))了。主分區(qū)好說,我們對照著DPT中的其實位置和大小都能看得出,那擴(kuò)展分區(qū)是怎么個形態(tài)呢?
我們直接看第四個DPT的信息:007A300B05FE3F1880D0020000600300,忽略掉CHS部分,并對照結(jié)構(gòu)體來看,它告訴了我們這些信息:
#!bash byIsBoot = 0x00 // 非引導(dǎo)分區(qū) byPartType = 0x05 // 擴(kuò)展分區(qū) dwStartSector = 0x0002D080 //起始扇區(qū) 184448 dwTotalSector = 0x00036000 //分區(qū)大小 221184 復(fù)制代碼這里可以看到,擴(kuò)展分區(qū)的起始位置其實也就是三個主分區(qū)的總大小了,再加上自身的分區(qū)大小,就是整個磁盤的大小了。例如我這個磁盤是200MB的,現(xiàn)在大小應(yīng)該是184448 + 221184 = 405632,注意單位是扇區(qū),所以換算成MB應(yīng)該是 405632/512 * 1024 * 1024 = 198.0625MB。為什么與文件總大小相比少了呢?因為,在分區(qū)表后面還有一些未被使用的空間。好奇的是,這個擴(kuò)展分區(qū)中到底放了些什么呢?
在winhex中Ctrl+G輸入扇區(qū)號184448,跟隨過去:
圖7:
還是和剛才一樣,黃色部分為前446字節(jié),這里全為0,因為不需要boot_code,而后64字節(jié)為擴(kuò)展分區(qū)的分區(qū)表信息。在圖的左右下方分別標(biāo)示出了現(xiàn)在所在的偏移扇區(qū)位置,以及總扇區(qū)個數(shù)和偏移位置(字節(jié)數(shù)表示)。還是來看這里的DPT信息吧,有兩個分區(qū)有信息,這次直接用winhex來看:
圖8:
第一個分區(qū)起始扇區(qū)是128,總大小為122880,類型是ntfs;第二個分區(qū)起始扇區(qū)是123008,總大小為94338,類型是擴(kuò)展分區(qū)。需要注意的是,擴(kuò)展分區(qū)的起始扇區(qū)是需要加上基地址(擴(kuò)展分區(qū)偏移扇區(qū)位置)的。也就是說,我們看到的第一個分區(qū),實際起始地址為:184448 + 128 = 184576,與圖6的partition4的起始位置是一樣的,那下一個呢?再來一個擴(kuò)展分區(qū)的類型是怎么個意思,也還是算一下實際的起始地址:184448 + 123008 = 307456。
再次Ctrl + G跟隨過去:
圖9:
同樣的,再次找到一個DPT信息,這里面只有一項,也就是圖6中的第五個分區(qū)了,也來算一下吧:
起始扇區(qū) = 307456 + 128 = 307584,與圖6中第五個分區(qū)起始位置一致。
從上面的實例中可以得出,整個磁盤大概是這么分布的:
圖10:
再看擴(kuò)展分區(qū)的鏈接圖示:
圖11:
于是,回到背景里提到的目標(biāo),我們要做的就是,根據(jù)磁盤中存有的信息,來重建出這么一個分區(qū)表。
自然的,我們需要去知道分區(qū)表指向的內(nèi)容是什么!
0x05 操作系統(tǒng)引導(dǎo)分區(qū)DBR
在上一節(jié)里面,提到了DPT,也提到了分區(qū)表的結(jié)構(gòu)體,從結(jié)構(gòu)體里我們可以看到偏移5的位置有鍵值byPartType,分區(qū)類型,去找了找資料,這里的取值非常多,常見類型大致如下:
#!bash 00H DOS或WINDOWS不允許使用,視為非法 01H FAT12 04H FAT16小于32MB 05H Extended 06H FAT16大于32MB 07H HPFS/NTFS OBH WINDOWS95 FAT32 OCH WINDOWS95 FAT32 0EH WINDOWS FAT16 0FH WINDOWS95 Extended(大于8G) 82H Linux swap 83H Linux 85H Linux extended 86H NTFS volume set 87H NTFS volume set 復(fù)制代碼在結(jié)合上一節(jié)的分區(qū)表,這里主要關(guān)注05、07、0B,即擴(kuò)展分區(qū)、NTFS、FAT32三種。而05的在上一節(jié)介紹過了,那么,我們將目光投向NTFS與FAT32兩種類型。
5.1 NTFS(New Technology File System)
首先,從MBR中看到分區(qū)信息能知道,分區(qū)1是NTFS分區(qū),在winhex中跟隨上一節(jié)中分區(qū)1的起始位置,可以看到如下信息,圖12:
已將每個數(shù)據(jù)對應(yīng)的結(jié)構(gòu)和數(shù)據(jù)都著色了,然后也是時候拿出NTFS文件系統(tǒng)中DBR的數(shù)據(jù)結(jié)構(gòu)了:
#!cpp typedef struct ntfs_boot_sector_t {BYTE ignored[3]; /* 0x00 Boot strap short or near jump */char system_id[8]; /* 0x03 Name : NTFS */BYTE sector_size[2]; /* 0x0B bytes per logical sector */BYTE sectors_per_cluster; /* 0x0D sectors/cluster */WORD reserved; /* 0x0E reserved sectors = 0 */BYTE fats; /* 0x10 number of FATs = 0 */BYTE dir_entries[2]; /* 0x11 root directory entries = 0 */BYTE sectors[2]; /* 0x13 number of sectors = 0 */BYTE media; /* 0x15 media code (unused) */WORD fat_length; /* 0x16 sectors/FAT = 0 */WORD secs_track; /* 0x18 sectors per track */WORD heads; /* 0x1A number of heads */DWORD hidden; /* 0x1C hidden sectors (unused) */DWORD total_sect; /* 0x20 number of sectors = 0 */BYTE physical_drive; /* 0x24 physical drive number */BYTE unused; /* 0x25 */WORD reserved2; /* 0x26 usually 0x80 */LCN sectors_nbr; /* 0x28 total sectors nbr */QWORD mft_lcn; /* 0x30 Cluster location of mft data.*/QWORD mftmirr_lcn; /* 0x38 Cluster location of copy of mft.*/char clusters_per_mft_record; /* 0x40 */BYTE reserved0[3]; /* zero */char clusters_per_index_record; /* 0x44 clusters per index block */BYTE reserved1[3]; /* zero */LCN volume_serial_number; /* 0x48 Irrelevant (serial number). */DWORD checksum; /* 0x50 Boot sector checksum. */BYTE bootstrap[426]; /* 0x54 Irrelevant (boot up code). */WORD marker; /* 0x1FE */ }ntfs_boot_sector ; 復(fù)制代碼再次想想我們的目的,是要重構(gòu)分區(qū)表,而且,我們可以看出,分區(qū)表中最重要的就是各分區(qū)的分區(qū)類型、起始位置以及
分區(qū)大小,三個信息了。當(dāng)然,還有是否為活動分區(qū),但這里我們主要是做數(shù)據(jù)恢復(fù),所以,暫不考慮其是否作為引導(dǎo)分區(qū)了。是的,我們只要獲取到每一個分區(qū)的分區(qū)類型、起始位置、分區(qū)大小三個信息。
我們來看看這個結(jié)構(gòu)體中能提供給我們一些什么:
- sectors_nbr:總扇區(qū)數(shù),即分區(qū)大小
- system_id:文件系統(tǒng)類型,即分區(qū)類型
而在剛才的試驗中,我們也可以看到,分區(qū)表中每個起始位置對應(yīng)的信息,都是指向DBR(Dos Boot Record操作系統(tǒng)引導(dǎo)分區(qū))結(jié)構(gòu)體的。于是,分區(qū)的起始位置也是可以獲取到的。
于是,我們的思路是從磁盤中去搜索各個DBR,然后獲取到分區(qū)表所需信息,再建立分區(qū)表。
既然聊到了NTFS,我們再來看看上面結(jié)構(gòu)體中,還有哪些信息是我們需要的吧:
- sector_size:每扇區(qū)字節(jié)數(shù),一般情況下是固定512字節(jié)的
- sectors_per_cluster:這個也挺重要的,需要引入一個新概念,“簇”。他在后面要用的MFT中起作用。先看看這個鍵值是干嘛用的,它表示的是每簇的扇區(qū)數(shù)量。
- hidden:字面意思是未使用的扇區(qū)數(shù)。這里表示的是從分區(qū)表到DBR的扇區(qū)數(shù)量。若為主分區(qū),則hidden表示的是扇區(qū)的起始位置。若是擴(kuò)展分區(qū),則hidden表示的是從擴(kuò)展分區(qū)表到該分區(qū)的扇區(qū)數(shù)。
mft_lcn:$MFT(主文件表)的偏移位置,這里的單位是簇,他LBA位置 = 分區(qū)offset + mft_lcn * sectors_per_cluster。以 圖:12 為例,分區(qū)起始位置為128,sectors_per_cluster = 8,mft_lcn = 853,所以LBA位置為:128 + 8 * 853 = 6952。
為了證實,我們在winhex中跟隨到6952扇區(qū),如下:
圖13:
在這里,我們也可以用MFT作為判斷某個搜索到的DBR是否正確的條件。有想更詳細(xì)了解MFT的可以去找找資料繼續(xù)看文件管理方式,這里就不展開了,畢竟,我們這次主要目的是恢復(fù)分區(qū)表。
mftmirr_lcn:每個MFT表所占用的扇區(qū)數(shù)量。一般為2.
對我們有用的信息大概也就是上面所提到的了。所以對于恢復(fù)NTFS型分區(qū),我們的策略是,全盤搜索,找到NTFS型DBR,之后通過MFT信息來判斷該分區(qū)是否正確。同時對于NTFS還需要提醒的是,其DBR扇區(qū)不僅僅在分區(qū)頭部存在,在分區(qū)的最后一個扇區(qū)中也有一個備份。所以,就算分區(qū)首部的DBR被破壞了,我們也可以通過分區(qū)尾部的DBR來恢復(fù)出分區(qū)表。
5.2 FAT32(32位文件分配表)
上一小節(jié)中,我們知道了怎么重建NTFS型分區(qū),接著,來看看FAT32。回到圖6中,看看第三個分區(qū)。winhex跟過去,得到截圖如下,這次就不上色了,太累~~~
圖14:
找了找資料,翻出FAT32的結(jié)構(gòu)體表示:
#!cpp typedef struct fat_boot_sector_t {BYTE ignored[3]; /* 0x00 Boot strap short or near jump */char system_id[8]; /* 0x03 Name - can be used to special casepartition manager volumes */BYTE sector_size[2]; /* 0x0B bytes per logical sector */BYTE sectors_per_cluster; /* 0x0D sectors/cluster */WORD reserved; /* 0x0E reserved sectors */BYTE fats; /* 0x10 number of FATs */BYTE dir_entries[2]; /* 0x11 root directory entries */BYTE sectors[2]; /* 0x13 number of sectors */BYTE media; /* 0x15 media code (unused) */WORD fat_length; /* 0x16 sectors/FAT */WORD secs_track; /* 0x18 sectors per track */WORD heads; /* 0x1A number of heads */DWORD hidden; /* 0x1C hidden sectors (unused) */DWORD total_sect; /* 0x20 number of sectors (if sectors == 0) *//* The following fields are only used by FAT32 */DWORD fat32_length; /* 0x24=36 sectors/FAT */WORD flags; /* 0x28 bit 8: fat mirroring, low 4: active fat */BYTE version[2]; /* 0x2A major, minor filesystem version */DWORD root_cluster; /* 0x2C first cluster in root directory */WORD info_sector; /* 0x30 filesystem info sector */WORD backup_boot; /* 0x32 backup boot sector */BYTE BPB_Reserved[12]; /* 0x34 Unused */BYTE BS_DrvNum; /* 0x40 */BYTE BS_Reserved1; /* 0x41 */BYTE BS_BootSig; /* 0x42 */BYTE BS_VolID[4]; /* 0x43 */BYTE BS_VolLab[11]; /* 0x47 */BYTE BS_FilSysType[8]; /* 0x52=82*//* */BYTE nothing[420]; /* 0x5A */WORD marker; }fat_boot_sector; 復(fù)制代碼前面是BIOS Parameter Block結(jié)構(gòu)體的一些信息,是公用的,后面的才是單純的FAT32所需要的。用winhex的模板解析一下,得到下面的內(nèi)容。
圖15:
首先,我們需要的文件類型是可以通過system_id來獲取到的,起始扇區(qū)也是可以通過DBR所在的位置得到的,相對NTFS來說FAT32中少了sectors_nbr鍵值,那我們應(yīng)該如何去獲取到FAT32的總大小呢?
我采用的辦法是,用搜索到的它的后一個DBR的位置減去當(dāng)前位置,如果后面沒有分區(qū)了,則用文件總大小減去當(dāng)前偏移位置。
這樣,我們也能將分區(qū)表需要的三個信息得到。再然后,我們也要來說說這結(jié)構(gòu)體里還有哪些對我們有用的信息。
- hidden:字面意思是未使用的扇區(qū)數(shù)。這里表示的是從分區(qū)表到DBR的扇區(qū)數(shù)量。若為主分區(qū),則hidden表示的是扇區(qū)的起始位置。若是擴(kuò)展分區(qū),則hidden表示的是從擴(kuò)展分區(qū)表到該分區(qū)的扇區(qū)數(shù)。
- total_sect:本來可以用來表示分區(qū)大小的,可只是個長整形變量,只能正確表示小分區(qū)的大小,對于大分區(qū)無力了。
reserved:保留扇區(qū)數(shù),起始,他指的是第一個FAT1表所在的相對偏移位置,例如,在partition3中,起始地址是82048扇區(qū),reserved扇區(qū)數(shù)量是6718扇區(qū),可以得到,FAT1表起始扇區(qū)為82048 + 6718 = 88766。在winhex跟過去,得到信息如下:
圖16:
途中每一種顏色代表一個目錄表項,綠色為0號FAT項,淡黃色為1號FAT項。通常0號FAT項總為0x0ffffff8。于是,這一特征也可以被我們當(dāng)做判斷分區(qū)表是否正確的標(biāo)準(zhǔn)。
fats:表示的是FAT表的個數(shù),通常為2個,即FAT1和FAT2。
fat32_length:每個FAT表的扇區(qū)數(shù)。在上面我們定位到了FAT1,那么FAT2就是用FAT1的起始位置加上每個FAT表的大小。
info_sector:filesystem_info的起始位置,起始也可以作為FAT32分區(qū)是否正確的判斷標(biāo)志。
backup_boot:FAT32的DBR備份文件存在的位置。上例中,backup_boot = 6,即DBR備份存放在分區(qū)起始扇區(qū)+6的地方。
有關(guān)FAT的一些結(jié)構(gòu)和作用:
具體有關(guān)FAT的其他信息,還請自行收集資料,因為,有了前面的信息,我們也可以有辦法恢復(fù)FAT32分區(qū)對應(yīng)的分區(qū)表數(shù)據(jù)了。
了解了前面的知識之后,我們就可以開始編寫程序來對分區(qū)表進(jìn)行重建了。
0x06 FileMapping(文件映射)
首先需要從磁盤、磁盤鏡像中找到DBR存在的痕跡,就需要對整個磁盤或鏡像文件進(jìn)行遍歷搜索,由于分區(qū)是線性擴(kuò)展的,而且DBR所在的位置永遠(yuǎn)的是扇區(qū)開頭,并且獨自占有一整個扇區(qū)。于是我們可以遍歷文件或磁盤中的每個扇區(qū)來快速完成搜索。
對于磁盤或者磁盤鏡像文件,肯定不能是通常的直接fopen、fread讀取整個文件了,因為,你沒那么多內(nèi)存去讀,這時候就要對文件進(jìn)行分片讀取,比如,每10M讀一次,如此循環(huán)將其遍歷一次。
但考慮到吞吐率的問題,這里引用的是FileMapping文件映射的方式,將文件直接映射到內(nèi)存中進(jìn)行操作。當(dāng)文件比較小的時候,我們可以直接全文件映射起來,但通常不太建議這么做。
我采用的也是上面說的循環(huán)讀取的思路,對大的磁盤文件循環(huán)映射起來,代碼如下:
#!cpp #define MAPPING_SIZE 67108864 #define BYTE_PER_M 1024*1024// // big_file: 需要映射的文件路徑 // ll_file_size: 需要映射的文件總大小 // int ToMapping(char *big_file, unsigned __int64 ll_file_size) { LCN i = 0;//得到系統(tǒng)分配粒度SYSTEM_INFO sinf;GetSystemInfo(&sinf);DWORD dwAll = sinf.dwAllocationGranularity;printf ("Total %dM.\nSearching...\n",ll_file_size / BYTE_PER_M ;if (ll_file_size <= MAPPING_SIZE) //內(nèi)存鏡像小于64M時,一次性掛載{Maping_file(big_file, 0, ll_file_size);}else{for (i = 0; i < (ll_file_size / MAPPING_SIZE) ; i++) //否則以64M為一個鏡像映射單位,循環(huán)掛載,直到全部映射完成{ if (i == 0){Maping_file(big_file, (i * (MAPPING_SIZE)) - (i * (MAPPING_SIZE) % dwAll), MAPPING_SIZE);}else {Maping_file(big_file, (i * (MAPPING_SIZE)) - ((i * (MAPPING_SIZE)) % dwAll), MAPPING_SIZE); }}if (ll_file_size > (i * MAPPING_SIZE)){ //最后一次可能并不是64M,需要根據(jù)實際大小來映射Maping_file(big_file, i * MAPPING_SIZE - ((i * MAPPING_SIZE) % dwAll), ll_file_size - i * MAPPING_SIZE);}}return 0; } 復(fù)制代碼ToMapping完成的是將文件分片交給Maping_file函數(shù)處理。
#!cpp int Maping_file(char* big_file, LCN lOffset, long lSize) {char* pPtr_File; //存放指向內(nèi)存映射文件的首地址HANDLE hFile = CreateFileA(big_file, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);if ( hFile == INVALID_HANDLE_VALUE){ //Open the data file.ErrorOut("CreateFile() Error!");}HANDLE hFileMapping = CreateFileMapping(hFile, NULL, //Create the file-mapping object.PAGE_READONLY,0, 0,NULL);if (hFileMapping == INVALID_HANDLE_VALUE){ErrorOut("CreateFileMapping() Error!");}PBYTE pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_READ,lOffset & 0xFFFFFFFF00000000, // Offset highlOffset & 0xFFFFFFFF, // Offset lowlSize); // bytes to mapif (pbFile == INVALID_HANDLE_VALUE){ErrorOut("MapViewOfFile() Error!");}/pPtr_File = (char*)pbFile;ToGetDBR(pPtr_File, lSize, lOffset);//UnmapViewOfFile(pbFile);CloseHandle(hFileMapping);CloseHandle(hFile);return 0; } 復(fù)制代碼這樣,就能通過CreateFileMapping將大的鏡像文件分塊為64M一小塊映射起來,之后調(diào)用ToGetDBR,來對磁盤中殘留的DBR信息進(jìn)行搜索。
0x07 搜索DBR
這里的思路是,遍歷整個磁盤,而后檢測每個扇區(qū),看是否滿足NTFS或FAT32型分區(qū)格式。若滿足,則鍵入到鏈表中。
#!cpp void ToGetDBR(char* p_file, long l_size, LCN offset) {long i = 0;char *buf = NULL;char *temp = NULL;LCN ll_offset = 0;do {buf = p_file + i * SECTOR_SIZE;if (!test_NTFS((ntfs_boot_sector*)buf, offset + i * SECTOR_SIZE)){ll_offset = offset + (i * SECTOR_SIZE);temp = (char*)malloc(512);memcpy(temp, buf, 512);if (InsertDBRList(g_dbr_list_head, temp, 1, g_n_dbr, ll_offset)) // NTFS type is 1{//printf ("Found NTFS! AT %lld sectors\n", ll_offset / SECTOR_SIZE);}ll_offset = 0;temp = NULL;}if(!test_FAT((fat_boot_sector*)buf, offset + i * SECTOR_SIZE)){ll_offset = offset + (i * SECTOR_SIZE);temp = (char*)malloc(512);memcpy(temp, buf, 512);if (InsertDBRList(g_dbr_list_head, temp, 2, g_n_dbr, ll_offset)) // FAT32 type is 2{//printf("Found FAT! AT %lld sectors\n", ll_offset / SECTOR_SIZE);}ll_offset = 0;temp = NULL;}i++;} while (i * SECTOR_SIZE < l_size); } 復(fù)制代碼檢測是否為NTFS與FAT32形式的磁盤可根據(jù)NTFS及FAT32結(jié)構(gòu)體特征來判斷,這里判斷代碼如下:
#!cpp int test_NTFS(const ntfs_boot_sector*ntfs_header, LCN l_size) {LCN lba = l_size / SECTOR_SIZE;chs tmp;int verbose = 1;if(ntfs_header->marker!=0xAA55 ||(ntfs_header->reserved)>0 ||ntfs_header->fats>0 ||ntfs_header->dir_entries[0]!=0 || ntfs_header->dir_entries[1]!=0 ||ntfs_header->sectors[0]!=0 || ntfs_header->sectors[1]!=0 ||ntfs_header->fat_length!=0 || (ntfs_header->total_sect)!=0 ||memcmp(ntfs_header->system_id,"NTFS",4)!=0)return 1;switch(ntfs_header->sectors_per_cluster){case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128:break;default:return 1;}return 0;int test_FAT(const fat_boot_sector* fat_header, LCN l_size) {if(!(fat_header->marker==0xAA55&& (fat_header->ignored[0]==0xeb || fat_header->ignored[0]==0xe9)&& (fat_header->fats==1 || fat_header->fats==2)))return 1; /* Obviously not a FAT */switch(fat_header->sectors_per_cluster){case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128:break;default:return 1;}switch(fat_header->fats){case 1:break;case 2:break;default:return 1;}return 0; } 復(fù)制代碼而所用到的DBR鏈表結(jié)構(gòu)體如下:
#!cpp typedef struct dbr_list_t {char* dbr; // DBR contentint n_type; // DBR type ntfs = 1 fat32 = 2int flag; // Is this DBR a Available?int n_is_org; // Is this DBR a orignal or copy.__int64 ll_offset; // DBR offset.__int64 ll_total_sector; // Partition offset.__int64 ll_start_sector; // Partition Size.dbr_list_t* p_next; // Point to next dbrdbr_list_t(){dbr = NULL;n_type = 0;p_next = 0;ll_offset = 0;n_is_org = 0;flag = 0;ll_total_sector = 0;ll_start_sector = 0;} }dbr_list; 復(fù)制代碼在搜索過中,對dbr、n_type、ll_offset、p_next四個鍵值進(jìn)行賦值,得到整個磁盤中可能存在的分區(qū)信息。并將這些搜索到的DBR存放在鏈表中,方便進(jìn)一步處理。
0x08 判斷DBR
在上一小節(jié),我們完成了對整個磁盤中可能存在的分區(qū)信息的搜索。那,搜索到的結(jié)果肯定不會全部正確或者可用,為了使重建的分區(qū)表更加可靠,需要對搜索到的分區(qū)信息進(jìn)行篩選,以及鏈表信息的填充。
在0x05中談到了NTFS和FAT32類型DBR的一些特性,這里就需要用到這些特性對去進(jìn)行判斷。
對于NTFS型的DBR。
#!cpp if (p_dbr_temp->n_type == 1) // NTFS {p_ntfs_temp = (ntfs_boot_sector*)p_dbr_temp->dbr;if (p_ntfs_temp->sectors_nbr < (g_ll_file_size / SECTOR_SIZE)) // 獲取到的大小不能比總大小還大{flag = 0;flag = JudgeMFT(file_path, p_dbr_temp, p_ntfs_temp);if (flag){p_dbr_temp->flag = 1; // 設(shè)置dbr可用標(biāo)志位p_dbr_temp->ll_total_sector = (LCN)p_ntfs_temp->sectors_nbr; // 設(shè)置分區(qū)總大小g_n_part++;}// 輸出信息printf("Type: NTFS.\tOffset: %I64u.\tSize %I64u.\t Hidden: %lu\tMFT at %I64u cluster.\t MFT is %s!\n",p_dbr_temp->ll_offset / SECTOR_SIZE, (LCN)p_ntfs_temp->sectors_nbr, p_ntfs_temp->hidden,p_ntfs_temp->mft_lcn.QuadPart,flag ? "Right" : "Wrong");} } 復(fù)制代碼若兩項都滿足,則判定其為正確的NTFS類型DBR,即可能為正確的分區(qū)信息。那該如何判斷MFT是否正確呢?在0x05中也提到過,定位到MFT的方法,定位過去之后,讀取文件看是否為滿足主文件記錄的格式。需要注意的是,對于NTFS搜索到的DBR有可能是分區(qū)起始扇區(qū)的,也有可能是分區(qū)結(jié)束扇區(qū)的,兩者都需要考慮,并且,在判斷MFT的同時,需要把dbr_list中的其他信息填充完整。
#!cpp // //file_path: 鏡像文件路徑 //p_dbr:dbr_list結(jié)構(gòu)體 //ntfs:dbr // int JudgeMFT(char* file_path, dbr_list* p_dbr, ntfs_boot_sector* ntfs) {char sz_temp1[4] = {0};char sz_temp2[4] = {0};DWORD readsize;LARGE_INTEGER tmp1 = {0};LARGE_INTEGER tmp2 = {0};tmp1.QuadPart = p_dbr->ll_offset + (ntfs->mft_lcn.QuadPart * ntfs->sectors_per_cluster * SECTOR_SIZE);tmp2.QuadPart = p_dbr->ll_offset - (ntfs->sectors_nbr * SECTOR_SIZE) + (ntfs->mft_lcn.QuadPart * ntfs->sectors_per_cluster * SECTOR_SIZE);if (!ReadFileOffset(file_path, tmp1.QuadPart, 4, sz_temp1, FILE_BEGIN))ErrorOut("ReadFile Error!\n");if (!memcmp(sz_temp1, "FILE", 4)){p_dbr->ll_start_sector = p_dbr->ll_offset / SECTOR_SIZE;return 1;}else{if (!ReadFileOffset(file_path, tmp2.QuadPart, 4, sz_temp2, FILE_BEGIN))ErrorOut("ReadFile Error!\n");if (!memcmp(sz_temp2, "FILE", 4)){p_dbr->ll_start_sector = p_dbr->ll_offset / SECTOR_SIZE - ntfs->sectors_nbr;p_dbr->n_is_org = 1;return 1;}}return 0; } 復(fù)制代碼對于FAT32型,需要考慮的似乎只有是否滿足能正確找到FAT表,如0x05中所說,其大小是需要靠后一個分區(qū)的起始扇區(qū)或文件總大小來獲取的。
#!cpp if (p_dbr_temp->n_type == 2) // FAT {p_fat_temp = (fat_boot_sector*)p_dbr_temp->dbr;if (!memcmp(p_fat_temp->BS_FilSysType, "FAT32", 5)) // 只處理FAT32{flag = 0;flag = JudgeFAT(file_path, p_dbr_temp, p_fat_temp);if (flag){p_dbr_temp->flag = 1;g_n_part++;}} 復(fù)制代碼在對于FAT32型DBR,同樣,也需要考慮獲取的DBR是backup的情況:
#!cpp int JudgeFAT(char *file_path, dbr_list* p_dbr, fat_boot_sector* fat) {char sz_temp1[4] = {0};char sz_temp2[4] = {0};LARGE_INTEGER tmp1 = {0};LARGE_INTEGER tmp2 = {0};char flag[4] = {'\xf8', '\xff', '\xff', '\x0f'};DWORD readsize = 0;tmp1.QuadPart = p_dbr->ll_offset + (fat->reserved * SECTOR_SIZE);tmp2.QuadPart = p_dbr->ll_offset - ((fat->backup_boot + fat->reserved) * SECTOR_SIZE);if (!ReadFileOffset(file_path, tmp1.QuadPart, 4, sz_temp1, FILE_BEGIN))ErrorOut("ReadFile Error!\n");if (!memcmp(sz_temp1, flag, 4)){p_dbr->ll_start_sector = p_dbr->ll_offset / SECTOR_SIZE;return 1;}else{if (!ReadFileOffset(file_path, tmp2.QuadPart, 4, sz_temp2, FILE_BEGIN))ErrorOut("ReadFile Error!\n");if (!memcmp(sz_temp2, flag, 4)){p_dbr->ll_start_sector = p_dbr->ll_offset / SECTOR_SIZE - fat->backup_boot;p_dbr->n_is_org = 1; return 1;}}return 0; } 復(fù)制代碼完成這一步后,我們得到了篩選出了整個磁盤中所有可用的DBR信息,并且獲取到了重建分區(qū)表所需要的分區(qū)類型、起始扇區(qū)、分區(qū)大小三個信息。
可以將其輸出由用戶選擇需要恢復(fù)的分區(qū):
#!cpp /*顯示DPT*/ int ShowDPT() {dbr_list* p_dbr_temp = NULL; __int64 tmp = 0;printf("\n\nChosse the partition you want to rebuild?\n");for(p_dbr_temp = g_dbr_list_head->p_next; p_dbr_temp != NULL;) {if (p_dbr_temp->flag) // 需要添加{p_dbr_temp->flag = 0; // 清空標(biāo)志位置if (tmp < p_dbr_temp->ll_start_sector){printf("\nPartition with type %s.\tStart with %lld sectors.\t Size %lld sectors.\t End with %lld sectors.\nIs this partition you want to restore?(y/n)", (p_dbr_temp->n_type == 1?"NTFS":"FAT32"), p_dbr_temp->ll_start_sector,p_dbr_temp->ll_total_sector,p_dbr_temp->ll_start_sector + p_dbr_temp->ll_total_sector);if (getchar() == 'y'){p_dbr_temp->flag = 1;tmp = p_dbr_temp->ll_start_sector + p_dbr_temp->ll_total_sector;g_n_part++;getchar();}elsegetchar();}}p_dbr_temp = p_dbr_temp->p_next;}return 0; } 復(fù)制代碼0x09 重構(gòu)DPT
重構(gòu)DPT需要考慮的問題有以下幾個:
總分區(qū)數(shù)是否大于4個
a. 若不是,則可全寫入0扇區(qū)MBR中 b. 若是,則需要新建擴(kuò)展分區(qū)完成擴(kuò)展
獲取到的DBR是否為分區(qū)起始扇區(qū)DBR
a. 若是,則無需更改 b. 若不是,則需要將作為backup的DBR復(fù)制到分區(qū)起始扇區(qū)
分區(qū)表是線性一次擴(kuò)展下去的,不存在分區(qū)交叉的情況,即各分區(qū)大小之和為文件總大小,各分區(qū)無公共部分。(這一步在0x08中的showdpt中做了處理)
創(chuàng)建擴(kuò)展分區(qū)時,主分區(qū)DPT最后一條記錄是指向擴(kuò)展DPT,擴(kuò)展DPT的最后一條記錄繼續(xù)指向下一個擴(kuò)展DPT。
MBR中boot_code部分信息可以不用考慮
考慮完了這些問題,就可以來編碼實現(xiàn)了。我的做法是,新建一個用于重構(gòu)的鏈表,將所有要更改的內(nèi)容、要更改的內(nèi)容的大小、以及要更改的位置寫入到鏈表中,方便后面寫文件以及恢復(fù)文件。
#!cpp void ReBuildDPT(char* sz_file_path) {rebuild_content_t* rebuild_list = CreateReBuildHead();if (g_n_part <= 4) // 小于四個分區(qū)時,只需要建立主分區(qū)表{sz_tmp = (char*)malloc(4 * sizeof(PartTableRecord) + 2);memset(sz_tmp, 0, 4 * sizeof(PartTableRecord) + 2);for(p_dbr_temp = g_dbr_list_head->p_next; p_dbr_temp != NULL;) {if (p_dbr_temp->flag) // 是否需可用信息{*(sz_tmp + k * 16 + 4) = (p_dbr_temp->n_type == 1) ? 0x07 : 0x0B; // byPartTypememcpy(sz_tmp + k * 16 + 8, (char *)&(p_dbr_temp->ll_start_sector), sizeof(__int64)); // dwStartSectormemcpy(sz_tmp + k * 16 + 12, (char *)&(p_dbr_temp->ll_total_sector), sizeof(__int64)); // dwTotalSectork++;if (p_dbr_temp->n_is_org) // 是否起始扇區(qū){InsertRebuildList(rebuild_list, p_dbr_temp->dbr, SECTOR_SIZE, p_dbr_temp->ll_start_sector, i++);}}p_dbr_temp = p_dbr_temp->p_next;}memcpy(sz_tmp + 64, sign, 2); InsertRebuildList(rebuild_list, sz_tmp, 4 * sizeof(PartTableRecord) + 2, 446, i++);}else // 否則考慮擴(kuò)展分區(qū)的情況{sz_tmp = (char*)malloc(4 * sizeof(PartTableRecord) + 2);memset(sz_tmp, 0, 4 * sizeof(PartTableRecord) + 2);for(p_dbr_temp = g_dbr_list_head->p_next; p_dbr_temp != NULL;) {if (p_dbr_temp->flag) // 是否需可用信息{if (k < 3) // 主分區(qū)只能有三個,最后一個為擴(kuò)展分區(qū){if (k != 2){*(sz_tmp + k * 16 + 4) = (p_dbr_temp->n_type == 1) ? 0x07 : 0x0B; // byPartTypememcpy(sz_tmp + k * 16 + 8, (char *)&(p_dbr_temp->ll_start_sector), sizeof(__int64)); // dwStartSectortmp = p_dbr_temp->ll_total_sector + 1;memcpy(sz_tmp + k * 16 + 12, (char *)&tmp, sizeof(__int64)); // dwTotalSectork++;}else{*(sz_tmp + k * 16 + 4) = (p_dbr_temp->n_type == 1) ? 0x07 : 0x0B; // byPartTypememcpy(sz_tmp + k * 16 + 8, (char *)&(p_dbr_temp->ll_start_sector), sizeof(__int64)); // dwStartSectortmp = p_dbr_temp->ll_total_sector + 1;memcpy(sz_tmp + k * 16 + 12, (char *)&tmp, sizeof(__int64)); // dwTotalSectork++;for (p_dbr_temp_tmp = p_dbr_temp->p_next; p_dbr_temp_tmp != NULL;){if (p_dbr_temp_tmp->flag){*(sz_tmp + k * 16 + 4) = 0x05; // byPartTypetmp = p_dbr_temp_tmp->ll_start_sector - 1;memcpy(sz_tmp + k * 16 + 8, (char *)&tmp, sizeof(__int64)); // dwStartSectortmp = (g_ll_file_size/SECTOR_SIZE) - p_dbr_temp_tmp->ll_start_sector + 1;memcpy(sz_tmp + k * 16 + 12, (char *)&tmp, sizeof(__int64)); // dwTotalSectork++;memcpy(sz_tmp + 64, sign, 2); InsertRebuildList(rebuild_list, sz_tmp, 4 * sizeof(PartTableRecord) + 2, 446, i++);break;}p_dbr_temp_tmp = p_dbr_temp_tmp->p_next;}}}else{sz_tmp = NULL;sz_tmp = (char*)malloc(4 * sizeof(PartTableRecord) + 2);memset(sz_tmp, 0, 4 * sizeof(PartTableRecord) + 2);*(sz_tmp + 4) = (p_dbr_temp->n_type == 1) ? 0x07 : 0x0B; // byPartTypetmp = 1; // 擴(kuò)展分區(qū)偏移地址從當(dāng)前地址算起(相對地址)memcpy(sz_tmp + 8, (char *)&tmp, sizeof(__int64)); // dwStartSectormemcpy(sz_tmp + 12, (char *)&(p_dbr_temp->ll_total_sector), sizeof(__int64)); // dwTotalSectorif (p_dbr_temp->p_next != NULL){for (p_dbr_temp_tmp = p_dbr_temp->p_next; p_dbr_temp_tmp != NULL;){if (p_dbr_temp_tmp->flag){*(sz_tmp + 16 + 4) = 0x05; // byPartTypetmp = p_dbr_temp_tmp->ll_start_sector - p_dbr_temp->ll_start_sector;//tmp = 1;memcpy(sz_tmp + 16 + 8, (char *)&tmp, sizeof(__int64)); // dwStartSectortmp = (g_ll_file_size/SECTOR_SIZE) - p_dbr_temp_tmp->ll_start_sector;memcpy(sz_tmp + 16 + 12, (char *)&tmp, sizeof(__int64)); // dwTotalSectorbreak;}p_dbr_temp_tmp = p_dbr_temp_tmp->p_next;}}memcpy(sz_tmp + 64, sign, 2); InsertRebuildList(rebuild_list, sz_tmp, 66, (p_dbr_temp->ll_start_sector - 1) * SECTOR_SIZE + 446, i++);}if (p_dbr_temp->n_is_org) // 是否起始扇區(qū){InsertRebuildList(rebuild_list, p_dbr_temp->dbr, SECTOR_SIZE, p_dbr_temp->ll_start_sector * SECTOR_SIZE, i++);}}p_dbr_temp = p_dbr_temp->p_next;}}HandleFile(sz_file_path, rebuild_list);FreeRebuildList(rebuild_list); } 復(fù)制代碼0x0A 文件處理
通過前面的操作,可以得到處理好了的rebuild_list。接下來要做的就是用它來完成重建分區(qū)表,恢復(fù)DBR的工作。
首先,我們的目的是,重建后的分區(qū)表后文件能作為VHD直接被win7以上系統(tǒng)加載。其次,希望能夠在我卸載VHD文件后,仍然恢復(fù)到原有狀態(tài)。意味著需要對更改的信息做一個備份,這沒問題,因為我們替換先前rebuild_list中的content就可以完成了。
之后,需要了解VHD文件格式。找了找資料。發(fā)現(xiàn),VHD文件僅僅在文件尾部添加了一個扇區(qū)的內(nèi)容,其結(jié)構(gòu)如下:
#!cpp /*vhd尾部信息結(jié)構(gòu)*/ typedef struct hd_ftr_t { char cookie[8]; /* Identifies original creator of the disk */ unsigned int features; /* Feature Support -- see below */ unsigned int ff_version; /* (major,minor) version of disk file */ unsigned __int64 data_offset; /* Abs. offset from SOF to next structure */ unsigned int timestamp; /* Creation time. secs since 1/1/2000GMT */ char crtr_app[4]; /* Creator application */ unsigned int crtr_ver; /* Creator version (major,minor) */ unsigned int crtr_os; /* Creator host OS */ unsigned __int64 orig_size; /* Size at creation (bytes) */ unsigned __int64 curr_size; /* Current size of disk (bytes) */ unsigned int geometry; /* Disk geometry */ unsigned int type; /* Disk type */ unsigned int checksum; /* 1's comp sum of this struct. */ unsigned char uu[16]; /* Unique disk ID, used for naming parents */ char saved; /* one-bit -- is this disk/VM in a saved state? */ char hidden; /* tapdisk-specific field: is this vdi hidden? */ char reserved[426]; /* padding */ }hd_ftr; 復(fù)制代碼這張表中,重要的就是orig_size、curr_size和checksum,通常情況下orig_size與curr_size相同,checksum是最后一個扇區(qū)所有字節(jié)相加后取反的值。我建了個模板來實現(xiàn)VHD標(biāo)志位的添加。
所以,最終的文件處理模塊如下:
#!cpp void HandleFile(char* file_path, rebuild_content_t* p_rebuild_list) {char* sz_vhd_buf = (char*)malloc(SECTOR_SIZE);memset(sz_vhd_buf, 0, SECTOR_SIZE);rebuild_content_t* p_rebuild_tmp = NULL;char tmp[SECTOR_SIZE] = {0}; // Gen VHDhd_ftr* vhd;vhd = (hd_ftr*)data;LARGE_INTEGER offset = {0};DWORD readsize = 0;/*Set hd_ftr struct*/vhd->orig_size = 0; // clearvhd->orig_size = g_ll_file_size - SECTOR_SIZE;vhd->orig_size = INT64_TO_NET(vhd->orig_size);vhd->curr_size = vhd->orig_size;vhd->checksum = 0;/*calc checksum*/unsigned int temp = 0;for (int i = 0; i < 512; i++){temp += data[i];}vhd->checksum = htonl(~temp); //for(p_rebuild_tmp = p_rebuild_list->p_next; p_rebuild_tmp != NULL;) {if (!ReadFileOffset(file_path, p_rebuild_tmp->ll_offset, p_rebuild_tmp->n_size, tmp, FILE_BEGIN))ErrorOut("Backup Read Error!\n");if (!WriteFileOffset(file_path, p_rebuild_tmp->ll_offset, p_rebuild_tmp->n_size, p_rebuild_tmp->content, FILE_BEGIN))ErrorOut("Backup Write Error!\n");memcpy(p_rebuild_tmp->content, tmp, p_rebuild_tmp->n_size); // BackUp SECTORp_rebuild_tmp = p_rebuild_tmp->p_next;}/ BackUp VHDReadFileOffset(file_path, -SECTOR_SIZE, SECTOR_SIZE, sz_vhd_buf, FILE_END);/* */// Write VHDWriteFileOffset(file_path, -SECTOR_SIZE, SECTOR_SIZE, (char*)vhd, FILE_END);printf("WriteFile Success! You can mount it as vhd file now!\n");system("pause");// Restore SECTORfor(p_rebuild_tmp = p_rebuild_list->p_next; p_rebuild_tmp != NULL;) {if (!ReadFileOffset(file_path, p_rebuild_tmp->ll_offset, p_rebuild_tmp->n_size, tmp, FILE_BEGIN))ErrorOut("Restore Read Error!\n");if (!WriteFileOffset(file_path, p_rebuild_tmp->ll_offset, p_rebuild_tmp->n_size, p_rebuild_tmp->content, FILE_BEGIN))ErrorOut("Restore Write Error!\n");memcpy(p_rebuild_tmp->content, tmp, p_rebuild_tmp->n_size); // BackUp SECTORp_rebuild_tmp = p_rebuild_tmp->p_next;}/ Restore VHDWriteFileOffset(file_path, -SECTOR_SIZE, SECTOR_SIZE, sz_vhd_buf, FILE_END);printf("Restore File Success!\n"); } 復(fù)制代碼這樣,重建分區(qū)表的工作就完成了。
0x0B 程序效果
還是用前面例子中用到的VHD文件來做演示,該VHD現(xiàn)在的情況是:先前新建過兩個NTFS的分區(qū),之后刪除了一個NTFS,并重新格式化成FAT32,再然后,我將其分區(qū)全部刪除,并新建了3個NTFS與兩個FAT32,最終效果如圖6所示。現(xiàn)在我們用剛寫好的工具來對它進(jìn)行搜索分析。
圖17:
可以看到它總共搜索到了8個可用的DBR信息,但顯然其中有一些是前幾次分區(qū)留下的內(nèi)容。選擇保留1.2.3.5.7五個分區(qū)(也就是圖6所示的五個分區(qū)),起始你選擇保留第三個的時候,就不有機(jī)會讓你選擇第四個了,因為他們是沖突的。結(jié)果如圖18:
提示寫文件成功,并已經(jīng)可以直接當(dāng)做VHD文件掛載了,我們用winhex打開處理過的文件,得到圖19:
可以看到,重構(gòu)后的分區(qū)表幾乎與原來的分區(qū)表一致。
接著,我們試著來恢復(fù)前幾次分區(qū)留下的信息,看是否能夠成功。圖20:
這里我選擇恢復(fù)了一個NTFS和一個FAT32,再進(jìn)winhex中查看,得到圖21:
可以看到第一個NTFS分區(qū),是并不存在與圖6的五個分區(qū)中的。
再次,我們用圖1的那個TrueCrypt解密后的打不開的磁盤作為例子,看看這次能否恢復(fù)出正確的分區(qū)表,并且,實現(xiàn)加載。找到可用的DBR信息如圖22:
按照邏輯,選擇了第一個后,就只能選擇1和2。已提示寫文件成功,圖23:
Winhex解析如圖24:
正確完整恢復(fù),所有文件也能正確解析,圖25:
接著,在做只留下尾部備份分區(qū)測試程序需找情況的時候,發(fā)現(xiàn)起始我們程序中并沒有必要將備份分區(qū)復(fù)制到起始扇區(qū),因為,也能正常解析。
于是,完工!(不過,還有個小細(xì)節(jié)需要注意,如果起始位置是個DBR頭部的話,系統(tǒng)將不會當(dāng)做MBR處理,而是當(dāng)做DBR,所以,清除頭部信息也很重要!)
0x0C Summarize
加上寫代碼的時間和些這文章的時間,大概花費了近十天吧。各種找資料、調(diào)程序、做樣本,也算是把恢復(fù)分區(qū)表的原理弄清楚了。同時,也把忘得差不多的磁盤格式、文件系統(tǒng)什么的再撿起來看了看。當(dāng)然文章其實并不詳細(xì),因為關(guān)注點不一樣,就沒講太多關(guān)于文件系統(tǒng)的內(nèi)容了。再往下,可以詳細(xì)到文件的恢復(fù)等等,當(dāng)然,起始也沒必要去做,畢竟現(xiàn)有的工具一大堆,我只是突然感興趣就拿來實現(xiàn)了一把。
再者,本程序暫時只支持NTFS和FAT32兩種類型,若有其他類型的原理應(yīng)該也是一致,大家仔細(xì)琢磨。
寫文章也比較倉促,也沒校稿習(xí)慣,若有勘誤,還請諒解并提醒更正。感謝!
源碼下載地址:github.com/purpleroc/h…
另推薦學(xué)習(xí)源碼:testdisk、ReadPartTable
——Tracy_梓朋
2016年1月6日21:31:52
總結(jié)
- 上一篇: Day8:盈利源泉是否可持续(1)
- 下一篇: vue3 + vite +ts 引入静态