通過DeviceIoControl讀磁盤的方式讀取獨占文件內容
前言
windows操作系統中常見的一個文件存儲系統是NTFS。在這個文件系統中MFT是它的核心。
圖一
MFT是一個數據結構,上圖是它的結構,它主要用來存放每個文件和目錄在磁盤中的索引。MFT由一個個MFT項構成。每個MFT項的大小是1024Bytes。每個MFT項由固定頭結構和多個屬性構成。屬性用來記錄與文件相關的信息。每個屬性又由屬性頭和屬性體構成。屬性頭包含了一些該屬性的重要信息,如屬性類型、屬性大小、是否為常駐屬性等等。[1]文件內容也被NTFS看作屬性。
其中,屬性分為常駐屬性和非常駐屬性。如果一個屬性的屬性體都可以存儲在MFT項中,稱該屬性是常駐屬性。如果1024Bytes的MFT項無法記錄該屬性的所有屬性體內容,稱該屬性是非常駐屬性。
非常駐屬性的屬性體裏存儲的不是文件真正的內容,而是文件內容的起始簇號和簇號個數(文件內容存放在MFT外部)。前者方便找到文件在磁盤的位置,後者方便確定文件的大小。這兩個信息稱為簇流信息。有時候一個文件的內容被存儲在多個簇流中,系統就用多個簇流信息來表示文件各部分內容的位置及大小。其中,除了文件的第一個簇流信息的起始簇號代表真正簇號,後面的每個簇流信息中的起始簇號記錄的值是前一個起始簇號的相對位置。[2]
如果我們需要讀磁盤上的某個文件,就可以這樣操作——從MFT項裏獲取文件各部分內容的簇流信息,從中獲得每個簇流的起始簇號,再用讀磁盤的方式打開一個讀磁盤句柄,將文件指針依次定位到各簇流的起始簇號位置,依次讀出每個簇流內容
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[1];
} 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, 0, NULL);
//--start -get file size [gfz]
ULONGLONG fileSize;
BY_HANDLE_FILE_INFORMATION fileInfo;
GetFileInformationByHandle(hFile, &fileInfo);
fileSize = fileInfo.nFileSizeLow + ((ULONGLONG)fileInfo.nFileSizeHigh << 32);
//--end [gfz]
ULONG clusterCount = (fileSize + ClusterSize - 1) / 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)0)->Extents) + clusterCount * sizeof(pOutBuf -> Extents); //consider that each cluster is in different extent
pOutBuf = (PRETRIEVAL_POINTERS_BUFFER)malloc(outBufSize * sizeof(char));
memset(pOutBuf, 0x0, 1);
vcnBuf.StartingVcn.QuadPart = 0;
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=0,clusterIndx=0; 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, 0, NULL);
LARGE_INTEGER offset;
char *pBuf = (char *)malloc(sizeof(char) * ClusterSize);
for (clusterIndx=0; 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 0;
}
以上代碼因為專註於功能所以沒有加入錯誤控制,但是實際應用中我們應該對每個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的話,會返回錯誤句柄。
結尾
歡迎在評論區留言,說出你的見解,大家一起討論~
參考
[1]. http://huifox.com/2018/01/25/ntfs-%E5%B8%B8%E9%A9%BB%E5%B1%9E%E6%80%A7%E4%B8%8E%E9%9D%9E%E5%B8%B8%E9%A9%BB%E5%B1%9E%E6%80%A7/
[2]. http://blog.51cto.com/shujvhuifu/1813848
通過DeviceIoControl讀磁盤的方式讀取獨占文件內容