http://blog.csdn.net/jha334201553/article/details/9089119
开始先说下DBR, DBR是继MBR 之后最先访问的地方,MBR利用int 13h 读取MBR并将之加载到物理地址0x7c00的地方. 然后将段地址:代码地址入栈后retf跳过去运行.
MBR利用BIOS中断int 13h读取数据加载到内存指定位置..传统的int 13h调用最多只能识别1024个磁头:
前面MBR讲解MBR的时候,有结构如下
/*+0x01*/ uchar StartHead; // 分区起始磁头号 (1磁头 = 63 扇区,取值 0~255 之间)
/*+0x02*/ uint16 StartSector:10; // 启始柱面号 10位 (1柱面 = 255 磁头,取值 0~1023 之间)
/*+0x02*/ uint16 StartCylinder:6; // 启始扇区号 6位 (取值 1 到 63 之间)
此结构可容纳最大值为FF FF FF (现在这个值基本都写成FE FF FF, 而废弃不用), 即最大能寻址的就是255柱面, 1023磁头, 63扇区,计算扇区个数为:
1023*255*63+255*63+63 = 16450623
再按每扇区 512 字节算, 那么它容量为 8 GB ≈ 512*16450623 B = 7.84 GB
传统的int 13h中断就受限于8G的限制, Microsoft等多家公司制定了int 13h扩展标准,让int 13h读写磁盘中断可以突破8G限制. 现在的计算机BIOS都是按扩展int 13h标准编写的代码.(具体详细内容可参考”扩展 int 13h规范”).
按MBR分区表里面的 SectionPrecedingPartition 逻辑扇区偏移(注意,这个逻辑扇区偏移是从0开始算的,读取出来值为63,而物理扇区是从1开始计算的,逻辑扇区转换物理扇区的时候必须+1才是正确的) 可以找到DBR的位置.可以看看winhex的显示
以下就偷懒不从MBR寻址分区的DBR了,而是直接打开盘符读取 (这样打开的第一个扇区就是DBR),这样做有个缺点,就是你用这个handle值将不能进行内存映射,只能一次多读取几个扇区来加快分析磁盘的速度(当前用的是一次读取20M数据然后分析)。
-
HANDLE
handle = CreateFile ( TEXT(
“\\\\.\\C:”
) ,
-
GENERIC_READ,
-
FILE_SHARE_READ|FILE_SHARE_WRITE,
-
NULL,
-
OPEN_EXISTING,
-
FILE_ATTRIBUTE_NORMAL,
-
NULL);
DBR结构定义为(对照winhex模板信息查看):
-
-
//
-
// NTFS 的DBR 数据结构
-
//
-
-
typedef
struct
_BIOS_PARAMETER_BLOCK {
-
-
/*+0x0B*/
uint16 BytesPerSector;
// 字节/扇区一般为0x0200 即512
-
/*+0x0D*/
uchar SectorsPerCluster;
// 扇区/簇
-
/*+0x0E*/
uint16 ReservedSectors;
// 保留扇区
-
/*+0x0F*/
uchar Fats;
//
-
/*+0x11*/
uint16 RootEntries;
//
-
/*+0x13*/
uint16 Sectors;
//
-
/*+0x15*/
uchar Media;
// 媒介描述
-
/*+0x16*/
uint16 SectorsPerFat;
//
-
/*+0x18*/
uint16 SectorsPerTrack;
// 扇区/磁轨
-
/*+0x1A*/
uint16 Heads;
// 头
-
/*+0x1C*/
uint32 HiddenSectors;
// 隐藏扇区
-
/*+0x20*/
uint32 LargeSectors;
// checked when volume is mounted
-
-
}BIOS_PARAMETER_BLOCK, *pBIOS_PARAMETER_BLOCK;
-
-
typedef
struct
_NTFS_Boot_Sector{
-
/*+0x00*/
uchar JmpCode[3];
// 跳转指令
-
/*+0x03*/
char
OemID[8];
// 文件系统ID
-
/*+0x0B*/
BIOS_PARAMETER_BLOCK PackedBpb;
// BPB
-
/*+0x24*/
uchar Unused[4];
// 未使用,总是为
-
/*+0x28*/
uint64 NumberSectors;
// 扇区总数
-
/*+0x30*/
lcn MftStartLcn;
// 开始C# $MFT (簇) 乘以 BIOS_PARAMETER_BLOCK.SectorsPerCluster 值得到扇区号
-
/*+0x38*/
lcn Mft2StartLcn;
// 开始C# $MFTMirr (簇)
-
/*+0x40*/
uchar ClustersPerFileRecordSegment;
// 文件记录大小指示器
-
/*+0x41*/
uchar Reserved0[3];
// 未使用
-
/*+0x44*/
uchar DefaultClustersPerIndexAllocationBuffer;
// 簇/索引块
-
/*+0x45*/
uchar Reserved1[3];
// 未使用
-
/*+0x48*/
uint64 SerialNumber;
// 64位序列号
-
/*+0x50*/
uint32 Checksum;
// 校验和
-
/*+0x54*/
uchar BootStrap[426];
// 启动代码
-
/*+0x1FE*/
uint16 RecordEndSign;
// 0xAA55 结束标记
-
}NTFS_Boot_Sector, *pNTFS_Boot_Sector;
在读取DBR的时候,一些数据以后经常会用到,那么需要根据DBR里面的信息保存以后会用到的信息,下面定义一个常用的保存信息结构:
-
// 保存 NTFS 的基本信息
-
typedef
struct
_NTFS_INFO
-
{
-
uint32 BytesPerSector;
// 每扇区的字节数
-
uint32 SectorsPerCluster;
// 每簇的扇区数
-
uint32 BytesPerCluster;
// 每簇的字节数
-
uint64 SectorCount;
// 扇区总数
-
uint64 MftStart;
// MFT表开始簇
-
uint64 MftMirrStart;
// MFT备份表开始簇
-
uint32 BytesPerFileRecord;
// 每个文件记录的字节数一般为512*2
-
-
uint16 VolumeLabelLength;
// 卷名长度,卷名从MFT第4个项0x60属性得到(与0x30属性相似)
-
wchar VolumeLabel[MAXIMUM_VOLUME_LABEL_LENGTH];
// 卷名
-
-
uint16 vcnlen;
-
uchar vcn[VCN_LENTH];
-
} NTFS_INFO, *PNTFS_INFO;
其中 MAXIMUM_VOLUE_LABEL_LENGTH定义为
-
#define MAXIMUM_VOLUME_LABEL_LENGTH (32*sizeof(wchar))
NTFS_Boot_Sector .MftStartLcn*NTFS_Boot_Sector. PackedBpb .SectorsPerCluster得到MFT所在扇区号,这里为 786432*8 = 6291456扇区(字节偏移为 6291456*512= 3221225472 ( 十六进制0xC0000000))。然后MFT表里面的内容是根据簇号来读取数据的,那么可以定义一个根据簇号,读取数据的函数,如下形式:
-
typedef
struct
_Partition_Stand_Post
-
{
-
HANDLE
handle;
// 分区句柄
-
uint64 CluNnum;
// 簇号
-
uint32 BytesPerCluster;
// 每簇字节
-
uint64 CluCount;
// 簇数量
-
PNTFS_INFO NtfsInfo;
// 指向NTFS_INFO 结构
-
}Partition_Stand_Post, *pPartition_Stand_Post;
-
-
// 按簇读取数据,
-
// 传入 一个Partition_Stand_Post结构体指针,并指定buf,读取大小
-
// 结果返回读取的数据指针
-
PBYTE
ReadClues(pPartition_Stand_Post post,
PBYTE
buf,
DWORD
lenth)
-
{
-
DWORD
dwbytes = 0;
-
LARGE_INTEGER li = {0};
-
li.QuadPart = post->CluNnum*post->BytesPerCluster;
-
SetFilePointer(post->handle, li.LowPart, &li.HighPart, FILE_BEGIN);
-
ReadFile(post->handle, buf, lenth, &dwbytes, NULL);
-
if
(lenth == dwbytes)
-
{
-
return
buf;
-
}
-
return
NULL;
-
}
下面先说MFT表的结构:
首先,看到的是头部,标记为”FILE”, 结构体如下定义:
-
// 文件记录头
-
typedef
struct
_FILE_RECORD_HEADER
-
{
-
/*+0x00*/
uint32 Type;
// 固定值’FILE’
-
/*+0x04*/
uint16 UsaOffset;
// 更新序列号偏移, 与操作系统有关
-
/*+0x06*/
uint16 UsaCount;
// 固定列表大小Size in words of Update Sequence Number & Array (S)
-
/*+0x08*/
uint64 Lsn;
// 日志文件序列号(LSN)
-
} FILE_RECORD_HEADER, *PFILE_RECORD_HEADER;
-
-
// 文件记录体
-
typedef
struct
_FILE_RECORD{
-
/*+0x00*/
FILE_RECORD_HEADER Ntfs;
// MFT表头
-
/*+0x10*/
uint16 SequenceNumber;
// 序列号(用于记录文件被反复使用的次数)
-
/*+0x12*/
uint16 LinkCount;
// 硬连接数
-
/*+0x14*/
uint16 AttributeOffset;
// 第一个属性偏移
-
/*+0x16*/
uint16 Flags;
// falgs, 00表示删除文件,01表示正常文件,02表示删除目录,03表示正常目录
-
/*+0x18*/
uint32 BytesInUse;
// 文件记录实时大小(字节) 当前MFT表项长度,到FFFFFF的长度+4
-
/*+0x1C*/
uint32 BytesAllocated;
// 文件记录分配大小(字节)
-
/*+0x20*/
uint64 BaseFileRecord;
// = 0 基础文件记录 File reference to the base FILE record
-
/*+0x28*/
uint16 NextAttributeNumber;
// 下一个自由ID号
-
/*+0x2A*/
uint16 Pading;
// 边界
-
/*+0x2C*/
uint32 MFTRecordNumber;
// windows xp中使用,本MFT记录号
-
/*+0x30*/
uint32 MFTUseFlags;
// MFT的使用标记
-
}FILE_RECORD, *pFILE_RECORD;
这里主要关注的就是文件头大小(0x38)可以找到第一个属性地址,紧跟在后面的是文件类型,00表示被删除的文件,01表示正常文件,02表示删除目录,03表示正常目录.再后面就是这个MFT记录的数据大小(很奇怪,为什么数据大小是从头到0xFFFFFFFF的大小+4,这个值为什么不是直接从头到0xFFFFFFFF的大小呢?).
根据FILE头部数据找到下面的一个个属性,接下来分析的就是一个个属性了.
属性由属性头跟属性体组成,属性头的结构定义如下:
-
// 属性头
-
typedef
struct
-
{
-
/*+0x00*/
ATTRIBUTE_TYPE AttributeType;
// 属性类型
-
/*+0x04*/
uint16 RecordLength;
// 总长度(Header+body长度)
-
/**0x06*/
uint16 unknow0;
-
/*+0x08*/
uchar Nonresident;
// 非常驻标志
-
/*+0x09*/
uchar NameLength;
// 操作属性名长度
-
-
// 0X0001为压缩标记
-
// 0X4000为加密标记
-
// 0X8000为系数文件标志
-
/*+0x0A*/
uint16 NameOffset;
// 属性名偏移(从属性起始位置的偏移)
-
// NameLength 如果不为零,则用这个值去寻址数据偏移
-
/*+0x0C*/
uint16 Flags;
// ATTRIBUTE_xxx flags.
-
/*+0x0E*/
uint16 AttributeNumber;
// The file-record-unique attribute instance number for this attribute.
-
} ATTRIBUTE, *PATTRIBUTE;
-
-
// 属性头
-
typedef
struct
_RESIDENT_ATTRIBUTE
-
{
-
/*+0x00*/
ATTRIBUTE Attribute;
// 属性
-
/*+0x10*/
uint32 ValueLength;
// Data部分长度
-
/*+0x14*/
uint16 ValueOffset;
// Data内容起始偏移
-
/*+0x16*/
uchar Flags;
// 索引标志
-
/*+0x17*/
uchar Padding0;
// 填充
-
} RESIDENT_ATTRIBUTE, *PRESIDENT_ATTRIBUTE;
其中
ATTRIBUTE_TYPE
是一个枚举类型,
里面定义了可能出现的所有类型。查看这个结构主要是看AttributeType(上图中,染上绿色的为属性类型,跟在后面的的红色框框内数据为属性头+属性体的大小(这个值必须是大于0x10,小于512的,程序中必须判断),由这个值可以得到下一个属性的地址),这个类型是什么,然后再跟去类型定义的Data部分去解析下面的属性体。遍历属性的时候可以根据属性类型来判断是否已经到达结尾,如果属性类型为
0xFFFFFFFF
则表示已经到达末尾(注意遍历的时候还是要结合FILE头部指定的大小来遍历,这样程序健壮性好很多,还有如果属性头后面跟着的大小值小于0x10也要结束遍历,因为这时候这个MFT项已经不可靠了,再继续下去程序出错可能性比较大(0x00值可能出现死循环))。
属性类型定义,及各个类型属性的结构(缺少无关紧要的结构,其他结构可参考nt4里面的源码并对照winhex分析):
-
// 属性类型定义
-
typedef
enum
_ATTRIBUTE_TYPE
-
{
-
AttributeStandardInformation = 0x10,
-
AttributeAttributeList = 0x20,
-
AttributeFileName = 0x30,
-
AttributeObjectId = 0x40,
-
AttributeSecurityDescriptor = 0x50,
-
AttributeVolumeName = 0x60,
-
AttributeVolumeInformation = 0x70,
-
AttributeData = 0x80,
-
AttributeIndexRoot = 0x90,
-
AttributeIndexAllocation = 0xA0,
-
AttributeBitmap = 0xB0,
-
AttributeReparsePoint = 0xC0,
-
AttributeEAInformation = 0xD0,
-
AttributeEA = 0xE0,
-
AttributePropertySet = 0xF0,
-
AttributeLoggedUtilityStream = 0x100
-
} ATTRIBUTE_TYPE, *PATTRIBUTE_TYPE;
-
-
// 基础信息ATTRIBUTE.AttributeType == 0x10
-
typedef
struct
_STANDARD_INFORMATION
-
{
-
uint64 CreationTime;
// 创建时间
-
uint64 ChangeTime;
// 修改时间
-
uint64 LastWriteTime;
// 最后写入时间
-
uint64 LastAccessTime;
// 最后访问时间
-
uint32 FileAttribute;
// 文件属性
-
uint32 AlignmentOrReserved[3];
//
-
#if 0
-
uint32 QuotaId;
-
uint32 SecurityId;
-
uint64 QuotaCharge;
-
USN
Usn;
-
#endif
-
} STANDARD_INFORMATION, *PSTANDARD_INFORMATION;
-
-
-
// 属性列表ATTRIBUTE.AttributeType == 0x20
-
typedef
struct
_ATTRIBUTE_LIST
-
{
-
ATTRIBUTE_TYPE AttributeType;
-
uint16 Length;
-
uchar NameLength;
-
uchar NameOffset;
-
uint64 StartVcn;
// LowVcn
-
uint64 FileReferenceNumber;
-
uint16 AttributeNumber;
-
uint16 AlignmentOrReserved[3];
-
} ATTRIBUTE_LIST, *PATTRIBUTE_LIST;
-
-
-
// 文件属性ATTRIBUTE.AttributeType == 0x30
-
typedef
struct
-
{
-
/*+0x00*/
uint64 DirectoryFile:48;
// 父目录记录号(前个字节)
-
/*+0x06*/
uint64 ReferenceNumber:16;
// +序列号(与目录相关)
-
/*+0x08*/
uint64 CreationTime;
// 文件创建时间
-
/*+0x10*/
uint64 ChangeTime;
// 文件修改时间
-
/*+0x18*/
uint64 LastWriteTime;
// MFT更新的时间
-
/*+0x20*/
uint64 LastAccessTime;
// 最后一次访问时间
-
/*+0x28*/
uint64 AllocatedSize;
// 文件分配大小
-
/*+0x30*/
uint64 DataSize;
// 文件实际大小
-
/*+0x38*/
uint32 FileAttributes;
// 标志,如目录\压缩\隐藏等
-
/*+0x3C*/
uint32 AlignmentOrReserved;
// 用于EAS和重解析
-
/*+0x40*/
uchar NameLength;
// 以字符计的文件名长度,没字节占用字节数由下一字节命名空间确定
-
-
// 文件名命名空间, 0 POSIX大小写敏感,1 win32空间,2 DOS空间, 3 win32&DOS空间
-
/*+0x41*/
uchar NameType;
-
/*+0x42*/
wchar Name[1];
// 以Unicode方式标识的文件名
-
} FILENAME_ATTRIBUTE, *PFILENAME_ATTRIBUTE;
-
-
// 数据流属性 ATTRIBUTE.AttributeType == 0x80
-
typedef
struct
_NONRESIDENT_ATTRIBUTE
-
{
-
/*+0x00*/
ATTRIBUTE Attribute;
-
-
/*+0x10*/
uint64 StartVcn;
// LowVcn 起始VCN 起始簇号
-
/*+0x18*/
uint64 LastVcn;
// HighVcn 结束VCN 结束簇号
-
-
/*+0x20*/
uint16 RunArrayOffset;
// 数据运行的偏移
-
/*+0x22*/
uint16 CompressionUnit;
// 压缩引擎
-
/*+0x24*/
uint32 Padding0;
// 填充
-
/*+0x28*/
uint32 IndexedFlag;
// 为属性值分配大小(按分配的簇的字节数计算)
-
/*+0x30*/
uint64 AllocatedSize;
// 属性值实际大小
-
/*+0x38*/
uint64 DataSize;
// 属性值压缩大小
-
/*+0x40*/
uint64 InitializedSize;
// 实际数据大小
-
/*+0x48*/
uint64 CompressedSize;
// 压缩后大小
-
} NONRESIDENT_ATTRIBUTE, *PNONRESIDENT_ATTRIBUTE;
以下特别要说明就是数据恢复中要使用的一些结构
0x30 AttributeFileName属性
(如果文件名很长,那么有多个0x30属性,一个记录短文件名,一个记录长文件名),记录了很多文件信息,可能使用到的有文件创建时间、文件修改时间、最后写入时间、文件最后一次访问时间。这里面的几个值相当于用
GetFileTime 或者GetFileInformationByHandle
得到的几个FILETIME值,可以调用
FileTimeToSystemTime
转换时间。其中DataSize
为实际文件使用的大小,在0x80属性中寻找簇恢复数据的时候会用到这个值。最后就是wchar的文件名,此名在cmd中显示需用
WideCharToMultiByte
转换后用
printf
显示,如果用
wprintf
显示中文会出现乱码问题。数据恢复的时候如果需要目录结构可由此属性中的
DirectoryFile
值得到,此值为父目录的记录号(相对于$MFT
元所在扇区的偏移,即:$MFT + DirectoryFile*2)例如:
上图,父目录号为0x0000000002A4
,从DBR中得到$MFT起始簇为786432(上面图一簇为8扇区),则父目录的MFT表项扇区为: 786432*8+0x0000000002A4*2 = 6292808,再查看6292808扇区:
所以这个文件为 X:\windows\notepad.exe(
其中X表示为根目录,具体看前面CreateFile参数值是什么了).
0x80 AttributeData属性( 注意:如果是目录的话, 此结构属性是0xA0 )
如果有多个0x80属性,则应该认为这个文件里面存在数据流文件,数据流表示形式为”0x30记录文件名:流文件名”,并且在explorer浏览中查看不到这个文件,NTFS刚出来的时候,文件流属性进程被病毒作者使用,比如如果要将hack.exe数据加到 1.txt 数据流里面,那么可以如下方式:
-
void
CrateDataStream()
-
{
-
HANDLE
hfile = CreateFile( TEXT(
“1.txt:DataStream.exe”
),
//1.txt中数据流名字为DataStream.exe(随意取的)
-
GENERIC_WRITE,
-
0,
-
NULL,
-
CREATE_ALWAYS,
-
FILE_ATTRIBUTE_NORMAL,
-
NULL);
-
if
(hfile == INVALID_HANDLE_VALUE)
-
{
-
return
;
// 打开文件错误
-
}
-
HANDLE
hExeFile = CreateFile( TEXT(
“hack.exe”
),
-
GENERIC_READ,
-
0,
-
NULL,
-
OPEN_EXISTING,
-
FILE_ATTRIBUTE_NORMAL,
-
NULL);
-
if
(hExeFile == INVALID_HANDLE_VALUE)
-
{
-
CloseHandle(hfile);
-
return
;
// 打开文件错误
-
}
-
DWORD
dwsize = GetFileSize(hExeFile, NULL);
-
BYTE
* buf =
new
BYTE
[dwsize];
-
DWORD
wbytes;
-
ReadFile(hExeFile, buf, dwsize, &wbytes, NULL);
-
WriteFile(hfile, buf, wbytes, &wbytes, NULL);
-
CloseHandle(hExeFile);
-
CloseHandle(hfile);
-
}
一般是病毒作者将这个 1.txt 添加到压缩文件(高级里面选上保存文件流数据), 然后搞成自解压包, 在解压后命令中写入1.txt: DataStream.exe, 在用户双击解压的时候就会运行里面的数据流程序。不过现在杀毒软件对这种数据流病毒基本都能杀了。
再说数据恢复的关键点:数据内容由
NONRESIDENT_ATTRIBUTE. RunArrayOffset
偏移指定DATA
数据地址。如果属性头 ATTRIBUTE.
Nonresident
标记为1
表示非常驻,则下面会解析怎么需要解析DATA部分, 如果为0则表示DATA就是文件内容, 比如一个txt文件里面记录的数据很少, 则此标记为0, 记事本里面数据就保存在DATA中。上图因为查看的是MFT自身,$MFT比较特殊,非常驻属性设置成0(即FALSE)但是却要像非常驻属性一样去分析。
上图蓝色的部分,
看不太清数据,原始数据如下:
最开始的数据为32,其中高4bits表示数据起始簇地址占用字节数,后4bits为数据大小(簇个数)占用字节数..
这里要特别注意,第一个起始簇信息是无符号的,后面第二个开始就是相对于前面一个簇的偏移,是有正负的,查了很多资料,这点基本上都没提及,这也是数据恢复最容易出错的地方,辛辛苦苦写好了程序,一测试发现恢复出来的数据有问题。
上面第一个扇区地址为52604144(0x64559E*8,相对去当前分区的扇区偏移),第二个扇区地址为(0x64559E – 0x 77)*8 = 6575399 扇区(这里值0x89为-119)。
恢复数据的时候可以根据簇大小读取文件,然后保存,再根据前面的
NONRESIDENT_ATTRIBUTE. DataSize
值去
SetFilePointer
设置文件大小继而调用
SetEndOfFile
。
定义一个结构体表示这个结构:
-
typedef
struct
_VCN_FLASH
-
{
-
uchar VcnLen:4;
// 簇流长度 *8*512 才是得到的文件字节数
-
uchar StartVcnLen:4;
// 簇流起始位置–簇号
-
-
uchar Data[1];
// 簇流长度&Data + 簇流起始位置&Data+VcnLen 数据部分
-
}VCN_FLASH, *PVCN_FLASH;
再定义2个函数计算有符号与无符号数:
-
uint64 make_uint64(uchar* buf,
int
lenth)
-
{
-
int64 ui=0;
-
if
(lenth > 8)
-
{
-
return
(int64)0;
-
}
-
-
for
(
int
i=0; i<lenth; i++)
-
{
-
ui = (buf[i]<<8*i)|ui;
-
}
-
return
ui;
-
}
-
-
-
int64 make_int64(uchar* buf,
int
lenth)
-
{
-
int64 ui=0;
-
if
(lenth > 8)
-
{
-
return
(int64)0;
-
}
-
-
for
(
int
i=0; i<lenth; i++)
-
{
-
ui = (buf[i]<<8*i)|ui;
-
}
-
-
// 判断符号位,为负则需减取反
-
if
(buf[lenth-1] >= 0x80)
-
{
-
int64 xorval = 0;
-
for
(i=0; i<lenth; i++)
-
{
-
xorval = xorval|(0xFF<<8*i);
-
}
-
ui = -((ui – 1)^xorval);
-
}
-
return
ui;
-
}
0x90 AttributeIndexRoot 属性 ( 目录索引B+树结构 )
这个属性,我也不是很清楚,等弄清楚了,再接着完善……………..