上节地址:http://blog.csdn.net/A00553344/archive/2009/12/18/5031993.aspx
7. 主文件列表(MFT)记录详解
了解了NTFS的基本结构后,现在来详细分析MFT表内的记录,它是NTFS格式存储的关键内容。
“常驻属性”和“非常驻属性”
前面我们知道,磁盘上的每一个文件(目录)对应MFT表内的一条记录,记录的大小是1KB,记录的内容是文件的所有属性,包括文件本身的数据也当成属性来存储。那么问题来了,对于很多文件来说,本身的数据就大于1KB,那么记录是怎么存储的呢?NTFS系统是这么规定的,当文件属性太大的时候(比如文件的数据属性),NTFS系统会将该属性值存放到卷中某个位置,而MFT记录的内容只会记录该属性索引到外部的地址,以及索引区的大小。这种属性被称为文件的“非常驻属性”。与之相反,能够保存在FMT内的的属性叫做“常驻属性”。
下表是MFT记录的属性列表,每一个MFT记录只包含其中的一部分属性,不会包含所有的属性。
属性号 | 属性名 | 属性描述 |
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个字节对卷来说是唯一的 |
读到这里,我们来实际编码读点数据来加深印象。
第一步:读入引导扇区
定义一个引导区的结构
Type
PBOOT_SEQUENCE = TBOOT_SEQUENCE^;
TBOOT_SEQUENCE = packed record // 引导扇区数据结构
_jmpcode : array[1..3] of Byte;
cOEMID: array[1..8] of Char;
wBytesPerSector: Word;
bSectorsPerCluster: Byte;
wSectorsReservedAtBegin: Word;
Mbz1: Byte;
Mbz2: Word;
Reserved1: Word;
bMediaDescriptor: Byte;
Mbz3: Word;
wSectorsPerTrack: Word;
wSides: Word;
dwSpecialHiddenSectors: DWord;
Reserved2: DWord;
Reserved3: DWord;
TotalSectors: Int64;
MftStartLcn: Int64;
Mft2StartLcn: Int64;
ClustersPerFileRecord: DWord;
ClustersPerIndexBlock: DWord;
VolumeSerialNumber: Int64;
_loadercode: array[1..430] of Byte;
wSignature: Word;
end;
procedure SaveDebugInfo(var buffer; bufSize: Cardinal; filename: string);
var
debugF : TFileStream;
begin
debugF := TFileStream.Create(filename,fmCreate);
try
debugF.Write(buffer,bufSize);
finally
debugF.Free;
end;
end;
procedure Button1Click(Sender: Tobject);
var
hDevice : THANDLE;
PbootSequence : PBOOT_SEQUENCE;
dwRead : Cardinal;
begin
hDevice := CreateFile( PChar('//./E'),
GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (hDevice = INVALID_HANDLE_VALUE) then
begin
Showmessage('无法读写磁盘!');
CloseHandle(hDevice);
exit;
end;
New(PBootSequence);
ZeroMemory(PBootSequence, SizeOf(TBOOT_SEQUENCE));
SetFilePointer(hDevice, 0, nil, FILE_BEGIN);
ReadFile(hDevice,PBootSequence^, 512,dwread,nil);
SaveDebugInfo(PBootSequence^,sizeof(TBOOT_SEQUENCE),'boot_sector.bin');
Dispose(PBootSequence);
Closehandle(hDevice);
End;
读入的引导区为下图:
每扇区字节数(红色):0x0200
每簇扇区数(蓝色): 0x08
$MFT的逻辑簇号(黄色): 0x0000000000000004
每MFT记录簇数(绿色): 0x000000F6
第二步 读入MFT
我们知道MFT的第一条记录是本身。现在已经知道了
1. 每扇区字节数(红色):0x0200
2. 每簇扇区数(蓝色): 0x08
3. $MFT的逻辑簇号(黄色): 0x0000000000000004
4. 每MFT记录簇数(绿色): 0x000000F6
可以计算出$MFT的位置和MFT中每条记录的大小(通常是1KB):
$MFT位置(偏移量) := $MFT的逻辑簇号 *每簇扇区数 *每扇区字节数
每MFT记录簇数不能直接拿来计算每条记录的大小,要看它是否小于0x80,计算公式如下:
if (每MFT记录簇数 < $80) then
每MFT记录字节数 := 每MFT记录簇数*每簇字节数;
else
每MFT记录字节数 := 1 shl ($100 -每MFT记录簇数)
我们现在获得$MFT的偏移动位置及$MFT中每条记录的大小,这样我们就可以读出$MFT中的第一条记录,即对应$MFT文件自身的那条记录。
代码:
SetLength(存放数据的缓冲区, 每MFT记录字节数);
SetFilePointer(hDevice, Int64Rec($MFT位置).Lo,
@Int64Rec($MFT位置).Hi, FILE_BEGIN);
Readfile(hDevice, PChar(存放数据的缓冲区)^, 每MFT记录字节数, dwread, nil);
SaveDebugInfo(存放数据的缓冲区, 每MFT记录字节数,'MFT.bin');
读到的对应$FMT文件的记录为:
-
红色部分: 记录属性头,所有的MFT记录都有这一部分.
-
绿色部分: $STANDRD_INFORMATION(标准属性) 包括基本文件属性,如只读、存档;时间标记,如文件的创建时间和最近一次修改的时间;有多少目录指向本文件
-
蓝色部分: $FILE_NAME(文件名属性) 这是以Unicode字符表示的,由于MS-DOS不能正确识别Win32子系统创建的文件名,当Win32子系统创建一个文件名时,MTFS会自动生成一个备用的MS-DOS文件名,所以一个文件可以有多种文件名属性。
-
灰色部分: $DATA(数据属性) 这是文件的内容
-
紫色部分: $BITMAP(位图属性) 位图
-
最后的FF FF FF FF 00 00 00 00 为一条MFT记录的结束标志.