磁碟分割槽表恢復原理

wyzsk發表於2020-08-19
作者: purpleroc · 2016/01/13 19:36

Author: [email protected]

Email: [email protected]

0x00 引子


實在是詞窮想不到要怎麼寫題目了,就把vs中的工程名當題目吧。

這篇文,主要講講MBR、DBR、NTFS、FAT32結構等等諸如此類的東西,以及在資料恢復中,我們可以從現有的被破壞了的磁碟中獲取到哪些有利於我們進行資料恢復的資訊。

不知道是最近沒休息好還是其他原因,總覺得靜不下心、集中不了注意力,也不知道從什麼時候開始,瀏覽網頁只需要幾秒鐘,查詢資料也從來不會耐心看完文章,總是一翻到底,用最快的速度去搜尋、定位自己要找的內容。但通常來說,網上有的與你問題相同的解決方案並不多。而人們寫文章往往不是奔著主題去的,而是和寫論文、寫書一樣,先把種種後面要用到的概念堆砌起來,然後再來慢慢的說解決方案。當然,其實這樣也挺好的,但作為一個目的驅動者,我更喜歡看需要用到的時候再講的內容。

於是,這篇文就這麼來寫吧,免得你看完NTFS和FAT32檔案系統就不想看下去了。

0x01 背景


事情是這樣的,這幾天在測試TrueCrypt解密的時候,碰到這麼一種情況:

用TrueCrypt做整盤加密系統時,TrueCrypt會重寫磁碟的MBR區域,將原本的MBR加密儲存到其他位置,啟動過程中透過了TrueCrypt的密碼驗證後再在記憶體中恢復原先的MBR並引導進入系統

這麼做沒問題,用我的解密程式解完後也能夠正常掛載並訪問,可另一種情況來了:

如果加密的磁碟中,並不是只有一個c盤,而還有其他分割槽

這時,我的解密工具便不能直接掛載了,雙擊出現:

圖1: Alt text

Winhex開啟看了看,解密後的資料都是正確的,也看到了DBR:

圖2: Alt text

可還是打不開,為啥呢?猜測是分割槽表被破壞了。那怎麼修復呢?瞬間想到了大一那會兒幫別人修電腦,搞壞分割槽表,花了一晚上找資料(而且還沒找全)的黑歷史,畢竟那會兒不知道用重建分割槽表的功能,也不理解原理,也就那次之後,呆圖書館看了蠻多資料恢復的書籍,去了解原理。

於是對解密後的檔案做了個映象,用資料恢復軟體Diskgenuis開啟,搜尋並重建分割槽表,得到結果如下:

圖3: Alt text

發現他很神奇的把兩個分割槽找回來了,如果點儲存,再去winhex中看,就能當磁碟來分析了。

所以,我的目的是想知道他是怎麼恢復分割槽表的!

想自己寫個程式,能修復映象檔案中損壞了的分割槽表,並且能夠當做vhd檔案形式被win7以上的系統直接載入,並且在載入完之後,恢復到原有形態。

於是,憋了一週,邊寫邊找資料,終於把程式寫完來寫這文章了。

要恢復分割槽表,首先得從損壞了分割槽表的磁碟裡找出分割槽資訊,再用這些資訊來生成分割槽表。

那問題是,磁碟中會有哪些資訊呢?   

0x02 定址方式(CHS/LBA)


在用正常磁碟做講解前先來了解下磁碟的兩種定址方式,一種是CHS(cylinder head sector)定址方式、一種是LBA(Logical Block Addressing)邏輯塊定址方式。其中CHS(定址方式)在分割槽表中使用24個bit位(前10位表示cylinder、中間8位表示head、後6位表示sector),其最大定址空間為8GB,後因為其滿足不了要求了,人們給他擴充到28bit,但定址空間也只有128G,面對現有的動輒上TB的硬碟還是無能為力。LBA是用來取代CHS的,LBA是一個整數,常見有32bit和64bit,32bit最大定址空間達到2TB。

不管CHS(定址方式)也好,還是LBA(定址方式)也好。磁碟儲存定址都需要透過cylinder、head、sector這三個變數來實現。

從上面我們瞭解到的資訊就是,有兩種定址方式,而且可以相互轉換,再然後呢,歸根到底其實就是用的CHS方式。

這一塊的詳細的介紹以及轉換方式我就不說了,有興趣的可以百度百度,這裡也提供一個連結:

http://blog.csdn.net/haiross/article/details/38659825

0x03 主開機記錄MBR


接著,來看看正常的磁碟中的MBR,所謂MBR即Main Boot Record 主開機記錄區,位於整個硬碟的0磁軌0柱面1扇區(也可以用LBA描述成0扇區)。總共佔512位元組,通常也就是1個扇區的大小。其重要作用就是負責從BIOS手中接過引導權,再去找可引導的分割槽,並將許可權交給可引導的DBR(Dos Boot Record),完成系統啟動。

MBR雖然佔了一個扇區,但其Boot_Code部分只佔了446個位元組。其餘64個位元組為DPT(Disk Partition Table硬碟分割槽表),對就是我們要恢復的東西,最後2個位元組就是傳說中的標誌位55AA了。於是,他的結構體大體如下:

#!cpp
typedef struct MBR_T
{
    UCHAR boot_code[446];
    PartTableRecord partition[4];
    UCHAR sign[2];
}MBR;

對照著結構體,我們看看winhex中的截圖:

圖4: Alt text

黃色區域就是boot_code所佔用的446個位元組,紅色部分就是DPT,藍色就是標誌位了。   

0x04 磁碟分割槽表DPT


既然找到了DPT,那肯定是要分析清楚,它是幹嘛用的,裡面都有些什麼資訊呢?

直接用winhex的模板看看先:

圖5: Alt text

桌面太小,截圖不完全,但也大體知道了裡面會有些什麼資訊,順便翻出結構體如下:

#!cpp
typedef struct PartTableRecord_t
{

    BYTE    byIsBoot;           //引導分割槽      1B  1B  80(引導分割槽),00(非引導分割槽)
    BYTE    byStartHead;        //起始磁頭      1B  2B
    BYTE    byStartSector;      //起始扇區      1B  3B  
    BYTE    byStartCylinder;    //起始柱面      1B  4B
    BYTE    byPartType;         //分割槽型別      1B  5B  07(NTFS),05(擴充套件分割槽),0B(FAT32)
    BYTE    byEndHead;          //結束磁頭      1B  6B
    BYTE    byEndSector;        //結束扇區      1B  7B
    BYTE    byEndCylinder;      //結束柱面      1B  8B
    DWORD   dwStartSector;      //開始扇區      4B  12B     
    DWORD   dwTotalSector;      //分割槽扇區數 4B  16B 最大2T Byte
} PartTableRecord;

恩,每個分割槽表佔用16個位元組,而MBR中只留了64個位元組,這也是為什麼一塊硬碟最多隻能建立4個主分割槽的原因了。多了放不下。那,為啥我們可以看到比四個還多的分割槽呢?因為擴充套件分割槽裡面可以建立邏輯分割槽,這裡個數不定,只要你磁碟機代號夠,想建立多少就建立多少。

從結構體後的註釋語句也可以知道16位元組中每一位分別代表什麼含義。這裡需要注意的是,表示分割槽位置和大小的地方有兩個,我們可以透過起始磁頭、扇區、柱面和結束磁頭、扇區、柱面來得到分割槽位置和大小,也可以直接透過LBA模式記錄的開始扇區和分割槽扇區數來獲取到分割槽的位置和大小。

那,問題來了,他們哪個是有用的?

在第二節中定址方式裡講過,CHS能記錄的最大的分割槽是8.4GB,超過這個大小,就無力了。那這時候32位的LBA自然就派上用場了。

我做了個實驗,把所有分割槽表中的CHS記錄全部清零,再用winhex載入,還是能夠正常識別。於是,我決定了,後面所有定址方式均以LBA方式來說。

再回到winhex中看分割槽資訊,來對照DPT一一理解。

圖6: Alt text

上圖可以看出,這塊硬碟總共有5個分割槽。其中主分割槽三個,擴充套件分割槽1個,邏輯分割槽2個(邏輯分割槽是在擴充套件分割槽裡面的)。也就是MBR中64個位元組除了主分割槽就是擴充套件分割槽。

根據DPT中的起始扇區以及扇區大小,就可以得到上圖中每個分割槽的大小、1st sector(起始扇區)了。主分割槽好說,我們對照著DPT中的其實位置和大小都能看得出,那擴充套件分割槽是怎麼個形態呢?

我們直接看第四個DPT的資訊:007A300B05FE3F1880D0020000600300,忽略掉CHS部分,並對照結構體來看,它告訴了我們這些資訊:

#!bash
byIsBoot = 0x00   // 非引導分割槽
byPartType = 0x05  // 擴充套件分割槽
dwStartSector = 0x0002D080     //起始扇區 184448
dwTotalSector = 0x00036000    //分割槽大小 221184

這裡可以看到,擴充套件分割槽的起始位置其實也就是三個主分割槽的總大小了,再加上自身的分割槽大小,就是整個磁碟的大小了。例如我這個磁碟是200MB的,現在大小應該是184448 + 221184 = 405632,注意單位是扇區,所以換算成MB應該是 405632/512 * 1024 * 1024 = 198.0625MB。為什麼與檔案總大小相比少了呢?因為,在分割槽表後面還有一些未被使用的空間。好奇的是,這個擴充套件分割槽中到底放了些什麼呢?

在winhex中Ctrl+G輸入扇區號184448,跟隨過去:

圖7: Alt text

還是和剛才一樣,黃色部分為前446位元組,這裡全為0,因為不需要boot_code,而後64位元組為擴充套件分割槽的分割槽表資訊。在圖的左右下方分別標示出了現在所在的偏移扇區位置,以及總扇區個數和偏移位置(位元組數表示)。還是來看這裡的DPT資訊吧,有兩個分割槽有資訊,這次直接用winhex來看:

圖8: Alt text

第一個分割槽起始扇區是128,總大小為122880,型別是ntfs;第二個分割槽起始扇區是123008,總大小為94338,型別是擴充套件分割槽。需要注意的是,擴充套件分割槽的起始扇區是需要加上基地址(擴充套件分割槽偏移扇區位置)的。也就是說,我們看到的第一個分割槽,實際起始地址為:184448 + 128 = 184576,與圖6的partition4的起始位置是一樣的,那下一個呢?再來一個擴充套件分割槽的型別是怎麼個意思,也還是算一下實際的起始地址:184448 + 123008 = 307456。

再次Ctrl + G跟隨過去:

圖9: Alt text

同樣的,再次找到一個DPT資訊,這裡面只有一項,也就是圖6中的第五個分割槽了,也來算一下吧:

起始扇區 = 307456 + 128 = 307584,與圖6中第五個分割槽起始位置一致。

從上面的例項中可以得出,整個磁碟大概是這麼分佈的:

圖10: Alt text

再看擴充套件分割槽的連結圖示:

圖11: Alt text

於是,回到背景裡提到的目標,我們要做的就是,根據磁碟中存有的資訊,來重建出這麼一個分割槽表。

自然的,我們需要去知道分割槽表指向的內容是什麼!

0x05 作業系統引導分割槽DBR


在上一節裡面,提到了DPT,也提到了分割槽表的結構體,從結構體裡我們可以看到偏移5的位置有鍵值byPartType,分割槽型別,去找了找資料,這裡的取值非常多,常見型別大致如下:

#!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

在結合上一節的分割槽表,這裡主要關注05、07、0B,即擴充套件分割槽、NTFS、FAT32三種。而05的在上一節介紹過了,那麼,我們將目光投向NTFS與FAT32兩種型別。

5.1 NTFS(New Technology File System)

首先,從MBR中看到分割槽資訊能知道,分割槽1是NTFS分割槽,在winhex中跟隨上一節中分割槽1的起始位置,可以看到如下資訊,圖12:

Alt text

已將每個資料對應的結構和資料都著色了,然後也是時候拿出NTFS檔案系統中DBR的資料結構了:

#!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 ;

再次想想我們的目的,是要重構分割槽表,而且,我們可以看出,分割槽表中最重要的就是各分割槽的分割槽型別、起始位置以及

分割槽大小,三個資訊了。當然,還有是否為活動分割槽,但這裡我們主要是做資料恢復,所以,暫不考慮其是否作為引導分割槽了。是的,我們只要獲取到每一個分割槽的分割槽型別、起始位置、分割槽大小三個資訊。

我們來看看這個結構體中能提供給我們一些什麼:

  • sectors_nbr:總扇區數,即分割槽大小
  • system_id:檔案系統型別,即分割槽型別

而在剛才的試驗中,我們也可以看到,分割槽表中每個起始位置對應的資訊,都是指向DBR(Dos Boot Record作業系統引導分割槽)結構體的。於是,分割槽的起始位置也是可以獲取到的。

於是,我們的思路是從磁碟中去搜尋各個DBR,然後獲取到分割槽表所需資訊,再建立分割槽表。

既然聊到了NTFS,我們再來看看上面結構體中,還有哪些資訊是我們需要的吧:

  • sector_size:每扇區位元組數,一般情況下是固定512位元組的
  • sectors_per_cluster:這個也挺重要的,需要引入一個新概念,“簇”。他在後面要用的MFT中起作用。先看看這個鍵值是幹嘛用的,它表示的是每簇的扇區數量。
  • hidden:字面意思是未使用的扇區數。這裡表示的是從分割槽表到DBR的扇區數量。若為主分割槽,則hidden表示的是扇區的起始位置。若是擴充套件分割槽,則hidden表示的是從擴充套件分割槽表到該分割槽的扇區數。
  • mft_lcn$MFT(主檔案表)的偏移位置,這裡的單位是簇,他LBA位置 = 分割槽offset + mft_lcn * sectors_per_cluster。以 圖:12 為例,分割槽起始位置為128,sectors_per_cluster = 8,mft_lcn = 853,所以LBA位置為:128 + 8 * 853 = 6952。

    為了證實,我們在winhex中跟隨到6952扇區,如下:

    圖13: Alt text

    在這裡,我們也可以用MFT作為判斷某個搜尋到的DBR是否正確的條件。有想更詳細瞭解MFT的可以去找找資料繼續看檔案管理方式,這裡就不展開了,畢竟,我們這次主要目的是恢復分割槽表。

  • mftmirr_lcn:每個MFT表所佔用的扇區數量。一般為2.

對我們有用的資訊大概也就是上面所提到的了。所以對於恢復NTFS型分割槽,我們的策略是,全盤搜尋,找到NTFS型DBR,之後透過MFT資訊來判斷該分割槽是否正確。同時對於NTFS還需要提醒的是,其DBR扇區不僅僅在分割槽頭部存在,在分割槽的最後一個扇區中也有一個備份。所以,就算分割槽首部的DBR被破壞了,我們也可以透過分割槽尾部的DBR來恢復出分割槽表。   

5.2 FAT32(32位檔案分配表)

上一小節中,我們知道了怎麼重建NTFS型分割槽,接著,來看看FAT32。回到圖6中,看看第三個分割槽。winhex跟過去,得到截圖如下,這次就不上色了,太累~~~

圖14: Alt text

找了找資料,翻出FAT32的結構體表示:

#!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 case
                            partition 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;

前面是BIOS Parameter Block結構體的一些資訊,是公用的,後面的才是單純的FAT32所需要的。用winhex的模板解析一下,得到下面的內容。

圖15: Alt text

首先,我們需要的檔案型別是可以透過system_id來獲取到的,起始扇區也是可以透過DBR所在的位置得到的,相對NTFS來說FAT32中少了sectors_nbr鍵值,那我們應該如何去獲取到FAT32的總大小呢?

我採用的辦法是,用搜尋到的它的後一個DBR的位置減去當前位置,如果後面沒有分割槽了,則用檔案總大小減去當前偏移位置。

這樣,我們也能將分割槽表需要的三個資訊得到。再然後,我們也要來說說這結構體裡還有哪些對我們有用的資訊。

  • hidden:字面意思是未使用的扇區數。這裡表示的是從分割槽表到DBR的扇區數量。若為主分割槽,則hidden表示的是扇區的起始位置。若是擴充套件分割槽,則hidden表示的是從擴充套件分割槽表到該分割槽的扇區數。
  • total_sect:本來可以用來表示分割槽大小的,可只是個長整形變數,只能正確表示小分割槽的大小,對於大分割槽無力了。
  • reserved:保留扇區數,起始,他指的是第一個FAT1表所在的相對偏移位置,例如,在partition3中,起始地址是82048扇區,reserved扇區數量是6718扇區,可以得到,FAT1表起始扇區為82048 + 6718 = 88766。在winhex跟過去,得到資訊如下:

    圖16: Alt text

  • 途中每一種顏色代表一個目錄表項,綠色為0號FAT項,淡黃色為1號FAT項。通常0號FAT項總為0x0ffffff8。於是,這一特徵也可以被我們當做判斷分割槽表是否正確的標準。

  • fats:表示的是FAT表的個數,通常為2個,即FAT1和FAT2。

  • fat32_length:每個FAT表的扇區數。在上面我們定位到了FAT1,那麼FAT2就是用FAT1的起始位置加上每個FAT表的大小。

  • info_sectorfilesystem_info的起始位置,起始也可以作為FAT32分割槽是否正確的判斷標誌。

  • backup_boot:FAT32的DBR備份檔案存在的位置。上例中,backup_boot = 6,即DBR備份存放在分割槽起始扇區+6的地方。

有關FAT的一些結構和作用:

  1. FAT32檔案一般有兩份FAT,他們由格式化程式在對分割槽進行格式化時建立,FAT1是主,FAT2是備份。
  2. FAT1跟在DBR之後,其具體地址由DBR的BPB引數中指定,FAT2跟在FAT1的後面。
  3. FAT表由FAT表項構成,我們把FAT表項簡稱FAT項,每個FAT項佔用4位元組。
  4. 每個FAT項都有一個固定的編號,這個編號從0開始。
  5. FAT表項的前兩個FAT項為檔案系統保留使用,0號FAT為介質型別,1號FAT為檔案系統錯誤標誌。
  6. 分割槽的資料區中每個簇都會對映到FAT表中的唯一一個FAT項,因為0號FAT和1號FAT被系統佔用,使用者的資料從2號FAT開始記錄。
  7. 如果某個檔案佔用很多個簇,則第一個FAT項記錄下一個FAT項的編號(既簇號),如果這個檔案結束了,則用“0F FF FF FF”表示。
  8. 分割槽格式化後,使用者檔案以簇為單位存放在資料區中,一個檔案至少佔用一個簇。
  9. FAT的主要作用是標明分割槽儲存的介質以及簇的使用情況。

具體有關FAT的其他資訊,還請自行收集資料,因為,有了前面的資訊,我們也可以有辦法恢復FAT32分割槽對應的分割槽表資料了。

瞭解了前面的知識之後,我們就可以開始編寫程式來對分割槽表進行重建了。

0x06 FileMapping(檔案對映)


首先需要從磁碟、磁碟映象中找到DBR存在的痕跡,就需要對整個磁碟或映象檔案進行遍歷搜尋,由於分割槽是線性擴充套件的,而且DBR所在的位置永遠的是扇區開頭,並且獨自佔有一整個扇區。於是我們可以遍歷檔案或磁碟中的每個扇區來快速完成搜尋。

對於磁碟或者磁碟映象檔案,肯定不能是通常的直接fopen、fread讀取整個檔案了,因為,你沒那麼多記憶體去讀,這時候就要對檔案進行分片讀取,比如,每10M讀一次,如此迴圈將其遍歷一次。

但考慮到吞吐率的問題,這裡引用的是FileMapping檔案對映的方式,將檔案直接對映到記憶體中進行操作。當檔案比較小的時候,我們可以直接全檔案對映起來,但通常不太建議這麼做。

我採用的也是上面說的迴圈讀取的思路,對大的磁碟檔案迴圈對映起來,程式碼如下:

#!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;
    //得到系統分配粒度
    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)                                      //記憶體映象小於64M時,一次性掛載
    {
        Maping_file(big_file, 0, ll_file_size);
    }else{
        for (i = 0; i < (ll_file_size / MAPPING_SIZE) ; i++)                 //否則以64M為一個映象對映單位,迴圈掛載,直到全部對映完成
        { 
            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,需要根據實際大小來對映
            Maping_file(big_file, i * MAPPING_SIZE - ((i * MAPPING_SIZE) % dwAll), ll_file_size - i * MAPPING_SIZE);
        }
    }
    return 0;
}

ToMapping完成的是將檔案分片交給Maping_file函式處理。

#!cpp
int Maping_file(char* big_file, LCN lOffset, long lSize)
{
    char* pPtr_File;                                                  //存放指向記憶體對映檔案的首地址
        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 high
        lOffset & 0xFFFFFFFF,                                // Offset low
        lSize);                                             // bytes to map
    if (pbFile == INVALID_HANDLE_VALUE){
        ErrorOut("MapViewOfFile() Error!");
    }
    /////////////////////////////////////////////
    pPtr_File = (char*)pbFile;
    ToGetDBR(pPtr_File, lSize, lOffset);
        //////////////////////////////////////////////
    UnmapViewOfFile(pbFile);
    CloseHandle(hFileMapping);
    CloseHandle(hFile);
    return 0;
}

這樣,就能透過CreateFileMapping將大的映象檔案分塊為64M一小塊對映起來,之後呼叫ToGetDBR,來對磁碟中殘留的DBR資訊進行搜尋。

0x07 搜尋DBR


這裡的思路是,遍歷整個磁碟,而後檢測每個扇區,看是否滿足NTFS或FAT32型分割槽格式。若滿足,則鍵入到連結串列中。

#!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);
}

檢測是否為NTFS與FAT32形式的磁碟可根據NTFS及FAT32結構體特徵來判斷,這裡判斷程式碼如下:

#!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;
}

而所用到的DBR連結串列結構體如下:

#!cpp
typedef struct dbr_list_t
{
    char* dbr;    // DBR content
    int n_type;   // DBR type   ntfs = 1    fat32 = 2
    int 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 dbr

    dbr_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;

在搜尋過中,對dbrn_typell_offsetp_next四個鍵值進行賦值,得到整個磁碟中可能存在的分割槽資訊。並將這些搜尋到的DBR存放在連結串列中,方便進一步處理。   

0x08 判斷DBR


在上一小節,我們完成了對整個磁碟中可能存在的分割槽資訊的搜尋。那,搜尋到的結果肯定不會全部正確或者可用,為了使重建的分割槽表更加可靠,需要對搜尋到的分割槽資訊進行篩選,以及連結串列資訊的填充。

在0x05中談到了NTFS和FAT32型別DBR的一些特性,這裡就需要用到這些特性對去進行判斷。

對於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;   // 設定dbr可用標誌位
            p_dbr_temp->ll_total_sector = (LCN)p_ntfs_temp->sectors_nbr;   // 設定分割槽總大小
            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");
    }
}
  1. DBR中sectors_nbr不能比整個檔案大小還大
  2. MFT是否正確

若兩項都滿足,則判定其為正確的NTFS型別DBR,即可能為正確的分割槽資訊。那該如何判斷MFT是否正確呢?在0x05中也提到過,定位到MFT的方法,定位過去之後,讀取檔案看是否為滿足主檔案記錄的格式。需要注意的是,對於NTFS搜尋到的DBR有可能是分割槽起始扇區的,也有可能是分割槽結束扇區的,兩者都需要考慮,並且,在判斷MFT的同時,需要把dbr_list中的其他資訊填充完整。

#!cpp
//
//file_path: 映象檔案路徑
//p_dbr:dbr_list結構體
//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;
}

對於FAT32型,需要考慮的似乎只有是否滿足能正確找到FAT表,如0x05中所說,其大小是需要靠後一個分割槽的起始扇區或檔案總大小來獲取的。  

#!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++;
        }
    }

在對於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;
}

完成這一步後,我們得到了篩選出了整個磁碟中所有可用的DBR資訊,並且獲取到了重建分割槽表所需要的分割槽型別、起始扇區、分割槽大小三個資訊。

可以將其輸出由使用者選擇需要恢復的分割槽:

#!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;   // 清空標誌位置
            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();
                }else
                    getchar();
            }
        }
        p_dbr_temp = p_dbr_temp->p_next;
    }
    return 0;
}

0x09 重構DPT


重構DPT需要考慮的問題有以下幾個:

  1. 總分割槽數是否大於4個

    a. 若不是,則可全寫入0扇區MBR中 b. 若是,則需要新建擴充套件分割槽完成擴充套件

  2. 獲取到的DBR是否為分割槽起始扇區DBR

    a. 若是,則無需更改 b. 若不是,則需要將作為backup的DBR複製到分割槽起始扇區

  3. 分割槽表是線性一次擴充套件下去的,不存在分割槽交叉的情況,即各分割槽大小之和為檔案總大小,各分割槽無公共部分。(這一步在0x08中的showdpt中做了處理)

  4. 建立擴充套件分割槽時,主分割槽DPT最後一條記錄是指向擴充套件DPT,擴充套件DPT的最後一條記錄繼續指向下一個擴充套件DPT。

  5. MBR中boot_code部分資訊可以不用考慮

考慮完了這些問題,就可以來編碼實現了。我的做法是,新建一個用於重構的連結串列,將所有要更改的內容、要更改的內容的大小、以及要更改的位置寫入到連結串列中,方便後面寫檔案以及恢復檔案。

#!cpp
void ReBuildDPT(char* sz_file_path)
{
    rebuild_content_t* rebuild_list = CreateReBuildHead();
    if (g_n_part <= 4)    // 小於四個分割槽時,只需要建立主分割槽表
    {
        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; // byPartType
                memcpy(sz_tmp + k * 16 + 8, (char *)&(p_dbr_temp->ll_start_sector), sizeof(__int64));  // dwStartSector
                memcpy(sz_tmp + k * 16 + 12, (char *)&(p_dbr_temp->ll_total_sector), sizeof(__int64)); // dwTotalSector
                k++;

                if (p_dbr_temp->n_is_org)  // 是否起始扇區
                {
                    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   // 否則考慮擴充套件分割槽的情況
    {
        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)  // 主分割槽只能有三個,最後一個為擴充套件分割槽
                {
                    if (k != 2)
                    {
                        *(sz_tmp + k * 16 + 4) = (p_dbr_temp->n_type == 1) ? 0x07 : 0x0B; // byPartType
                        memcpy(sz_tmp + k * 16 + 8, (char *)&(p_dbr_temp->ll_start_sector), sizeof(__int64));  // dwStartSector
                        tmp = p_dbr_temp->ll_total_sector + 1;
                        memcpy(sz_tmp + k * 16 + 12, (char *)&tmp, sizeof(__int64)); // dwTotalSector
                        k++;
                    }
                    else
                    {
                        *(sz_tmp + k * 16 + 4) = (p_dbr_temp->n_type == 1) ? 0x07 : 0x0B; // byPartType
                        memcpy(sz_tmp + k * 16 + 8, (char *)&(p_dbr_temp->ll_start_sector), sizeof(__int64));  // dwStartSector
                        tmp = p_dbr_temp->ll_total_sector + 1;
                        memcpy(sz_tmp + k * 16 + 12, (char *)&tmp, sizeof(__int64)); // dwTotalSector
                        k++;

                        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; // byPartType
                                tmp = p_dbr_temp_tmp->ll_start_sector - 1;
                                memcpy(sz_tmp + k * 16 + 8, (char *)&tmp, sizeof(__int64));  // dwStartSector
                                tmp = (g_ll_file_size/SECTOR_SIZE) - p_dbr_temp_tmp->ll_start_sector + 1;
                                memcpy(sz_tmp + k * 16 + 12, (char *)&tmp, sizeof(__int64)); // dwTotalSector
                                k++;
                                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; // byPartType

                    tmp = 1;   // 擴充套件分割槽偏移地址從當前地址算起(相對地址)
                    memcpy(sz_tmp + 8, (char *)&tmp, sizeof(__int64));  // dwStartSector
                    memcpy(sz_tmp + 12, (char *)&(p_dbr_temp->ll_total_sector), sizeof(__int64)); // dwTotalSector

                    if (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; // byPartType
                                tmp = p_dbr_temp_tmp->ll_start_sector - p_dbr_temp->ll_start_sector;
                                //tmp = 1;
                                memcpy(sz_tmp + 16 + 8, (char *)&tmp, sizeof(__int64));  // dwStartSector
                                tmp = (g_ll_file_size/SECTOR_SIZE) - p_dbr_temp_tmp->ll_start_sector;
                                memcpy(sz_tmp + 16 + 12, (char *)&tmp, sizeof(__int64)); // dwTotalSector
                                break;
                            }
                            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)  // 是否起始扇區
                {
                    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);
}

0x0A 檔案處理


透過前面的操作,可以得到處理好了的rebuild_list。接下來要做的就是用它來完成重建分割槽表,恢復DBR的工作。

首先,我們的目的是,重建後的分割槽表後檔案能作為VHD直接被win7以上系統載入。其次,希望能夠在我解除安裝VHD檔案後,仍然恢復到原有狀態。意味著需要對更改的資訊做一個備份,這沒問題,因為我們替換先前rebuild_list中的content就可以完成了。

之後,需要了解VHD檔案格式。找了找資料。發現,VHD檔案僅僅在檔案尾部新增了一個扇區的內容,其結構如下:

#!cpp
/*vhd尾部資訊結構*/
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; 

這張表中,重要的就是orig_sizecurr_sizechecksum,通常情況下orig_sizecurr_size相同,checksum是最後一個扇區所有位元組相加後取反的值。我建了個模板來實現VHD標誌位的新增。

所以,最終的檔案處理模組如下:

#!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 VHD
    hd_ftr* vhd;
    vhd = (hd_ftr*)data;
    LARGE_INTEGER offset = {0};
    DWORD readsize = 0;

    /*Set hd_ftr struct*/
    vhd->orig_size = 0;   // clear
    vhd->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 SECTOR
        p_rebuild_tmp = p_rebuild_tmp->p_next;
    }

///////////////////////////////////////////////// BackUp VHD
    ReadFileOffset(file_path, -SECTOR_SIZE, SECTOR_SIZE, sz_vhd_buf, FILE_END);

/////////////////////////////////////////////*  */// Write VHD
    WriteFileOffset(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 SECTOR

    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("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 SECTOR
        p_rebuild_tmp = p_rebuild_tmp->p_next;
    }

///////////////////////// Restore VHD
    WriteFileOffset(file_path, -SECTOR_SIZE, SECTOR_SIZE, sz_vhd_buf, FILE_END);

    printf("Restore File Success!\n");
}

這樣,重建分割槽表的工作就完成了。

0x0B 程式效果


還是用前面例子中用到的VHD檔案來做演示,該VHD現在的情況是:先前新建過兩個NTFS的分割槽,之後刪除了一個NTFS,並重新格式化成FAT32,再然後,我將其分割槽全部刪除,並新建了3個NTFS與兩個FAT32,最終效果如圖6所示。現在我們用剛寫好的工具來對它進行搜尋分析。

圖17: Alt text

可以看到它總共搜尋到了8個可用的DBR資訊,但顯然其中有一些是前幾次分割槽留下的內容。選擇保留1.2.3.5.7五個分割槽(也就是圖6所示的五個分割槽),起始你選擇保留第三個的時候,就不有機會讓你選擇第四個了,因為他們是衝突的。結果如圖18:

Alt text

提示寫檔案成功,並已經可以直接當做VHD檔案掛載了,我們用winhex開啟處理過的檔案,得到圖19:

Alt text

可以看到,重構後的分割槽表幾乎與原來的分割槽表一致。

接著,我們試著來恢復前幾次分割槽留下的資訊,看是否能夠成功。圖20:

Alt text

這裡我選擇恢復了一個NTFS和一個FAT32,再進winhex中檢視,得到圖21:

Alt text

可以看到第一個NTFS分割槽,是並不存在與圖6的五個分割槽中的。

再次,我們用圖1的那個TrueCrypt解密後的打不開的磁碟作為例子,看看這次能否恢復出正確的分割槽表,並且,實現載入。找到可用的DBR資訊如圖22:

Alt text

按照邏輯,選擇了第一個後,就只能選擇1和2。已提示寫檔案成功,圖23:

Alt text

Winhex解析如圖24:

Alt text

正確完整恢復,所有檔案也能正確解析,圖25:

Alt text

接著,在做只留下尾部備份分割槽測試程式需找情況的時候,發現起始我們程式中並沒有必要將備份分割槽複製到起始扇區,因為,也能正常解析。

於是,完工!(不過,還有個小細節需要注意,如果起始位置是個DBR頭部的話,系統將不會當做MBR處理,而是當做DBR,所以,清除頭部資訊也很重要!)

0x0C Summarize


加上寫程式碼的時間和些這文章的時間,大概花費了近十天吧。各種找資料、調程式、做樣本,也算是把恢復分割槽表的原理弄清楚了。同時,也把忘得差不多的磁碟格式、檔案系統什麼的再撿起來看了看。當然文章其實並不詳細,因為關注點不一樣,就沒講太多關於檔案系統的內容了。再往下,可以詳細到檔案的恢復等等,當然,起始也沒必要去做,畢竟現有的工具一大堆,我只是突然感興趣就拿來實現了一把。

再者,本程式暫時只支援NTFS和FAT32兩種型別,若有其他型別的原理應該也是一致,大家仔細琢磨。

寫文章也比較倉促,也沒校稿習慣,若有勘誤,還請諒解並提醒更正。感謝!

原始碼下載地址:https://github.com/purpleroc/hand_disk
另推薦學習原始碼:testdisk、ReadPartTable
——Tracy_梓朋
2016年1月6日21:31:52

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章