前言
windows操作系统中常见的一个文件存储系统是NTFS。在这个文件系统中MFT是它的核心。
图一
MFT是一个数据结构,上图是它的结构,它主要用来存放每个文件和目录在磁盘中的索引。MFT由一个个MFT项构成。每个MFT项的大小是1024Bytes。每个MFT项由固定头结构和多个属性构成。属性用来记录与文件相关的信息。每个属性又由属性头和属性体构成。属性头包含了一些该属性的重要信息,如属性类型、属性大小、是否为常驻属性等等。[1]文件内容也被NTFS看作属性。
其中,属性分为常驻属性和非常驻属性。如果一个属性的属性体都可以存储在MFT项中,称该属性是常驻属性。如果1024Bytes的MFT项无法记录该属性的所有属性体内容,称该属性是非常驻属性。
非常驻属性的属性体里存储的不是文件真正的内容,而是文件内容的起始簇号和簇号个数(文件内容存放在MFT外部)。前者方便找到文件在磁盘的位置,后者方便确定文件的大小。这两个信息称为簇流信息。有时候一个文件的内容被存储在多个簇流中,系统就用多个簇流信息来表示文件各部分内容的位置及大小。其中,除了文件的第一个簇流信息的起始簇号代表真正簇号,后面的每个簇流信息中的起始簇号记录的值是前一个起始簇号的相对位置。[2]
如果我们需要读磁盘上的某个文件,就可以这样操作——从MFT项里获取文件各部分内容的簇流信息,从中获得每个簇流的起始簇号,再用读磁盘的方式打开一个读磁盘句柄,将文件指针依次定位到各簇流的起始簇号位置,依次读出每个簇流内容,最后汇总起来就是该文件的所有内容啦。带着这个方案,使用DeviceIOControl()函数,我们就可以获得MFT项里的簇流信息。这个函数重点需要两个结构体,它们定义在<WinIoCtl.h>头文件里:
typedef struct {
LARGE_INTEGER StartingVcn;
} STARTING_VCN_INPUT_BUFFER, *PSTARTING_VCN_INPUT_BUFFER; typedef struct RETRIEVAL_POINTERS_BUFFER {
DWORD ExtentCount;
LARGE_INTEGER StartingVcn;
struct {
LARGE_INTEGER NextVcn;
LARGE_INTEGER Lcn;
} Extents[];
} RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER;
其中,LCN是逻辑簇号,即这个簇在磁盘中的序号;VCN是虚拟簇号,即这个簇在所属文件所有簇中的序号。第一个结构体中的StartingVcn是文件的起始VCN号,一般值为0。第二个结构体中的ExtentCount表示簇流的数量;StartingVcn表示第一个簇流的起始VCN簇号,一般为0;Extent结构体存放两个与簇流相关的信息,其中NextVcn表示下一个簇流的起始Vcn簇号;Lcn表示该簇流的逻辑簇号。有多少簇流我们就给第二个结构体分配多少个Extent结构体,虽然第二个结构体在定义时只有一个Extent结构体,但是未来给第二个结构体分配堆空间时,可以按照簇流数量来分配Extent结构体。
图二
假设一个文件有三个簇流,则如上图所示。某个簇流的NextVcn减去上一个簇流的NextVcn就可以得到该簇流有多少个簇,每个簇大小可以用GetDiskFreeSpace()函数计算出来,并且一个簇流中的簇的Lcn是顺序累加的。这样我们从该簇流的起始Lcn开始,循环累加Lcn,每次读出簇大小的内容,直到读完该簇流。一个簇流的内容就获得了。然后根据下一个簇流的Lcn,执行相同的操作。直到把整个文件内容读完。
代码
typedef unsigned long ULONG;
typedef unsigned long long ULONGLONG;
void ReadDisk(char *pFilePath, char *pDrive, char *pDriveFile)
{
//--start -get bytes in one cluster [gbioc]
ULONG sectorsPerCluster, bytesPerSectors, ClusterSize;
GetDiskFreeSpace(pDrive, §orsPerCluster, &bytesPerSectors, NULL, NULL);
ClusterSize = sectorsPerCluster * bytesPerSectors;
//--end [gbioc] HANDLE hFile = CreateFile(pFilePath, FILE_READ_ATTRIBUTES, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, , NULL); //--start -get file size [gfz]
ULONGLONG fileSize;
BY_HANDLE_FILE_INFORMATION fileInfo;
GetFileInformationByHandle(hFile, &fileInfo);
fileSize = fileInfo.nFileSizeLow + ((ULONGLONG)fileInfo.nFileSizeHigh << );
//--end [gfz] ULONG clusterCount = (fileSize + ClusterSize - ) / ClusterSize; //--start -get file's cluster info in disk [gfciid]
STARTING_VCN_INPUT_BUFFER vcnBuf;
PRETRIEVAL_POINTERS_BUFFER pOutBuf;
ULONG outBufSize, readBytes;
outBufSize = (ULONG)&(((PRETRIEVAL_POINTERS_BUFFER))->Extents) + clusterCount * sizeof(pOutBuf -> Extents); //consider that each cluster is in different extent
pOutBuf = (PRETRIEVAL_POINTERS_BUFFER)malloc(outBufSize * sizeof(char));
memset(pOutBuf, 0x0, ); vcnBuf.StartingVcn.QuadPart = ;
DeviceIoControl(hFile, FSCTL_GET_RETRIEVAL_POINTERS, &vcnBuf, sizeof(vcnBuf), pOutBuf, outBufSize*sizeof(char), &readBytes, NULL); //①.
//--end [gfciid] ULONG extentCount = pOutBuf -> ExtentCount; //--start -put each cluster of file into array [pecofia]
ULONG *Clusters = (ULONG *)malloc(sizeof(ULONG) * clusterCount);
LARGE_INTEGER curLcn;
LARGE_INTEGER preVcn = pOutBuf -> StartingVcn;
ULONG extentIndx, clusterIndx, clustersInOneExtent;
for (extentIndx=,clusterIndx=; extentIndx<extentCount; ++extentIndx,++clusterIndx){
curLcn = pOutBuf -> Extents[extentIndx].Lcn;
for (clustersInOneExtent = (ULONG)(pOutBuf -> Extents[extentIndx].NextVcn.QuadPart - preVcn.QuadPart); clustersInOneExtent; ++clusterIndx, --clustersInOneExtent, ++curLcn.QuadPart){
Clusters[clusterIndx] = curLcn.QuadPart;
}
preVcn = pOutBuf -> Extents[extentIndx].NextVcn;
} free(pOutBuf);
CloseHandle(hFile);
//--end [pecofia] //--start -read drive like file
HANDLE hDrive;
hDrive = CreateFile(pDriveFile, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, , NULL);
LARGE_INTEGER offset;
char *pBuf = (char *)malloc(sizeof(char) * ClusterSize);
for (clusterIndx=; clusterIndx<clusterCount; ++clusterIndx){
offset.QuadPart = ClusterSize * Clusters[extentIndx];
SetFilePointer(hDrive, offset.LowPart, &offset.HighPart, FILE_BEGIN);
ReadFile(hDrive, pBuf, ClusterSize * sizeof(char), &readBytes, NULL);
} free(pBuf);
free(Clusters);
CloseHandle(hDrive);
//--end
} int main()
{
ReadDisk("C:\\file.dat", "C:", "\\\\.\\C:"); return ;
}
以上代码因为专注于功能所以没有加入错误控制,但是实际应用中我们应该对每个Win API、堆分配进行错误控制。
①:关于‘FSCTL_GET_RETRIEVAL_POINTERS’标志位,以下是MSDN上的解释:
The FSCTL_GET_RETRIEVAL_POINTERS operation retrieves a variably sized data structure that describes the allocation and location on disk of a specific file. The structure describes the mapping between virtual cluster numbers (VCN offsets within the file or stream space) and logical cluster numbers (LCN offsets within the volume space).
使用这个标志位可以获得指定文件的内容信息,即使待读文件是独占文件,依然可以用这个标识位获取文件的簇流信息,然后通过读磁盘的方式读出文件内容。
注:如果独占文件,DeviceIoControl的这个标志位使用GENEIC_READ的话,会返回错误句柄。
结尾
欢迎在评论区留言,说出你的见解,大家一起讨论~