本来的需求是XEN下的镜像取证,但这篇仅包括他支持的一种格式,就是VHD,此项目从头开始大概用了两周时间,中间遇到了很多让人头大的问题,光是思考的笔记就写了十几页纸,不过实际上并没有那么难,主要是很久没编码了,还有很多概念没搞清楚。好吧,搬家过来的第一个博客就从这个项目开始吧。

要求:

1、解析vhd格式文件,判断合法性

2、该vhd装的文件系统是NTFS格式

3、拿到该格式下的目录结构,即包含哪些文件和目录。

4、跨平台

思路:

一、vhd格式解析

解析首先要弄懂数据结构,网上关于他的官方格式说明是找不到的,但是XEN竟然能支持vhd格式肯定能有相应的数据结构,所以去搜索,xen的源码,果然里面就含有一个vhd.h,格式旁边还有注释,非常完美。此外,网上还找到了一篇《storage_layout系列之VHD结构详解》,是北亚数据的创始人写的,也很详细。

这个格式主要就是一个位于文件尾部512字节的数据结构:

// VHD Footer structure
typedef struct {
u_char cookie[MT_CKS]; // Cookie
u_int32_t features; // Features
u_int32_t ffversion; // File format version
u_int64_t dataoffset; // Data offset
u_int32_t timestamp; // Timestamp
u_int32_t creatorapp; // Creator application
u_int32_t creatorver; // Creator version
u_int32_t creatorhos; // Creator host OS
u_int64_t origsize; // Original size
u_int64_t currsize; // Current size
u_int32_t diskgeom; // Disk geometry
u_int32_t disktype; // Disk type
u_int32_t checksum; // Checksum
u_char uniqueid[]; // Unique ID
u_char savedst; // Saved state
u_char reserved[]; // Reserved
}__attribute__((__packed__)) vhd_footer_t;

【lseek可能遇到的问题 】

由于vhd若装的是windows 7那么很可能就是5G以上,我测试的是一个11G的,但是lseek参数是ULONG只能支持到32位即4GB空间,超过4GB会报错,所以定位取尾部一个扇区的数据结构时,要用SEEK_END。当然之后还会出现类似的指针地址太大问题,解决方法有俩:

 1、posix标准下,可以用在Makefile里面写上
CFLAGS +=  -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES
CXXFLAGS +=  -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES

别的不用动就能支持了。

2、一个比较生硬的转换法:

 

int  count= 要lseek的数字/(*);
for( int i = ;i<count;i++ )
lseek( * );
lseek( 要lseek的数字%(*) )

未测试成功性。

通过disktype能判断是静态vhd磁盘还是动态vhd磁盘,这里只考虑动态vhd磁盘,通过dataoffset就能跳到动态磁盘头部,动态vhd磁盘数据结构如下:

// VHD Dynamic Disk Header structure
typedef struct {
u_char cookie[MT_CKS]; // Cookie
u_int64_t dataoffset; // Data offset
u_int64_t tableoffset; // Table offset
u_int32_t headerversion; // Header version
u_int32_t maxtabentries; // Max table entries
u_int32_t blocksize; // Block size
u_int32_t checksum; // Checksum
u_char parentuuid[]; // Parent Unique ID
u_int32_t parentts; // Parent Timestamp
u_char reserved1[]; // Reserved
u_char parentname[];// Parent Unicode Name
u_char parentloc1[]; // Parent Locator Entry 1
u_char parentloc2[]; // Parent Locator Entry 2
u_char parentloc3[]; // Parent Locator Entry 3
u_char parentloc4[]; // Parent Locator Entry 4
u_char parentloc5[]; // Parent Locator Entry 5
u_char parentloc6[]; // Parent Locator Entry 6
u_char parentloc7[]; // Parent Locator Entry 7
u_char parentloc8[]; // Parent Locator Entry 8
u_char reserved2[]; // Reserved
}__attribute__((__packed__)) vhd_ddhdr_t;

动态vhd格式的整体布局如下:

刚才拿到了尾部扇区,头部的0扇区是对他的备份,接着利用头部扇区再定位到BAT。BAT就是块地址表,他的数组中的每个值对应一个块地址:

定位方法就是batmap[i]*扇区大小

取数据我们只需要关注块数据的位置即可,上图的BAT的y0 y1……代表的就是块的首地址,跳到y0首地址,这里是一个512B+2MB的结构,前512字节(4096位)说明后面4096个扇区(2MB)的数据的分配情况,我们不需要管分配情况,只需要定位即可,所以我们每次定位要跳过512字节。

定位思路就是先算出块的位置,再算出块内扇区位置,非常简单,最终得出的地址转换函数如下:

/*
* 功能:将磁盘地址转到vhd地址位置获取到对应扇区信息
* offset偏移扇区数,size读取长度
*/
u_char * Address2VHD(u_int64_t offset,int size)
{
int blk_index = offset/(blk_size / MT_SECS);
int sc_index = offset % (blk_size / MT_SECS);
u_char sc[MT_SECS*size];
int lk = lseek(vhdfd,SwapInt32(batmap[blk_index])*(u_int32_t)MT_SECS + + sc_index*(u_int32_t)MT_SECS,SEEK_SET);
if(lk < )
{
perror("lseek");
}
else
{
read(vhdfd,sc,MT_SECS*size);
return sc;
}
}

【块设备的问题】

I/O设备分为块设备与字符设备,磁盘是块设备,他与内存交互的单位就是块,块都有自己地址,一个块一般含1到64个扇区,但是虚拟磁盘的块有2MB这么大,也就是4096个扇区,而且每块前面都有一个位图,形式不太一样,不能当一般磁盘设备来看待,毕竟是虚拟的磁盘,不必大惊小怪。

此外是文件系统存储的最小单位,再高级格式化时能设置,与块没什么直接关系,不要搞乱了。

二、找到第一个扇区MBR并解析

有了地址转换函数再找MBR主引导扇区就容易多了,但首先要做的是定义MBR的格式。MBR的数据结构网上非常容易找,我这里主要参考的《数据重现 文件系统原理精解与数据恢复最佳实践》这本书,讲解得非常详细。

MBR的数据结构如下:

#pragma pack(1)

/*一个分区表的表头信息*/
typedef struct DPT_Header
{
u_char State; //分区状态, 0 = 未激活,0x80 = 激活
u_char StartHead; //分区起始磁头号
u_int16_t StartSC; //分区起始扇区和柱面号,低字节(低6位为扇区号,高2位为柱面号的第9,10位),高字节(柱面号的低8位),考虑到字节序问题可能不一样
u_char Type; //分区类型,00 表示此项未用,其他标号代表各种系统
u_char EndHead; //分区结束磁头号
u_int16_t EndSC; //分区结束扇区和柱面号,定义同前
u_int32_t Relative; //在线性寻址方式下的分区相对扇区地址(对于基本分区即为绝对地址),从0开始的扇区数
u_int32_t Sectors; //分区大小 (总扇区数)
} DPT_Header; //引导区512u_char结构
typedef struct _MBR_SECTOR
{
u_char BootCode[];//启动记录440 u_char
u_int32_t DiskSignature;//磁盘签名 4字节
//u_int16_t blank1;
u_int16_t NoneDisk;//二个字节
DPT_Header Partition[];//分区表结构64 u_char
u_int16_t Signature;//结束标志2 u_char 55 AA
//u_int16_t blank2;
} MBR_SECTOR, *PMBR_SECTOR;
#pragma pack()


分区中的Type类型可以定义各种操作系统,对应编码表:

【字节对齐问题】

由于读取的时候我们会是一个个字节读进来,结构体默认的字节对齐(一般是4字节对齐,也就每个类型总在4字节内,或者恰好是4的整数倍,否则就填充)并且sizeof()函数会按字节对齐后所消耗的真正空间。这样在后期运算会带来很大麻烦,所以这里加上#pragma pack(1) #pragma pack()规定就使用1字节对齐,就不会出现问题了。

取到了MBR,其中有4个分区表结构DPT_Header(因为NTFS最多支持4个主分区,但是动态磁盘支持更多,这里暂时不考虑动态磁盘),DPT_Header中有个起始扇区号,这就是每个扇区的起始扇区,那么就一个一个跳过去查看。

【显示空间大小问题】

由于空间时不时是上百G,那么在将扇区数转成GB或MB显示的时候要十分小心,例如一般是939393*每扇区字节数/1024/1024,若是int,那么第一个乘法就溢出了,所以应该把乘法放后面。

【malloc及传参问题】

这是基础内容了,传参的时候func(uchar**)必须是双重指针,否则在内部malloc后只是值改变,而指针本身没有变化。

 

三、找到MFT表的具体位置

在ntfs中,MFT表记录了整个文件系统的布局,每个mft都会记录一个文件的详细信息,包括创建时间、修改时间、作者、文件名、目录名等等。整个表一般占全系统的12.5%,当然他还可以往上配置得更大一点,一个文件被删除,也只是更改里面的属性而已,若不被重新分配,通过mft表就能很快恢复过来,他一般集中在文件系统的某个特定位置,现在我们的目标就是找到他。

《数据重现 文件系统原理精解与数据恢复最佳实践》这本书详细介绍了mft表的内容这里不再赘述。

这里开始就要引入ntfs.h了,里头有一个PACKED_BOOT_SECTOR结构,在分区表的其实扇区偏移地址就是指向这里,所以第一步就是跳到这里。

typedef struct _PACKED_BOOT_SECTOR {

    UCHAR Jump[];                                                  //  offset = 0x000
UCHAR Oem[]; // offset = 0x003
BIOS_PARAMETER_BLOCK PackedBpb; // offset = 0x00B
UCHAR Unused[]; // offset = 0x024
LONGLONG NumberSectors; // offset = 0x028
LCN MftStartLcn; // offset = 0x030
LCN Mft2StartLcn; // offset = 0x038
CHAR ClustersPerFileRecordSegment; // offset = 0x040
UCHAR Reserved0[];
CHAR DefaultClustersPerIndexAllocationBuffer; // offset = 0x044
UCHAR Reserved1[];
LONGLONG SerialNumber; // offset = 0x048
ULONG Checksum; // offset = 0x050
UCHAR BootStrap[0x200-0x044]; // offset = 0x054 } PACKED_BOOT_SECTOR;

拿到这个数据结构,里面又有很多的内容,其中有一个是MftStartLcn,这就是Mft表的起始簇。一个簇有多大呢?这里有个数据结构BIOS_PARAMETER_BLOCK,就是上面的0x00B,他的定义如下:

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;

显然这个结构里包含了,每个簇的扇区数,每个扇区的字节数等,通过这些指标我们就能定位mft里了。

实际上,网上能搜到一个toysntfs的程序,他采用符号链接和MBR的两种方法对MFT的表解析并读取根目录内容(只针对本地磁盘而不是针对虚拟机的),但是下下来后竟然发现后一种方法是不成功的,虽然传入的参数完全一样,但是最终read得到的结果却不同,再苦闷中分析了两天,才发现,原来是对WINAPI的操作理解不太到位。这个起始簇是相对于分区的,也就是说这个分区的第一个簇是0,下一个分区的第一个簇也是0,所以绝对的地址必须在前面加上一个起始扇区的偏移量,否则定位错误,经修改,运行正常。

定位到MFT就用下面的公式:

NtfsData.MftStartLcn.QuadPart * NtfsData.SectorsPerCluster

四、MFT的解析及遍历

MFT(主文件表也可以称为文件记录块)本身也是一个文件,他占2个扇区,文件系统的前16个MFT是元文件,包括用于文件定位和恢复的数据结构、引导程序数据以及整个卷的分配位图等信息,用户是不能访问的,这里列出前5个:

0    $MFT    //mft本身
  1  $MftMirr    //mft 元数据文件的镜像,用于备份恢复
  2  $LogFile    //文件操作历史记录文件
  3  $Volume    //文件卷信息文件
  4  $AttrDef    //属性定义文件
  5  $Root(\)    //根目录文件

取根目录的扇区位置即:

ulStartSector.QuadPart = NtfsData.MftStartLcn.QuadPart * NtfsData.SectorsPerCluster + ulStartMft.QuadPart*2;

获取目录结构的方法有两个:

1、从$root开始递归遍历,首先查看0x16~17偏移看是否是目录,是的话通过文件记录号跳转递归查询:

mft记录头的结构如下:

后面紧跟着各种属性:

属性分析的框架:

ReadSector( MFT索引号, 长度)
{
  解析MFT记录头;(大小0x38字节)
  解析属性部分
  {
    Switch(属性类型)
    {
      Case: 对不同的属性做处处理,读取或修改,break;
      Default:
    }
  }

我们要的主要是文件名

2、遍历所有的MFT,一般来说MFT是连续的,但也有不连续的可能,主要是$data属性超出的情况,但是不是做文件恢复这里影响不大,只要读取filename即可,另一种说法是遍历MFT在磁盘上分布的区域大小,计算出总的MFT记录数量,然后发送控制码FSCTL_GET_NTFS_FILE_RECORD来获取文件引用号。这个方法不知道对虚拟机镜像可不可行,未测试。

下面这篇文章对解析有较多内容的阐述:

http://www.cnblogs.com/guanlaiy/archive/2013/02/24/2924089.html

主要参考:

《storage_layout系列之VHD结构详解》

《XEN虚拟机分析》 薛海峰,卿斯汉,张焕国

《数据重现 文件系统原理精解与数据恢复最佳实践》

《数据恢复技术》

http://www.cnblogs.com/guanlaiy/archive/2013/02/24/2924132.html

http://www.cnblogs.com/guanlaiy/archive/2013/02/24/2924089.html

虚拟机VHD格式解析到NTFS文件系统解析的更多相关文章

  1. NTFS 文件系统解析

    1. windows 下磁盘文件读写 下面是读取D:\磁盘上的第0扇区 512 Bytes CreateFile()打开磁盘,获取文件句柄: SetFilePointer()设置读写的位置: Read ...

  2. 深入理解JVM虚拟机4:Java class介绍与解析实践

      用java解析class文件 转自https://juejin.im/post/589834a20ce4630056097a56 前言 身为一个java程序员,怎么能不了解JVM呢,倘若想学习JV ...

  3. iOS开发之JSON格式数据的生成与解析

    本文将从四个方面对IOS开发中JSON格式数据的生成与解析进行讲解: 一.JSON是什么? 二.我们为什么要用JSON格式的数据? 三.如何生成JSON格式的数据? 四.如何解析JSON格式的数据? ...

  4. PE格式第九讲,资源表解析

    PE格式第九讲,资源表解析 一丶熟悉Windows管理文件的方法 首先,为什么标题是这个,主要是为了下边讲解资源方便,因为资源结构体很乱.如果直接拿出来讲解,那么就会很晕. 1.windows管理文件 ...

  5. [转帖]overlay文件系统解析

    overlay文件系统解析 来源:http://dockone.io/article/1511 原作者: 陈爱珍 布道师@七牛云 一个 overlay 文件系统包含两个文件系统,一个 upper 文件 ...

  6. 转载 -- iOS开发之JSON格式数据的生成与解析

    本文将从四个方面对IOS开发中JSON格式数据的生成与解析进行讲解: 一.JSON是什么? 二.我们为什么要用JSON格式的数据? 三.如何生成JSON格式的数据? 四.如何解析JSON格式的数据? ...

  7. Python time strptime() 函数根据指定的格式把一个时间字符串解析为时间元组

    Python time strptime() 函数根据指定的格式把一个时间字符串解析为时间元组 import time dt=time.strptime('2019-08-08 11:32:23', ...

  8. 轻便的gb28181协议中的rtp+ps格式视频流的封装和解析

    streams 轻便的gb28181协议中的rtp+ps格式视频流的封装和解析 packet packet实现ps的相关封装和解析, example/enc 通过joy4来读本地视频文件,然后调用Rt ...

  9. 详解NTFS文件系统

    一.分析NTFS文件系统的结构 当用户将硬盘的一个分区格式化为NTFS分区时,就建立了一个NTFS文件系统.NTFS文件系统同FAT32文件系统一样,也是用“簇”为存储单位,一个文件总是占用一个或多个 ...

随机推荐

  1. JQuery中如何click中传递参数

    代码如下: click(data,fn)中的data其实是json对象,取的时候,只能通过当前的事件源来取,data是默认放在event中的,所以这里的data是eventdata,引用的时候也使用e ...

  2. Unity3d shader之次表面散射(Subsurface Scattering)

    次表面散射是一种非常常用的效果,可以用在很多材质上如皮肤,牛奶,奶油奶酪,番茄酱,土豆等等  初衷是想做一个牛奶shader的,但后来就干脆研究了sss这是在vray上的次表面散射效果 这是本文在un ...

  3. POJ 1775 (ZOJ 2358) Sum of Factorials

    Description John von Neumann, b. Dec. 28, 1903, d. Feb. 8, 1957, was a Hungarian-American mathematic ...

  4. HDOJ1518Square 深搜

    Square Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  5. UVa 3704 Cellular Automaton(矩乘)

    题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=15129 [思路] 矩阵乘法-循环矩阵 题目中的转移矩阵是一个循环矩 ...

  6. JavaScript 设计风格&模式 概览 20140418

    基本的概念 在JavaScript中,一旦定义好一个变量,该变量会自动成为内置对象的一个属性,(如果该变量是全局变量,那么会成为全局对象的一个属性). 定义的变量实际上也是一个伪类,拥有自身的属性,该 ...

  7. 《Numerical Methods》-chaper4-一元非线性方程的解

    在许多生产时间问题中,我们根据已知条件往往会列出一个一元非线性方程,一个最典型的例子就是银行存款的问题,由于其利息需要基于前一年的本息和,因此列出来的方程x的指数往往是高次的.还有物理问题当中一系列用 ...

  8. 计算1到n整数中,字符ch出现的次数

    个位ch个数 + 十位ch个数 * 10 + 百位ch个数 * 100:同时如果某一位刚好等于ch,还需要减去多算的一部分值. #include <stdio.h> //整数1到n,字符c ...

  9. Java GC 专家系列3:GC调优实践

    本篇是”GC专家系列“的第三篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.所以,你应该已经了解了JDK 7中的5种GC类型,以及每种G ...

  10. iPhone之Quartz 2D系列--编程指南(1)概览

    以下几遍关于Quartz 2D博文都是转载自:http://www.cocoachina.com/bbs/u.php?action=topic&uid=38018 iPhone之Quartz ...