1前言
NTFS是现在流行的磁盘格式.想想当年FAT32的时代都差不多过去了,现在装机差不多都是NTFS.我们平时编程时使用文件操作函数基于操作系统,所以不管磁盘是何种格式,都只是用相同的几个函数就可搞掂,似乎分析NTFS文件系统没有什么必要.但是如果要搞些底层一点的东西,比如数据恢复啊,磁盘分析啊等等,甚至搞一些高级病毒之类,了解NTFS文件系统都很有必要!
现在我准备开始这个研究,现在的我对于这个是一窍不通,所以也不知从哪里开始,只能见一步学一步.所以借爱delphi(www.aidvr.com)网站发一些杂乱无章的笔记.
先定一个目标,要实现NTFS文件系统解析,写出一个程序,能在NTFS下1.读取所有文件目录结构. 2.建立新文件/目录 3.删除文件/目录. 4.读出存在文件的数据. 5.向存在文件写入数据.
从今天起,将要开始这个笔记,直到完成目标.每有什么新的发现或研究成果我都会到这个贴子下跟贴发表,其间可能遇到好多好多的困难,希望得到各位高手的帮助.在此先谢谢各位!
要分析NTFS文件系统,必须先获得分区上的数据.可以写一个函数获取指定分区上指定扇区(512字节)的数据.
添加一个单元用来做这个任务.
unit Wu_DiskSectorUnit;
interface
uses
Windows,SysUtils,Wu_BasicUnit;
function Wu_OpenDiskSector(Drive:char):boolean; //打开指定分区,准备读取数据 Drive:代表指定分区A-Z 例:Wu_OpenDiskSector('C');
function Wu_ReadDiskSector(SectorNum:cardinal;var retbuf:Pchar):boolean; //读取分区指定扇区的数据(512字节),并将数据指针返回retbuf
function Wu_WriteDiskSector(SectorNum:cardinal;inbuf:Pchar):boolean; //将数据写入分区指定扇区(512字节),inbuf代表要写入内容的指针
procedure Wu_CloseDiskSector(); //退出时用来结束打开指定分区
implementation
var
hDeviceHandle:cardinal;
ReadBuf:array[0..511] of char;
WriteBuf:array[0..511] of char;
procedure Wu_CloseDiskSector();
begin
if closehandle(hDeviceHandle) then
begin
hDeviceHandle:=INVALID_HANDLE_VALUE;
end;
end;
function Wu_OpenDiskSector(Drive:char):boolean;
var
D:string;
begin
Wu_CloseDiskSector();
D:='\\.\'+Drive+':';
hDeviceHandle := CreateFile(Pchar(D), GENERIC_ALL,FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,0, 0);
if (hDeviceHandle <> INVALID_HANDLE_VALUE) then
begin
result:=true;
end
else
begin
result:=false;
end;
end;
function Wu_ReadDiskSector(SectorNum:cardinal;var retbuf:Pchar):boolean;
var
n:integer;
I_LARGE:LARGE_INTEGER;
I_INT64:INT64;
I_SectorNum:INT64;
begin
for n:=0 to 511 do
begin
ReadBuf[n]:=#0;
end;
if (hDeviceHandle <> INVALID_HANDLE_VALUE) then
begin
//FileSeek(hDevicehandle,SectorNum*512,0);这样写有一个错误,就是不支持大于2146959360的数
I_SectorNum:=SectorNum;
I_INT64:=I_SectorNum*512;
Wu_Copy(@I_INT64,1,8,@I_LARGE,1);
SetFilePointer(hDevicehandle,I_LARGE.LowPart,@I_LARGE.highpart,0);
if FileRead(hDevicehandle,ReadBuf[0],512)=512 then
begin
result:=true;
end
else
begin
result:=false;
end;
end
else
begin
result:=false;
end;
retbuf:=@ReadBuf[0];
end;
function Wu_WriteDiskSector(SectorNum:cardinal;inbuf:Pchar):boolean;
var
n:integer;
I_LARGE:LARGE_INTEGER;
I_INT64:INT64;
I_SectorNum:INT64;
begin
for n:=0 to 511 do
begin
WriteBuf[n]:=inbuf[n];
end;
if (hDeviceHandle <> INVALID_HANDLE_VALUE) then
begin
//FileSeek(hDevicehandle,SectorNum*512,0);这样写有一个错误,就是不支持大于2146959360的数
I_SectorNum:=SectorNum;
I_INT64:=I_SectorNum*512;
Wu_Copy(@I_INT64,1,8,@I_LARGE,1);
SetFilePointer(hDevicehandle,I_LARGE.LowPart,@I_LARGE.highpart,0);
if FileWrite(hDevicehandle,WriteBuf[0],512)=512 then
begin
result:=true;
end
else
begin
result:=false;
end;
end
else
begin
result:=false;
end;
end;
end.
现在我们用Wu_ReadDiskSector读取分区第一个扇区的数据 var P:Pchar; Wu_OpenDiskSector('G'); //打开G盘 Wu_ReadDiskSector(0,P); //读取G盘0扇区 Wu_CloseDiskSector; 获得这个扇区称为引导扇区,512个字节数据的意义如下表(这个表从网上拿来的) 字节偏移 长度(字节) 常用值 意义 0X00 3 0XEB5290 JMP指令 0X03 4 NTFS 文件系统 ID 0X0B 2 0X0002 每扇区字节数 0X0D 1 0X08 每簇扇区数 0X0E 2 0X0000 保留扇区 0X10 3 0X000000 总为0 0X13 2 0X0000 NTFS未使用,为0 0X15 1 0XF8 介质描述 0X16 1 0X0000 总为0 0X18 2 0X3F00 每磁道扇区数 0X1A 2 0XFF00 磁头数 0X1C 4 0X3F000000 隐含扇区 0X20 4 0X00000000 NTFS未使用,为0 0X24 4 0X80008000 NTFS未使用,为0 0X28 8 0X4AF57F0000000000 扇区总数 0X30 8 0X0400000000000000 $MFT的逻辑簇号 0X38 8 0X54FF070000000000 $MFTMirr的逻辑簇号 0X40 4 0XF6000000 每MFT记录簇数 0X44 4 0X0100000 每索引簇数 0X48 8 0X14A51B74C91B741C 卷标 0X50 4 0X00000000 校验和 0X54 430 略 引导代码 0X1FE 2 0X55AA 签名 根据此表我们写出数据结构. type R_Wu_NTFSBPB=packed record bJmp:array[0..2] of byte; //跳转指令[0] bNTFlags:array[0..3] of char; //"NTFS"标识[3] breserve1:array[0..3] of char; //这个上表没有说明[7] wBytePerSector:word; //每扇区字节数(一般都是512)(重要)[B] bSectorPerCluster:byte; //每簇扇区数(重要)[D] wReserveSectors:word; //保留扇区数(重要)[E] bFatNum:byte; //???未知[10] wRootDirNum:word; //???未知[11] wSectorOfParti:word; //???未知[13] bMedium:byte; //介质描述[15] wSectorPerFat:word; //???未知[16] wSectorPerTrack:word; //每磁道扇区数[18] wHeadNum:word; //磁头数[1A] dwHideSector:cardinal; //隐含扇区[1C] dwSectoOfParti:cardinal;//NTFS未使用,为0[20] bDeviceFlag:byte; //???未知[24] bReserve2:byte; //???未知[25] wReserve3:word; //???未知[26] ullSectorsOfParti:int64;//扇区总数(这里理论上是一个64位无符号类型,不知用int64是否有错)(重要)[28] ullMFTAddr:int64; //$MFT的起始逻辑簇号(这里理论上是一个64位无符号类型,不知用int64是否有错)(重要)[30] ullMFTMirrAddr:int64; //$MFTMirr的起始逻辑簇号(这里理论上是一个64位无符号类型,不知用int64是否有错)(重要)[38] bClusterPerFile:cardinal;//每MFT记录簇数[40] dwClusterPerINDX:cardinal;//每索引簇数[44] bSerialID:array[0..7] of char;//卷标[48] //[50] end; |
现在我们已经可以分析引导扇区所记录的内容.
var
P:Pchar;
BPR:R_Wu_NTFSBPB;
Wu_OpenDiskSector('G'); //打开G盘
Wu_ReadDiskSector(0,P); //读取G盘0扇区
wu_Copy(P,1,80,@BPR,1); //将数据直接拷贝到结构变量中即可
Wu_CloseDiskSector;
下面我们可以根据BPR的数据来进行一些初始化的设置
unit Wu_NTFSLook;
interface
uses
Wu_DiskSectorUnit,Wu_NTFSTypeUnit,Wu_BasicUnit;
procedure OpenDisk(Disk:Char); //打开某个盘
procedure CloseDisk; //退出时关闭
implementation
var
BPR:R_Wu_NTFSBPR; //用来保存当前打开分区的引导数据
ClusterSize:cardinal;//定义当前打开分区中每一个簇等于多少个基本扇区
MFTRecordSize:cardinal=2; //定义当前打开分区中每一个MFT等于多少个基本扇区
MFTBegin:int64; //定义当前打开分区中MFT的起始位置(每几个基本扇区)
procedure OpenDisk(Disk:Char);
var
P:Pchar;
begin
Wu_CloseDiskSector;
Wu_OpenDiskSector(Disk);
Wu_ReadDiskSector(0,P); //读取指定盘0扇区
Wu_Copy(P,1,80,@BPR,1);
ClusterSize:=BPR.wBytePerSector div 512 * BPR.bSectorPerCluster;
MFTBegin:=BPR.ullMFTAddr * ClusterSize;
end;
procedure CloseDisk;
begin
Wu_CloseDiskSector;
end;
end.
下一步就可以通过[$MFT的起始逻辑簇号]来访问$MFT.
MFT应该是一个数据结构,由每一条MFT整齐排列组成一整个MFT列表,用来记录磁盘内所有文件目录的关系.
既然MFT是一个接着一个排列的,那么我们必须要知道每一个MFT的大小,以方便我们的指针对应位置.
(注:这里MFT的大小是一个很奇怪的问题,现在所查到的资料都说得不清不楚,有些说固定大小为1024字节,有些又说不固定,有待研究)
我们暂且定义MFT的大小为1024字节.
const
MFTRecordSize=1024; //不管怎样都一定是512的整数倍这是肯定的
然后[$MFT的起始逻辑簇号]指向了列表第一项的位置.
BPR.ullMFTAddr;
下面是从网上找来的MFT文件记录属性头结构
偏移 长度 描述
0X00 4 固定值“FILE”
0X04 2 更新序列号偏移,与操作系统有关
0X06 2 固定列表大小
0X08 8 日志文件序列号
0X10 2 序列号(用于记录文件被反复使用的次数)
0X12 2 硬连接数,跟目录中的项目关联,非常重要的参数
0X14 2 第一个属性的偏移
0X16 2 标志字节①
0X18 4 文件记录实时大小(字节)②
0X1C 4 文件记录分配大小(字节)
0C20 8 基础记录 (0: itself)
0X28 2 下一个自由ID号
0X2A 2 边界
0X2C 4 WINDOWS XP中使用,本MFT记录号
0X30 4 MFT的使用标记③
经详细研究,MFT记录中前52($34)个字节的内容所表达的意思是固定的,那么我们又可以写数据结构了
type
R_Wu_MFT1=packed record
bHeadID:array [0..3] of char; //MFT标志,'FILE'[0]
usFixupOffset:word; //更新序列号偏移,与操作系统有关[4]
usFixupNum:word; //更新序列号的大小,固定列表大小[6]
bReserve1:array [0..7] of byte; //日志文件序列号[8]
wUnknownSeqNum:word; //序列号(用于记录文件被反复使用的次数)[10]
usLinkNum:word; //硬连接数[12]
usAttrOffset:word; //第一个属性的偏移地址(重要)[14]
wResident:word; //文件属性标志(重要,表示这个MFT文件的状态1:普通文件 0:文件被删除 3:普通目录 2:目录被删除)[16]
ulMFTSize:cardinal; //文件记录的实际长度,用来记录本MFT的实时大小(重要,用于MFT中每个属性分析到结束)[18]
ulMFTAllocSize:cardinal; //记录分配的大小(重要??)[1C]
ullMainMFT:int64; //基本文件记录的文件索引号(重要??)[20]
wNextFreeID:word; //下一个自由ID号(重要??)[28]
wBianJie:word; //边界[2A]
ulXpMFTNum:cardinal; //WINDOWS XP中使用,本MFT记录号[2C]
MFTusesID:word; //MFT的使用标记(重要,它在MFT记录的两个扇区中与每扇区的最末两个字节相对应,如若不然,系统将示此记录为非法记录)[30]
//[32]
MFTUsesTemp:array of word; //MFT的使用标记(重要,上面由于MFT记录扇区将每个扇区中最后的两个字节占用掉了,所以在这里补回来)[32]
end;
MFT是由一个固定的头52($34)字节和一系列属性(不固定,每一条属性长度不固定,属性列表是由哪一些属性组成也不固定)组成,但总大小不超过1024字节(超过时由多条记录组成).
那么现在我们就可以读取指定一项MFT的记录
我们写一个函数用来获得指定第几个MFT的MFT头数据.
var
MFTHead:R_Wu_MFT1; //用来保存调用GetMFTHead所获得的MFT记录基本头数据
function GetMFTHead(N:int64):Pchar;
var
P:Pchar;
begin
Wu_ReadDiskSector(MFTBegin+N*MFTRecordSize,P);
Wu_Copy(P,1,52,@MFTHead,1); //拷贝到结构变量中
Result:=@MFTHead;
end;
现在我们再重新分析一下它们的关系.
引导数据->MFT列表的第一项位置(指向)
MFT列表->每一个MFT(包含)
上一个MFT->下一个MFT(固定偏移1024)
每一个MFT所包含
{
MFT头(固定包含52字)
MFT属性列表(一个或多个)
}
MFT头->MFT属性列表的第一项位置(偏移指向)
上一个MFT属性->下一个MFT属性(偏移指向)
每一个MFT属性所包含
{
MFT属性头(固定包含9字)
MFT属性数据(各种属性)
}
我们上面已经可以获取MFT头,现在要进行的是对MFT所包含的属性数据分析.
MFT属性头是固定的,可以用一个数据结构来表达
type
R_Wu_MFTATTRIBUTEHead=packed record
Attribute:cardinal; //属性类型(重要)[0]
AllLen:cardinal; //属性的总长度(重要)[4]
ResidentSymbol:byte; //常驻或非常驻标志(0X00:常驻属性;0X01:非常驻属性)(重要)[8]
//[9]
end;
每一个MFT属性的类型是由MFT属性头的属性类型决定的
每一条MFT记录都可能包含任意几条属性
下面拿一个网上的NTFS卷上常用属性说明表
属性号 属性名 属性描述
0X10 $STANDRD_INFORMATION(标准属性) 包括基本文件属性,如只读、存档;时间标记,如文件的创建时间和最近一次修改的时间;有多少目录指向本文件
0X20 $ATTRIBUTE_LIST(属性列表) 当一个文件需要使用多个MFT文件记录时,用来表示该文件的属性列表
0X30 $FILE_NAME(文件名属性) 这是以Unicode字符表示的,由于MS-DOS不能正确识别Win32子系统创建的文件名,当Win32子系统创建一个文件名时,MTFS会自动生成一个备用的MS-DOS文件名,所以一个文件可以有多种文件名属性。
0X40 $VOLUME_VERSION(卷版本) 卷版本号
0X50 $SECURITY_DEscriptOR(安全描述符) 这是为了向后兼容而被保留的,主要用于保护文件以防止未授权访问。
0X60 $VOLUME_NAME(卷名) 卷名称或卷标识
0X70 $VOLUME_INFORMATION(卷信息) 卷信息
0X80 $DATA(数据属性) 这是文件的内容
0X90 $INDEX_ROOT(索引根属性) 索引根
0XA0 $INDEX_ALLOCATION(索引分配属性) 索引分配
0XB0 $BITMAP(位图属性) 位图
0XC0 $SYMBOLIC_LINK(符号链接) 符号链接
0XD0 $EA_INFORMATION(EA信息) 扩充属性信息:主要为与OS/2兼容
0XE0 $EA 扩充属性:主要为与OS/2兼容
0X100 $OBJECT_ID 对象ID:一个具有64个字节的标识符,其中最低的16个字节对卷来说是唯一的
********************************************
标准属性($10)
$STANDRD_INFORMATION的类型值为$10,总是常驻属性.它包含一个文件或目录的基本元数据.
虽然标准信息属性中含用的信息比较多,但对于文件系统来说并不重要.这个属性中的日期和时间值会相应地随着对文件的各种操作而与操作系统的时间保持一致.
网上很难找到该结构的定义信息
*******************************************
*******************************************
属性列表($20)
$ATTRIBUTE_LIST 当一个文件(或目录)的属性不能放在一个MFT文件记录中,当一个文件需要使用多个MFT文件记录时,用来表示该文件的属性列表
虽然数据属性常常因太大而存储在运行中,但是其他属性也可能因MFT文件记录没有足够空间而需要存储在运行中。另外,如果一个文件有太多的属性而不能存放在MFT记录中,那么第二个MFT文件记录就可用来容纳这些额外的属性(或非常驻属性的头)。在这种情况下,一个叫作“属性列表”(attribute list)的属性就加进来。属性列表包括文件属性的名称和类型代码以及属性所在MFT的文件引用。属性列表通常用于太大或太零散的文件,这种文件因VCN-LCN映射关系太大而需要多个MFT文件记录。具有超过200个运行的文件通常需要属性列表。
这个结构也不是很清楚
*******************************************
*******************************************
文件名属性($30)
$FILE_NAME 文件名属性,这个是我们读取文件名的关键
网上的表:
偏移 大小 值 描述
0X00 4 0X30 属性类型
0X04 4 0X68 总长度
0X08 1 0X00 非常驻标志(0X00:常驻属性;0X01:非常驻属性)
0X09 1 0X00 属性名的名称长度
0X0A 2 0X18 属性名的名称偏移
0X0C 2 0X00 标志
0X0E 2 0X03 标识
0X10 4 0X4A 属性长度(L)
0X14 2 0X18 属性内容起始偏移
0X16 1 0X01 索引标志
0X17 1 0X00 填充
0X18 8 0500000000000500 父目录记录号(前6个字节)+序列号(与目录相关)
0X20 8 e0e3e1a066e9c301 文件创建时间
0X28 8 同上 文件修改时间
0X30 8 同上 最后一次MFT更新的时间
0X38 8 同上 最后一次访问时间
0X40 8 0040000000000000 文件分配大小
0X48 8 0040000000000000 文件实际大小
0X50 4 06000000 标志,如目录、压缩、隐藏等
0X54 4 00000000 用于EAS和重解析点
0X58 1 04 以字符计的文件名长度⑤,每字节占用字节数由下一字节命名空间确定,一个字节长度,所以文件名最大为255字节长(L)
0X59 1 03 文件名命名空间,见表8
0X60 2L 24004d004600 以Unicode方式标识的文件名
标志 意义 描述
0 POSIX 这是最大的命名空间。它大小写敏感,并允许使用除NULL(0)和左斜框(/)以外的所有Unicode字符作为文件名,文件名最大长度为255个字符。有一些字符,如冒号(:),在NTFS下有效,但WINDOWS不让使用。
1 WIN32 WIN32和POSIX命名空间的一个子集,不区分大小写,可以使用除“*/:<>?\|”以外的所有Unicode字符。另外,文件不能以句点和空格结束。
2 DOS DOS是WIN32命名空间的一个子集,要求比空格的ASC||码要大,且不能使用* + / , : ; < > ? \ | 等字符,另外其格式是1-8个字符的文件名,然后是句点分隔,然后是1-3个字符的扩展名。
3 Win32 & DOS 该命名空间要求文件名对Win32和DOS命名空间都有效,这样,文件名就可以在文件记录中只保存一个文件名
上面的表好像有一个错误,就是0X59再加一个字节应该为0X5A,而上表写0X60不知为什么
我们定义它的数据结构
type
R_Wu_FILENAMEATTRIBUTE=packed record //MFT文件名属性结构($30)
Attribute:cardinal; //属性类型[0]
AllLen:cardinal; //总长度,包括标准属性头本身[4]
ResidentSymbol:byte; //常驻或非常驻标志[8]
{其实上面那三个就是刚才MFT属性头,和R_Wu_MFTATTRIBUTEHead定义是一样的,现在这里重写只是为了数据拷贝操作的方便}
NameLen:byte; //名称长度[9]
NameOffset:word; //名称偏移[A]
FileSymbol:word; //标志,如0x0001为压缩标志,0x4000为加密标志,0x8000为稀疏标志[C]
Identifier:word; //标识[E]
AttributeLen:cardinal;//属性长度[10]
AttributeContentOffset:word;//属性内容起始偏移[14]
IndexSymbol:byte; //索引标志[16]
Fill:byte; //填充[17]
FileConferenceNumOfFartherDir:array[0..5] of byte;//父目录的文件参考号(6Byte)[18]
FileConferenceNumOfFartherDir_Temp:word;//父目录的文件参考号??[1E]
FileCreateTime:int64; //文件创建时间[20]
FileModifyTime:int64; //文件修改时间[28]
LastMFTRenewTime:int64;//最后一次的MFT更新时间[30]
FileLastVisitTime:int64;//文件最后一次的访问时间[38]
FileAllocSize:int64; //文件分配大小[40]
FileRealSize:int64; //文件实际大小[48]
FileSymbol_C:cardinal; //文件标志,如目录,压缩,隐藏等[50]
EAS:cardinal; //用于EAs和重解析点[54]
FileNameLen:byte; //以字符计算的文件名长度,每字符字节数由下一字节命名空间决定[58]
FileNameSpace:byte; //文件名命名空间[59]
//[5A]
FileName:string; //以Unicode 方式表示的文件名[5A]
end;
*******************************************
*******************************************
数据属性($80)
$DATA:数据流属性,也就是磁盘文件的内容.这是我们读取文件的内容的关键.
NTFS对于文件数据有两种不同的处理方式.当文件大小小于一定数时,它就直接将内容放在MFT中(不可思议吧,MFT里面有时也管这个),只有文件到达一定长度,MFT中放不下了,才将数据移动磁盘空间中存放.
那么直接放在MFT中的称之为常驻,否则为非常驻.(理论上MFT中每一种属性都可以这么干的)
下面用网上的表来说明:
偏移 大小 值 意义
0X00 4 0X80 属性类型(0X80,数据流属性)
0X04 4 0X48 属性长度(包括本头部的总大小)
0X08 1 0X01 非常驻标志(0X00:常驻属性;0X01:非常驻属性)
0X09 1 0X00 名称长度,$AttrDef中定义,所以名称长度为0
0X0A 2 0X0040 名称偏移
0X0C 2 0X00 标志,0X0001为压缩标志,0X4000为加密标志,0X8000为系数文件标志
0X0E 2 0X0001 标识
0X10 8 0X00 其实VCN
0X18 8 0X1FF1 结束VCN
0X20 2 0X40 数据运行的偏移
0X22 2 0X00 压缩引擎
0X24 4 0X00 填充
0X28 8 0X1FF2000 为属性值分配大小(按分配的簇的字节数计算)
0X30 8 0X1FF1C00 属性值实际大小
0X38 8 0X1FF1C00 属性压缩大小
0X40 … 2148062431… 数据运行④
④ 第四处为数据运行,是在数据流属性为非常驻的状况下索引到数据流的关键。其具体计算方式如下:
这是例子中的80H属性,其中蓝色部分为该80H属性(数据属性)的运行:
80 00 00 00 68 00 00 00 01 04 40 00 00 00 08 00
00 00 00 00 00 00 00 00 4b 00 00 00 00 00 00 00
48 00 00 00 00 00 00 00 00 c0 04 00 00 00 00 00
88 bc 04 00 00 00 00 00 88 bc 04 00 00 00 00 00
24 00 53 00 44 00 53 00 21 48 06 24 31 01 f3 aa
02 31 01 0d 7a fd 31 01 f3 38 02 31 01 c3 4b 05
00 a2 6b 81 d0 50 3d e1
该运行分为
子运行1:21 48 06 24
子运行2:31 01 f3 aa 02
子运行3:31 01 0d 7a fd
子运行4:31 01 f3 38 02
子运行5:31 01 c3 4b 05
以子运行1:“21 48 06 24”为例,“2”表示后面4个字节中后面2个字节是子运行的起始簇号,即子运行的起始簇号为“24 06”,“1”表示前面的1个字节表示子运行的大小,即该子运行的大小为“48”。所以该文件数据实际是从起始扇区号为0x2406的地方,占用0x48个簇。接下来是子运行2,运行2的簇号的起始位置为0x2406 + 0x02aaf3 = 0x2cef9。占用0x01个簇。接下来是子运行3,按照前面的理论,子运行3的起始簇号应该是0x2cef9+0xfd0a0d。但是0xfd0a0d的第一个字节为1(fd的首字节),证明此数为负数,所以不能简单的做加法,而应该取该数的补数来计算。既,0xfd7a0d取反加1,得到0x2F5F2,所以运行3的起始扇区号为0x2cef9 - 0x285f3 = 0x4096。占用0x01个簇。依此类推直到子运行之后为“00”时结束。
在程序中,一个Run代表整个运行,每一个子运行以链表的方式链接在一起。
表中没有关于常驻数据属性的说明,其实我们可以自己分析一下来自定义一个结构.
type
R_Wu_DATAATTRIBUTE_1=packed record //MFT数据属性结构(常驻)($80)
Attribute:cardinal; //属性类型[0]
AllLen:cardinal; //总长度,包括标准属性头本身[4]
ResidentSymbol:byte; //常驻或非常驻标志[8]
{其实上面那三个就是刚才MFT属性头,和R_Wu_MFTATTRIBUTEHead定义是一样的,现在这里重写只是为了数据拷贝操作的方便}
NameLen:byte; //名称长度,$AttrDef中定义,所以名称长度为0[9]
NameOffset:word; //名称偏移[A]
FileSymbol:word; //标志,如0x0001为压缩标志,0x4000为加密标志,0x8000为稀疏标志[C]
Identifier:word; //标识[E]
DataLen:cardinal; //数据长度(???,判断它就是)[10]
DataOffset:cardinal; //数据起始位置(???,估计它就是)[14]
//[18]
Data:string; //数据内容[18]
end;
在研究中发现这里有一点,XP中对于后面数据的处理,一定是8的整倍数.就是说如果文件长度小于8,那么一定会留8个空间,如果文件长度为9,那么又会留到16个空间.可能是以统一$30属性中MS-DOS文件名8字节的处理.
非常驻数据属性结构
type
R_Wu_DATAATTRIBUTE_2=packed record //MFT数据属性结构(非常驻)($80)
Attribute:cardinal; //属性类型[0]
AllLen:cardinal; //总长度,包括标准属性头本身[4]
ResidentSymbol:byte; //常驻或非常驻标志[8]
{其实上面那三个就是刚才MFT属性头,和R_Wu_MFTATTRIBUTEHead定义是一样的,现在这里重写只是为了数据拷贝操作的方便}
NameLen:byte; //名称长度[9]
NameOffset:word; //名称偏移[A]
FileSymbol:word; //标志,如0x0001为压缩标志,0x4000为加密标志,0x8000为稀疏标志[C]
Identifier:word; //标识[E]
StartVCN:int64; //起始VCN[10]
EndVCN:int64; //结束VCN[18]
RunOffset:word; //数据运行的偏移[20]
CompressEngine:word; //压缩引擎[22]
Fill:cardinal; //填充[24]
MallocForAttribute:int64;//为属性值分配的大小[28]
AttributeRealSize:int64;//属性值实际大小[30]
AttributeCompressSize:int64;//属性值压缩大小[38]
//[40]
DataRun:string; //数据运行的内容[40]
end;
*******************************************
*******************************************
索引根属性($90)
$INDEX_ROOT:这个属性记录当前目录(目录有这个属性)下一级的目录或文件的信息,关键!
在NTFS系统说,目录也是文件,目录以文件的形式存在.
一般来说,目录包括头属性、标准属性(0X10)、文件名属性(0X30)、索引根属性(0X90),大多数时候还会包含索引分配属性(0XA0)。
目录中包含的保存在该目录下的所有文件的信息
保存在该目录下的每一个文件都有一个项目。这个项目我们把它称作索引项,因为目录通过它索引到文件。因此,与文件的数据属性一样,目录的“数据属性”有一个新的名字叫做“索引根属性”(0X90属性)。索引根属性包括两个方面的内容。一个是该属性的头,这里我把他叫做索引头。还有一个部分是索引部分。索引部分又包括“索引项”和“索引项尾”。
引用网上的表:
属性头
10属性头
10属性
30属性头
30属性
90属性头(索引头)
90属性 (索引项)
索引项1
索引项2
………
00 00 00 00 00 00 00 00 10 00 00 00 02 00 00 00 (索引尾)
FF FF FF FF 82 79 47 11
那么一整个索引根属性($90)又是怎么组成的呢?看下面:
MFT属性头(固定9字)|索引头(也是固定的)|索引项...|索引尾|
小目录组成就是这样的.
网上关于索引根属性表:
偏移 大小 意义
0X00 4 属性号
0X04 4 属性长度
0X08 1 常驻标志
0X09 1 名称长度
0X0A 2 名称偏移
0X0C 2 标志(常驻属性不能压缩)
0X0E 2 属性ID
0X10 4 属性长度(不含头)
0X14 2 属性偏移
0X16 1 索引标志
0X17 1 填充
0X18 8 属性名
0X20 4 索引属性类型
0X24 4 排序规则
0X28 4 索引项分配大小
0X2C 1 每索引记录的簇数
0X2D 3 填充
0X30 4 每索引的偏移
0X34 4 索引项的总大小
0X38 4 索引项的分配
0X3C 1 标志,(0X01大索引)
0X3C 3 填充
上表最后有一个地方标志的位置是3C,但后面为什么填充也是3C??
我们写下面的结构:
type
R_Wu_INDEXATTRIBUTE=packed record //MFT索引根属性结构($90)
Attribute:cardinal; //属性类型[0]
AllLen:cardinal; //总长度,包括标准属性头本身[4]
ResidentSymbol:byte; //常驻或非常驻标志[8]
{其实上面那三个就是刚才MFT属性头,和R_Wu_MFTATTRIBUTEHead定义是一样的,现在这里重写只是为了数据拷贝操作的方便}
NameLen:byte; //名称长度[9]
NameOffset:word; //名称偏移[A]
FileSymbol:word; //标志,常驻属性不能压缩[C]
Identifier:word; //标识,属性ID[E]
AttriLen:cardinal; //属性长度,不含头[10]
AttriOffset:word; //属性偏移[14]
IndexSign:byte; //索引标志[16]
Fill1:byte; //填充1[17]
AttributeName:int64; //属性名[18]
IndexAttribute:cardinal;//索引属性类型[20]
ListAttribue:cardinal;//排序规则[24]
IndexSize:cardinal; //索引项分配大小[28]
IndexOfCluster:byte; //每索引记录的簇数[2C]
Fill2:array[0..2] of byte;//填充2[2D]
IndexToIndexSize:cardinal;//每索引的偏移[30]
AllIndexSize:cardinal;//索引项的总大小[34]
AllIndexList:cardinal;//索引项的分配[38]
IDX:byte; //标志,(0X01大索引)[3C]
Fill3:array[0..2] of byte;//填充3[3D]
//[40]
end;
MFT索引根属性后接着索引项列表,索引项列表由一个或多个索引项组成.
索引项的结构,网上的表:
偏移 大小 意义
0X00 8 文件的MFT记录号
0X08 2 索引项大小
0X0A 2 名称偏移
0X0C 4 索引标志+填充
0X10 8 父目录的MFT文件参考号
0X18 8 文件创建时间
0X20 8 文件修改时间
0X28 8 文件最后修改时间
0X30 8 文件最后访问时间
0X38 8 文件分配大小
0X40 8 文件实际大小
0X48 8 文件标志
0X50 1 文件名长度(F)
0X51 1 文件名命名空间
0X52 2F 文件名(填充到8字节)
0X52+2F P
0X52 +P+2F 8 子节点索引缓存的VCL
type
R_Wu_INDEXDATA=packed record //MFT索引根属性索引项结构
MFTnum:array[0..5] of byte;//文件的MFT记录号[0]
MFTnum_Temp:word; //文件的MFT记录号???[6]
IndexLen:word; //索引项大小[8]
FileNameOffset:word; //名称偏移???[A]
IndexSign:word; //索引标志[C]
Fill:word; //填充[E]
FMFTnum:array[0..5] of byte;//父目录的MFT文件参考号[10]
FMFTnum_Temp:word; //父目录的MFT文件参考号???[16]
CreateTime:int64; //文件创建时间[18]
ModifyTime:int64; //文件修改时间[20]
RecordModifyTime:int64; //文件最后修改时间[28]
LastVisitTime:int64; //文件最后访问时间[30]
FileAlloc:int64; //文件分配大小[38]
FileSize:int64; //文件实际大小[40]
FileSign:int64; //文件标志[48]
FileNameLen:byte; //文件名长度[50]
FileNameAir:byte; //文件名命名空间[51]
//[52]
FileName:string; //文件名(填充到8字节)[52]
end;
*******************************************
*******************************************
索引分配属性($A0)
$INDEX_ALLOCATION:如果一个目录它的下一级目录或文件数大于一定值时,$90中无法存放下所有的信息,那么就使用磁盘空间来存储这些信息,并且启用这个属性记录这个新分出来的空间位置的信息,关键!
如果MFT有过多的MFT索引根属性索引项,NTFS就将它们放在磁盘空间当作一个文件内容来看待,所以这里$A0和$80的非常驻结构是绝对的相似,所以我们可以直接拿$80结构直接改改就用.
type
R_Wu_INDEXListATTRIBUTE=packed record //MFT索引分配属性结构($A0)
Attribute:cardinal; //属性类型[0]
AllLen:cardinal; //总长度,包括标准属性头本身[4]
ResidentSymbol:byte; //常驻或非常驻标志[8]
{其实上面那三个就是刚才MFT属性头,和R_Wu_MFTATTRIBUTEHead定义是一样的,现在这里重写只是为了数据拷贝操作的方便}
NameLen:byte; //名称长度[9]
NameOffset:word; //名称偏移[A]
FileSymbol:word; //标志,如0x0001为压缩标志,0x4000为加密标志,0x8000为稀疏标志[C]
Identifier:word; //标识[E]
StartVCN:int64; //起始VCN[10]
EndVCN:int64; //结束VCN[18]
RunOffset:word; //数据运行的偏移[20]
CompressEngine:word; //压缩引擎[22]
Fill:cardinal; //填充[24]
MallocForAttribute:int64;//为属性值分配的大小[28]
AttributeRealSize:int64;//属性值实际大小[30]
AttributeCompressSize:int64;//属性值压缩大小[38]
//[40]
DataRun:string; //数据运行的内容[40]
end;
这里需要理解的是,数据运行的内容和$80(数据属性)那里是一样的,所以每一个运行的大小也是等于一个簇.因为如果不是这样就不可能统一了!而且据观察不存在连续两个或以上的簇存在.一般一个运行只是一个簇
现在列表都存到数据中去了,那么数据中又是些什么内容呢?其实数据中的内容正是MFT索引根属性索引项的列表,和$90中的是一样的,只是还多了一个叫索引区头部的结构
引用网上的表:
偏移 大小 意义
0X00 4 INDEX
0X04 2 更新序列号的偏移
0X06 2 更新序列号与更新数组(以字节为单位)
0X08 8 日志文件序列号
0X10 8 本索引缓存在索引分配中的VCN
0X18 4 索引项的偏移(以字节为单位,相对0x18偏移到索引项)
0X1C 4 总的索引项的大小(相对0X18偏移到结尾)
0X20 4 索引项分配大小
0X24 1 如果不是叶节点,置1,表示还有子节点
0X25 3 用0填充
0X28 2 更新序列(重要:与每扇区最后2个字节的值一致)
0X2A 2S-1 更新序列数组
我们根据表自己定义一个结构:
type
R_Wu_INDEXDataHead=packed record //MFT索引分配属性结构($A0)
IndexID:array[0..3] of char; //INDEX标志'INDX'[0]
NewIDOffset:word; //更新序列号的偏移,指向更新序列的位置(重要)[4]
NewID_Temp:word; //更新序列号与更新数组[6]
LogFileID:int64; //日志文件序列号[8]
IndexVCN:int64; //本索引缓存在索引分配中的VCN[10]
IndexListOffset:cardinal; //索引项的偏移(以字节为单位,相对0x18偏移到索引项)(重要)[18]
AllIndexListSizeOffset:cardinal;//总的索引项的大小(相对0X18偏移到结尾)[1C]
IndexSize:cardinal; //索引项分配大小[20]
IsYeJieDian:byte; //如果不是叶节点,置1,表示还有子节点[24]
Fill:array[0..2] of byte; //填充[25]
INDEXusesID:word; //INDEX的使用标记(重要,它在INDEX记录的各个扇区中与每扇区的最末两个字节相对应,如若不然,系统将示此记录为非法记录)[28]
//[2A]
INDEXusesTemp:array of word; //INDEX的使用标记数组(重要,上面由于INDEX记录扇区将每个扇区中最后的两个字节占用掉了,所以在这里补回来)[2A]
end;
*******************************************
*******************************************
位图属性($B0)
$BITMAP:当启用索引分配属性($A0)的时候,这个属性跟随启用.
索引位图属性(0XB0),用于描述索引分配属性使用的虚拟簇号。
但是具体的作用,与数据结构尝未了解.
*******************************************
好了,至此我们已经差不多把数据结构都定义好了.可以进入MFT的解析.程序部分
解析NTFS关键就是对MFT的解析,MFT里面包含的属性解析.当然MFT的属性种类太多,以我现在的水平来说要做到全部解释是不可能的事,所以挑选对我们很重要的属性来解析.要获得磁盘分区的文件系统,就必须解出目录与目录或文件之间的结构,是怎样组织起来的.
我们要能读出目录类型的MFT中下一级所有目录及文件.
对于文件类型的MFT,我们要能读出文件的内容.
当然目录或文件它们的名字我们也是要解析出来的.
如果我们能做到以上三点,基本上可以完成当初定下的目标4及目标1的一大部分了.
现在开始,在开始解析之前我们还得定义一个结构,这个结构用来存放对MFT解析的信息:
type
Wu_DataRun_type=record //定义数据运行
T:int64; //指定簇
Len:int64; //连续的长度
end;
type
Wu_Dir_type=record //定义下一级目录和文件的记录
MFTID:int64; //MFT号
IsDir:boolean; //这个是目录还是文件
FileSize:int64; //文件大小
FileName:string; //目录或文件的名字
end;
type
Wu_NTFS_LookMFTData=record //定义这个结构来存放解析MFT的详细信息
//公有属性
MFTOK:boolean; //当前MFT是否有效(通过入口获得)
MFTID:int64; //当前MFT号(开始时设置)
FMFTID:int64; //它的父目录的MFT号(通过$30获得)
IsDir:boolean; //这个是目录还是文件(通过入口获得,true目录,false文件)
FileName:string; //目录或文件的名字(通过$30获得)
//文件专有属性
FileSize:int64; //文件大小(通过$30获得)
DataRunSize:integer; //运行总数(通过$80获得,非常驻)
DataRun:array of Wu_DataRun_type;//记录经过解析的文件运行(通过$80获得,非常驻)
Data:string; //没有运行的文件的文件内容(通过$80获得,常驻)
//目录专有属性
DirListSize:integer;
DirList:array of Wu_Dir_type;//所有下一级目录或文件列表(通过$90,$A0获得)
IndexRunSize:integer; //$A0属性的列表运行总数(通过$A0获得)
IndexRun:array of Wu_DataRun_type;//记录经过解析的文件运行(通过$A0获得)
//其它未解析的属性
Attrib_SaveSize:integer; //属性总数
Attrib_Save:array[0..19] of string; //属性的列表
end;
type
P_Wu_NTFS_LookMFTData=^Wu_NTFS_LookMFTData;
2.首先我们得定义一个全局变量(本人很喜欢用全局而不临时建内存)用来读入一个MFT的内容,这样的好处是解析的时候不再需要频繁地读扇区操作.
var
ReadMFT_Save:array[0..1032] of byte; //这里默认MFT长度就是1024字节吧,因为为了防止后面复制R_Wu_MFT1时溢出,故此多加9个
3.现在开始对MFT解析,首先是入口
procedure Wu_Look_MFT_1(Read_MFT_Save:Pchar;Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;T:int64);
var
P:Pchar;
begin
//读入MFT全内容
Wu_ReadDiskSector(MFTBegin+T*MFTRecordSize,P); //读入第t个MFT的第一个基本扇区(512字节)
Wu_Copy(P,1,512,Read_MFT_Save,1);
Wu_ReadDiskSector(MFTBegin+T*MFTRecordSize+1,P);//读入第t个MFT的第二个基本扇区(512字节)
Wu_Copy(P,1,512,Read_MFT_Save,513);
Wu_Look_MFT(Read_MFT_Save,Read_MFTAttrib_Save,T);
end;
procedure Wu_Look_MFT(Read_MFT_Save:Pchar;Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;T:int64); //查看一个MFT,T为第t个MFT
var
N:cardinal;
MFTHead:R_Wu_MFT1;
MFTattribHead:R_Wu_MFTATTRIBUTEHead;
S:string;
begin
//初始化
Read_MFTAttrib_Save.MFTOK:=false;
Read_MFTAttrib_Save.MFTID:=T;
Read_MFTAttrib_Save.FMFTID:=0;
Read_MFTAttrib_Save.FileSize:=0;
Read_MFTAttrib_Save.DataRunSize:=0;
Read_MFTAttrib_Save.DataRunSize:=0;
Read_MFTAttrib_Save.DirListSize:=0;
Read_MFTAttrib_Save.Attrib_SaveSize:=0;
Read_MFTAttrib_Save.IndexRunSize:=0;
//将头数据读出来
Wu_Copy(Read_MFT_Save,1,50,@MFTHead,1);
setlength(MFTHead.MFTUsesTemp,2);
Wu_Copy(Read_MFT_Save,MFTHead.usFixupOffset+3,2,@MFTHead.MFTUsesTemp[0],1);//将第一个扇区使用标记所占用掉的word读出来
Wu_Copy(Read_MFT_Save,MFTHead.usFixupOffset+5,2,@MFTHead.MFTUsesTemp[1],1);//将第二个扇区使用标记所占用掉的word读出来
//然后将有用的数据换掉使用标记所占用的无用字
Wu_Copy(@MFTHead.MFTUsesTemp[0],1,2,Read_MFT_Save,$1FF);
Wu_Copy(@MFTHead.MFTUsesTemp[1],1,2,Read_MFT_Save,$3FF);
//获得一些属性
if MFTHead.wResident=1 then //这是一个文件
begin
Read_MFTAttrib_Save.IsDir:=false;
Read_MFTAttrib_Save.MFTOK:=true;
end
else
if MFTHead.wResident=3 then //这是一个目录
begin
Read_MFTAttrib_Save.IsDir:=true;
Read_MFTAttrib_Save.MFTOK:=true;
end
else
begin
exit; //不是有效文件的MFT,跳出
end;
//这时才可以开始解析MFT属性
N:=MFTHead.usAttrOffset+1;
repeat
Wu_Copy(Read_MFT_Save,N,9,@MFTattribHead,1); //将头复制进结构变量
if MFTattribHead.Attribute<>$FFFFFFFF then
begin
//保存属性内容
setlength(S,MFTattribHead.AllLen); //设置S的长度,用来存放这个属性所有内容
Wu_Copy(Read_MFT_Save,N,MFTattribHead.AllLen,Pchar(S),1); //复制过去
//添加一项属性记录
setlength(Read_MFTAttrib_Save.Attrib_Save[Read_MFTAttrib_Save.Attrib_SaveSize],MFTattribHead.AllLen);
Wu_Copy(Read_MFT_Save,N,MFTattribHead.AllLen,Pchar(Read_MFTAttrib_Save.Attrib_Save[Read_MFTAttrib_Save.Attrib_SaveSize]),1); //复制过去
Read_MFTAttrib_Save.Attrib_SaveSize:=Read_MFTAttrib_Save.Attrib_SaveSize+1; //属性记录列表加1
end;
//解析部分属性
Case MFTattribHead.Attribute of
$30:
begin
Wu_Look_MFTAttrib_30(Read_MFTAttrib_Save,S); //文件名属性分析
end;
$80:
begin
if MFTattribHead.ResidentSymbol=0 then
Wu_Look_MFTAttrib_80_1(Read_MFTAttrib_Save,S) //文件数据属性分析
else
Wu_Look_MFTAttrib_80_2(Read_MFTAttrib_Save,S); //文件数据属性分析
end;
$90:
begin
Wu_Look_MFTAttrib_90(Read_MFTAttrib_Save,S); //目录列表属性分析
end;
$A0:
begin
Wu_Look_MFTAttrib_A0(Read_MFTAttrib_Save,S); //目录列表属性分析
end;
end;
N:=N+MFTattribHead.AllLen;
until MFTattribHead.Attribute=$FFFFFFFF;
end;
4.上面可以看作对MFT的第一步解析,先是读入MFT表中指定一个MFT的全部数据,然后将它的头解出来,还有最重要的是将"NTFS把每一个基本扇区的后两字改掉"改回来.
然后才开始解析MFT的每一项属性,再将解出的属性交给另一些专门处理的函数来处理.
5.下面写各个属性的处理,首先是文件名属性
procedure Wu_Look_MFTAttrib_30(Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;S:string); //文件名属性分析
var
FILENAMEATTRIBUTE:R_Wu_FILENAMEATTRIBUTE;
UnicodeFileName:string;
begin
Wu_Copy(Pchar(S),1,90,@FILENAMEATTRIBUTE,1); //复制进属性
Read_MFTAttrib_Save.FMFTID:=0;
Wu_Copy(@FILENAMEATTRIBUTE.FileConferenceNumOfFartherDir,1,6,@Read_MFTAttrib_Save.FMFTID,1); //复制获得父目录的MFT号
if Read_MFTAttrib_Save.IsDir=false then //如果这是一个文件
begin
Read_MFTAttrib_Save.FileSize:=FILENAMEATTRIBUTE.FileRealSize; //文件长度
end;
setlength(UnicodeFileName,FILENAMEATTRIBUTE.FileNameLen*2);
Wu_Copy(Pchar(S),91,FILENAMEATTRIBUTE.FileNameLen*2,Pchar(UnicodeFileName),1);
Read_MFTAttrib_Save.FileName:=Wu_UnicodeToString(UnicodeFileName); //文件名
end;
对于文件名,我们还需要一个将Unicode字符串转字符串的函数,自己写一个
function Wu_UnicodeToString(S:string):string; //Unicode字符转String
var
S1:PwideChar;
P:Pointer;
begin
P:=Pchar(S);
S1:=P;
result:=OleStrtoString(S1);
end;
6.然后是文件数据属性分析,这里我们会遇到两种情况,一种是小文件数据存放在MFT中,称之为常驻
procedure Wu_Look_MFTAttrib_80_1(Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;S:string); //文件数据属性分析
var
Wu_DATAATTRIBUTE:R_Wu_DATAATTRIBUTE_1;
begin
Wu_Copy(Pchar(S),1,24,@Wu_DATAATTRIBUTE,1); //复制进属性
setlength(Read_MFTAttrib_Save.Data,Wu_DATAATTRIBUTE.DataLen); //设置数据空间
Wu_Copy(Pchar(S),Wu_DATAATTRIBUTE.DataOffset+1,Wu_DATAATTRIBUTE.DataLen,Pchar(Read_MFTAttrib_Save.Data),1); //复制进去
end;
另外一种是大文件,它放到磁盘空间里去,称之为非常驻,我们就要解析出它的数据运行
procedure Wu_Look_MFTAttrib_80_2(Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;S:string); //文件数据属性分析
var
DATAATTRIBUTE:R_Wu_DATAATTRIBUTE_2;
RunS:string;
T:integer;
RunT,RunL:int64;
RunB1,RunB2,RunBT1,RunBT2:byte;
LCN:int64;
label
RunBeginL,RunEndL;
begin
Wu_Copy(Pchar(S),1,64,@DATAATTRIBUTE,1); //复制进属性
setlength(RunS,DATAATTRIBUTE.AllLen-64);
Wu_Copy(Pchar(S),DATAATTRIBUTE.RunOffset+1,DATAATTRIBUTE.AllLen-64,Pchar(RunS),1); //得到数据运行的内容
T:=1;
LCN:=0;
RunBeginL:
RunT:=0;
RunL:=0;
Wu_Copy(Pchar(RunS),T,1,@RunBT1,1);
if RunBT1=0 then
goto RunEndL;
RunBT2:=RunBT1 shr 4;
RunB1:=RunBT2;
RunBT2:=RunBT1 shl 4;
RunBT1:=0;
RunBT1:=RunBT2 shr 4;
RunB2:=RunBT1;
Wu_Copy(Pchar(RunS),T+1,RunB2,@RunL,1);
Wu_Copy(Pchar(RunS),T+1+RunB2,RunB1,@RunT,1);
LCN:=LCN+Wu_VcnToL(RunT,RunB1);
SetLength(Read_MFTAttrib_Save.DataRun,Read_MFTAttrib_Save.DataRunSize+1);
Read_MFTAttrib_Save.DataRun[Read_MFTAttrib_Save.DataRunSize].T:=LCN;
Read_MFTAttrib_Save.DataRun[Read_MFTAttrib_Save.DataRunSize].Len:=RunL;
Read_MFTAttrib_Save.DataRunSize:=Read_MFTAttrib_Save.DataRunSize+1;
T:=T+RunB1+RunB2+1;
goto RunBeginL;
RunEndL:
end;
对了,我们还得先写上一个VCN地址转换
function Wu_VcnToL(I:int64;L:byte):int64;
var
K1,K2,K3,K4:int64;
begin
K1:=$FFFFFFFF;
K2:=K1 shr (32-(L*8));
K3:=I;
K4:=I shr ((L*8)-1);
if K4=1 then
begin
K3:=K2-I+1;
K3:=0-K3;
end;
result:=K3;
end;
7.然后写文件目录解析
procedure Wu_Look_MFTAttrib_90(Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;S:string); //目录列表属性分析
var
INDEXATTRIBUTE:R_Wu_INDEXATTRIBUTE;
INDEXDATA:R_Wu_INDEXDATA;
IndexS,IDataS,UnicodeFileName:string;
MFTnum:int64;
T:cardinal;
label
INDEXBeginL,INDEXEndL;
begin
Wu_Copy(Pchar(S),1,64,@INDEXATTRIBUTE,1); //复制进属性
SetLength(IndexS,INDEXATTRIBUTE.AllLen-64);
Wu_Copy(Pchar(S),65,INDEXATTRIBUTE.AllLen-64,Pchar(IndexS),1); //得到数据运行的内容
T:=1;
INDEXBeginL:
if T+82>INDEXATTRIBUTE.AllLen-64 then //如果超出属性长度
goto INDEXEndL; //跳到解项结束
Wu_Copy(Pchar(IndexS),T,82,@INDEXDATA,1);
SetLength(IDataS,INDEXDATA.IndexLen);
Wu_Copy(Pchar(IndexS),T,INDEXDATA.IndexLen,Pchar(IDataS),1); //获得每一个项
SetLength(UnicodeFileName,INDEXDATA.FileNameLen*2); //设置文件名空间
Wu_Copy(Pchar(IDataS),83,INDEXDATA.FileNameLen*2,Pchar(UnicodeFileName),1); //获得文件名
SetLength(Read_MFTAttrib_Save.DirList,Read_MFTAttrib_Save.DirListSize+1);
Read_MFTAttrib_Save.DirList[Read_MFTAttrib_Save.DirListSize].FileName:=Wu_UnicodeToString(UnicodeFileName);
MFTnum:=0;
Wu_Copy(@INDEXDATA.MFTnum,1,5,@MFTnum,1);
Read_MFTAttrib_Save.DirList[Read_MFTAttrib_Save.DirListSize].MFTID:=MFTnum;
Read_MFTAttrib_Save.DirList[Read_MFTAttrib_Save.DirListSize].FileSize:=INDEXDATA.FileSize;
Read_MFTAttrib_Save.DirList[Read_MFTAttrib_Save.DirListSize].IsDir:=INDEXDATA.FileSign and $10000000=$10000000;
Read_MFTAttrib_Save.DirListSize:=Read_MFTAttrib_Save.DirListSize+1;
T:=T+INDEXDATA.IndexLen;
goto INDEXBeginL;
INDEXEndL:
end;
8.还有一种情况是目录下一级的目录或文件很多,MFT中存放不下,那么这时就会放到磁盘中,以数据运行形式
procedure Wu_Look_MFTAttrib_A0(Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;S:string); //目录列表属性分析
var
INDEXListATTRIBUTE:R_Wu_INDEXListATTRIBUTE;
RunS,IndexS:string;
T:integer;
RunT,RunL:int64;
RunB1,RunB2,RunBT1,RunBT2:byte;
LCN:int64;
N,n1:integer;
P:Pchar;
label
RunBeginL,RunEndL;
begin
Wu_Copy(Pchar(S),1,64,@INDEXListATTRIBUTE,1); //复制进属性
setlength(RunS,INDEXListATTRIBUTE.AllLen-64);
Wu_Copy(Pchar(S),INDEXListATTRIBUTE.RunOffset+1,INDEXListATTRIBUTE.AllLen-64,Pchar(RunS),1); //得到数据运行的内容
T:=1;
LCN:=0;
RunBeginL:
RunT:=0;
RunL:=0;
Wu_Copy(Pchar(RunS),T,1,@RunBT1,1);
if RunBT1=0 then
goto RunEndL;
RunBT2:=RunBT1 shr 4;
RunB1:=RunBT2;
RunBT2:=RunBT1 shl 4;
RunBT1:=0;
RunBT1:=RunBT2 shr 4;
RunB2:=RunBT1;
Wu_Copy(Pchar(RunS),T+1,RunB2,@RunL,1);
Wu_Copy(Pchar(RunS),T+1+RunB2,RunB1,@RunT,1);
LCN:=LCN+Wu_VcnToL(RunT,RunB1);
SetLength(Read_MFTAttrib_Save.IndexRun,Read_MFTAttrib_Save.IndexRunSize+1);
Read_MFTAttrib_Save.IndexRun[Read_MFTAttrib_Save.IndexRunSize].T:=LCN;
Read_MFTAttrib_Save.IndexRun[Read_MFTAttrib_Save.IndexRunSize].Len:=RunL;
Read_MFTAttrib_Save.IndexRunSize:=Read_MFTAttrib_Save.IndexRunSize+1;
T:=T+RunB1+RunB2+1;
goto RunBeginL;
RunEndL:
for n:=0 to Read_MFTAttrib_Save.IndexRunSize-1 do //每一个运行解析
begin
setlength(IndexS,IndexListSize*512); //定义一个Index长度
for n1:=0 to IndexListSize-1 do //(这里还未太清楚是否存在几个INDEX连在一起用一个RUN来表未的状态)
begin
Wu_ReadDiskSector(Read_MFTAttrib_Save.IndexRun[n].T*ClusterSize+n1,P);
Wu_Copy(P,1,512,Pchar(IndexS),n1*512+1);
end;
Wu_Look_IndexList(Read_MFTAttrib_Save,Pchar(IndexS),IndexListSize*512); //解析Index
end;
end;
上面获得运行后还得解析,就是对列表区的解析
procedure Wu_Look_IndexList(Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;IndexBuf:Pchar;IndexSLen:integer); //文件数据属性分析
var
INDEXDataHead:R_Wu_INDEXDataHead;
INDEXDATA:R_Wu_INDEXDATA;
IDataS:string;
UnicodeFileName:string;
T,L,n:integer;
MFTnum:int64;
label
INDEXBeginL,INDEXEndL;
begin
Wu_Copy(IndexBuf,1,42,@INDEXDataHead,1); //获取头
T:=INDEXDataHead.IndexListOffset+$18+1; //索引项起始位置
L:=INDEXDataHead.AllIndexListSizeOffset+$18+1; //索引项结束位置
setlength(INDEXDataHead.INDEXusesTemp,IndexListSize);
for n:=0 to IndexListSize-1 do
begin
Wu_Copy(IndexBuf,INDEXDataHead.NewIDOffset+n*2+3,2,@INDEXDataHead.INDEXusesTemp[n],1);
Wu_Copy(@INDEXDataHead.INDEXusesTemp[n],1,2,IndexBuf,n*512+511);//补回被替换的字
end;
INDEXBeginL:
if T+82>L then //如果超出属性长度
goto INDEXEndL; //跳到解项结束
Wu_Copy(IndexBuf,T,82,@INDEXDATA,1);
SetLength(IDataS,INDEXDATA.IndexLen);
Wu_Copy(IndexBuf,T,INDEXDATA.IndexLen,Pchar(IDataS),1); //获得每一个项
SetLength(UnicodeFileName,INDEXDATA.FileNameLen*2); //设置文件名空间
Wu_Copy(Pchar(IDataS),83,INDEXDATA.FileNameLen*2,Pchar(UnicodeFileName),1); //获得文件名
SetLength(Read_MFTAttrib_Save.DirList,Read_MFTAttrib_Save.DirListSize+1);
Read_MFTAttrib_Save.DirList[Read_MFTAttrib_Save.DirListSize].FileName:=Wu_UnicodeToString(UnicodeFileName);
MFTnum:=0;
Wu_Copy(@INDEXDATA.MFTnum,1,5,@MFTnum,1);
Read_MFTAttrib_Save.DirList[Read_MFTAttrib_Save.DirListSize].MFTID:=MFTnum;
Read_MFTAttrib_Save.DirList[Read_MFTAttrib_Save.DirListSize].FileSize:=INDEXDATA.FileSize;
Read_MFTAttrib_Save.DirList[Read_MFTAttrib_Save.DirListSize].IsDir:=INDEXDATA.FileSign and $10000000=$10000000;
Read_MFTAttrib_Save.DirListSize:=Read_MFTAttrib_Save.DirListSize+1;
T:=T+INDEXDATA.IndexLen;
goto INDEXBeginL;
INDEXEndL:
end;
至此,我们已经可以对一个MFT进行简单的解析了!
var
ReadMFTAttrib_Save:Wu_NTFS_LookMFTData; //定义一个全局变量
然后程序中试试解析一下.
Wu_Look_MFT_1(@ReadMFT_Save,@ReadMFTAttrib_Save,5);
运行成功,解得!
到前面为止,我们似乎已经成功地能够对指定第几个MFT解析.
但现在明确地告诉各位,我们还犯了一个错误.在最初我们学习NTFS文件系统的时候,有一句说话"NTFS把一切当作文件,MFT本身也是文件".
我们一直都把MFT列表当作是一个所有MFT项紧密连接成的一个数据空间.但事实并不是这样的,以下是摘抄网上的话:
(7)理论上$MFT在卷中的分配空间(占12%)。
(8)逻辑上,$MFT在卷中会占用一块连续的空间,但实际情况$MFT可能会被分散在磁盘的几个不同的区域。甚至,可能在元文件的部分就被拆分开。据笔者分析,这些情况的发生可能由于卷上的文件不断增加,最先开辟的$MFT文件已经用完,系统会再次开辟空间存放文件记录。另一种情况是,卷是由FAT或者其他格式转化而来,当卷空间不足的时候,也可能将MFT分散存储。
这里就说明了整个MFT列表并不一定是连续在一起的,会有一部分MFT列表,之后接着用户的文件数据,之后又接另一部分MFT列表这样的情况发生.
那现在我们该怎么办呢?如果出现这种情况,那么它的规则又是怎样的呢?一定有它的规则,系统不会乱放,如果乱放了它自己也找不着.
那么这个规则就存在于第0号MFT记录当中,0号记录的文件名正是$MFT,我们当初通过引导扇区的一个指针找到了它,而它不仅仅代表MFT的开始位置,最重要的是这个位置上的MFT记录就是对自身列表的一个描述.
一切就开朗了,列表本身就是一个文件,它对它自己数据存储位置的描述,而它的数据就是MFT列表.
其实大家详细了解一下开始几个MFT记录,就会明白,不单列表本身当作文件,而且连引导扇区都当作一个文件来处理了.
那么就是说我们上面写的Wu_Look_MFT_1是不成立的了
procedure Wu_Look_MFT_1(Read_MFT_Save:Pchar;Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;T:int64);
var
P:Pchar;
begin
//读入MFT全内容
Wu_ReadDiskSector(MFTBegin+T*MFTRecordSize,P); //读入第t个MFT的第一个基本扇区(512字节)
Wu_Copy(P,1,512,Read_MFT_Save,1);
Wu_ReadDiskSector(MFTBegin+T*MFTRecordSize+1,P);//读入第t个MFT的第二个基本扇区(512字节)
Wu_Copy(P,1,512,Read_MFT_Save,513);
Wu_Look_MFT(Read_MFT_Save,Read_MFTAttrib_Save,T);
end;
这里没有考虑到MFT列表可能不连续这个问题,
那么我们该怎么写呢?首先,我们得分析$MFT这个文件
var
MFT0_MFTAttrib_Save:Wu_NTFS_LookMFTData;//定义一个全局变量用来存放0号MFT分析结果($MFT)
MFT0_Buf_Save:array[0..1023] of byte;
procedure Wu_Look_MFT0();
var
P:Pchar;
begin
//读入MFT全内容
Wu_ReadDiskSector(MFTBegin,P); //MFT的第一个基本扇区(512字节)
Wu_Copy(P,1,512,MFT0_Buf_Save,1);
Wu_ReadDiskSector(MFTBegin+1,P);//MFT的第二个基本扇区(512字节)
Wu_Copy(P,1,512,MFT0_Buf_Save,513);
Wu_Look_MFT(MFT0_Buf_Save,MFT0_MFTAttrib_Save,T);
end;
上面解析出$MFT后,得到MFT0_MFTAttrib_Save中有关于$MFT数据运行
现在我们改写Wu_Look_MFT_1
procedure Wu_Look_MFT_1(Read_MFT_Save:Pchar;Read_MFTAttrib_Save:P_Wu_NTFS_LookMFTData;T:int64);
var
P:Pchar;
offset_T,L1,L2,L3:cardinal;
n,nsave:integer;
OK:boolean;
begin
//添加记算正确的扇区号
L1:=T*MFTRecordSize; //第T个记录前面所占用的扇区数
L2:=0;
L3:=L1;
nsave:=0;
OK:=false; //默认第T个记录位置未解出
for n:=0 to MFT0_MFTAttrib_Save.DataRunSize-1 do //在所有运行数据中找
begin
L2:=L2+MFT0_MFTAttrib_Save.DataRun[n].Len*ClusterSize;
if L1<L2 then
begin
OK:=true; //解出,正确的MFT号
nsave:=n;
break;
end;
L3:=L3-MFT0_MFTAttrib_Save.DataRun[n].Len*ClusterSize;
end;
if OK then
begin
offset_T:=MFT0_MFTAttrib_Save.DataRun[nsave].T*ClusterSize+L3;
end
else
begin
exit; //不是正确的MFT号,因为它超出了MFT列表
end;
//读入MFT全内容
Wu_ReadDiskSector(offset_T,P); //读入第t个MFT的第一个基本扇区(512字节)
Wu_Copy(P,1,512,Read_MFT_Save,1);
Wu_ReadDiskSector(offset_T+1,P);//读入第t个MFT的第二个基本扇区(512字节)
Wu_Copy(P,1,512,Read_MFT_Save,513);
Wu_Look_MFT(Read_MFT_Save,Read_MFTAttrib_Save,T);
end;