Handle_Disk

Author: purpleroc@0xFA-Team
Email: admin@0xfa.club

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了。于是,他的结构体大体如下:

1.typedef struct MBR_T
2.{
3. UCHAR boot_code[446];
4. PartTableRecord partition[4];
5. UCHAR sign[2];
6.}MBR;

  对照着结构体,我们看看winhex中的截图:
  图4:
  Alt text

  黄色区域就是boot_code所占用的446个字节,红色部分就是DPT,蓝色就是标志位了。
  

0x04 磁盘分区表DPT

  既然找到了DPT,那肯定是要分析清楚,它是干嘛用的,里面都有些什么信息呢?
  直接用winhex的模板看看先:
  图5:
  Alt text
  桌面太小,截图不完全,但也大体知道了里面会有些什么信息,顺便翻出结构体如下:

1.typedef struct PartTableRecord_t
2.{
3.
4. BYTE byIsBoot; //引导分区 1B 1B 80(引导分区),00(非引导分区)
5. BYTE byStartHead; //起始磁头 1B 2B
6. BYTE byStartSector; //起始扇区 1B 3B
7. BYTE byStartCylinder; //起始柱面 1B 4B
8. BYTE byPartType; //分区类型 1B 5B 07(NTFS),05(扩展分区),0B(FAT32)
9. BYTE byEndHead; //结束磁头 1B 6B
10. BYTE byEndSector; //结束扇区 1B 7B
11. BYTE byEndCylinder; //结束柱面 1B 8B
12. DWORD dwStartSector; //开始扇区 4B 12B
13. DWORD dwTotalSector; //分区扇区数 4B 16B 最大2T Byte
14.} 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部分,并对照结构体来看,它告诉了我们这些信息:

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,分区类型,去找了找资料,这里的取值非常多,常见类型大致如下:

1.00H DOS或WINDOWS不允许使用,视为非法  
2.01H FAT12
3.04H FAT16小于32MB
4.05H Extended
5.06H FAT16大于32MB
6.07H HPFS/NTFS
7.OBH WINDOWS95 FAT32
8.OCH WINDOWS95 FAT32
9.0EH WINDOWS FAT16
10.0FH WINDOWS95 Extended(大于8G)
11.82H Linux swap
12.83H Linux
13.85H Linux extended
14.86H NTFS volume set
15.87H NTFS volume set

  在结合上一节的分区表,这里主要关注05、07、0B,即扩展分区、NTFS、FAT32三种。而05的在上一节介绍过了,那么,我们将目光投向NTFS与FAT32两种类型。

0x5a NTFS (New Technology File System)

  首先,从MBR中看到分区信息能知道,分区1是NTFS分区,在winhex中跟随上一节中分区1的起始位置,可以看到如下信息,图12:
  Alt text
  已将每个数据对应的结构和数据都着色了,然后也是时候拿出NTFS文件系统中DBR的数据结构了:

1.typedef struct ntfs_boot_sector_t {
2. BYTE ignored[3]; /* 0x00 Boot strap short or near jump */
3. char system_id[8]; /* 0x03 Name : NTFS */
4. BYTE sector_size[2]; /* 0x0B bytes per logical sector */
5. BYTE sectors_per_cluster; /* 0x0D sectors/cluster */
6. WORD reserved; /* 0x0E reserved sectors = 0 */
7. BYTE fats; /* 0x10 number of FATs = 0 */
8. BYTE dir_entries[2]; /* 0x11 root directory entries = 0 */
9. BYTE sectors[2]; /* 0x13 number of sectors = 0 */
10. BYTE media; /* 0x15 media code (unused) */
11. WORD fat_length; /* 0x16 sectors/FAT = 0 */
12. WORD secs_track; /* 0x18 sectors per track */
13. WORD heads; /* 0x1A number of heads */
14. DWORD hidden; /* 0x1C hidden sectors (unused) */
15. DWORD total_sect; /* 0x20 number of sectors = 0 */
16. BYTE physical_drive; /* 0x24 physical drive number */
17. BYTE unused; /* 0x25 */
18. WORD reserved2; /* 0x26 usually 0x80 */
19. LCN sectors_nbr; /* 0x28 total sectors nbr */
20. QWORD mft_lcn; /* 0x30 Cluster location of mft data.*/
21. QWORD mftmirr_lcn; /* 0x38 Cluster location of copy of mft.*/
22. char clusters_per_mft_record; /* 0x40 */
23. BYTE reserved0[3]; /* zero */
24. char clusters_per_index_record; /* 0x44 clusters per index block */
25. BYTE reserved1[3]; /* zero */
26. LCN volume_serial_number; /* 0x48 Irrelevant (serial number). */
27. DWORD checksum; /* 0x50 Boot sector checksum. */
28. BYTE bootstrap[426]; /* 0x54 Irrelevant (boot up code). */
29. WORD marker; /* 0x1FE */
30.}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来恢复出分区表。
  

0x5b FAT32(32位文件分配表)

  上一小节中,我们知道了怎么重建NTFS型分区,接着,来看看FAT32。回到图6中,看看第三个分区。winhex跟过去,得到截图如下,这次就不上色了,太累~~~
  图14:
  Alt text
  找了找资料,翻出FAT32的结构体表示:

1.  typedef struct fat_boot_sector_t {
2. BYTE ignored[3]; /* 0x00 Boot strap short or near jump */
3. char system_id[8]; /* 0x03 Name - can be used to special case
4. partition manager volumes */
5. BYTE sector_size[2]; /* 0x0B bytes per logical sector */
6. BYTE sectors_per_cluster; /* 0x0D sectors/cluster */
7. WORD reserved; /* 0x0E reserved sectors */
8. BYTE fats; /* 0x10 number of FATs */
9. BYTE dir_entries[2]; /* 0x11 root directory entries */
10. BYTE sectors[2]; /* 0x13 number of sectors */
11. BYTE media; /* 0x15 media code (unused) */
12. WORD fat_length; /* 0x16 sectors/FAT */
13. WORD secs_track; /* 0x18 sectors per track */
14. WORD heads; /* 0x1A number of heads */
15. DWORD hidden; /* 0x1C hidden sectors (unused) */
16. DWORD total_sect; /* 0x20 number of sectors (if sectors == 0) */
17.
18. /* The following fields are only used by FAT32 */
19. DWORD fat32_length; /* 0x24=36 sectors/FAT */
20. WORD flags; /* 0x28 bit 8: fat mirroring, low 4: active fat */
21. BYTE version[2]; /* 0x2A major, minor filesystem version */
22. DWORD root_cluster; /* 0x2C first cluster in root directory */
23. WORD info_sector; /* 0x30 filesystem info sector */
24. WORD backup_boot; /* 0x32 backup boot sector */
25. BYTE BPB_Reserved[12]; /* 0x34 Unused */
26. BYTE BS_DrvNum; /* 0x40 */
27. BYTE BS_Reserved1; /* 0x41 */
28. BYTE BS_BootSig; /* 0x42 */
29. BYTE BS_VolID[4]; /* 0x43 */
30. BYTE BS_VolLab[11]; /* 0x47 */
31. BYTE BS_FilSysType[8]; /* 0x52=82*/
32.
33. /* */
34. BYTE nothing[420]; /* 0x5A */
35. WORD marker;
36.}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: