https://msdn.microsoft.com/en-us/library/windows/desktop/aa365521(v=vs.85).aspx
好久没写博客了,最近看了下DeviceIoControl 关于磁盘的应用,特记一文,以备久后查阅。
首先介绍下,文件在磁盘的存储结构(具体可以到网上查询NTFS文件系统相关的教程后者数据恢复方面教程的介绍)。下面介绍的仅与此文相关。
文件属性(头):



(Ps: 截图摘自[数据重现文件系统原理精解与数据恢复最佳实践].(马林))
然后我们需要认识两个结构:
-
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;
通过使用参数
FSCTL_GET_RETRIEVAL_POINTERS 调用函数DeviceIoControl 我们就可以获得文件在磁盘中的定位信息。
方式如下:
- DeviceIoControl(
- (HANDLE) hDevice,
- FSCTL_GET_RETRIEVAL_POINTERS,
- (LPVOID) lpInBuffer,
- (DWORD) nInBufferSize,
- (LPVOID) lpOutBuffer,
- (DWORD) nOutBufferSize,
- (LPDWORD) lpBytesReturned,
- (LPOVERLAPPED) lpOverlapped );
函数第三个参数对应上述第一个结构,此结构比较简单,需要传入文件的其实Vcn号,这里填入 0 即可 (StartingVcn.QuadPart = 0)。
第二个结构相对复杂些:
由上述介绍可以知道,文件(相对较大的文件)在磁盘中是以簇流(连续的簇)的形式存放的。结构体中 ExtentCount 即表示簇流的个数
StartingVcn 第一个簇流的起始Vcn号, 而每个Extents都包含一个NextVcn号和一个Lcn,Lcn即表示本簇流的起始Lcn,NextVcn是用来判断下一个簇流的位置(通过NextVcn也可以的到上一个簇流的大小)
下面是msdn的解释:
- NextVcn
- The VCN at which the next extent begins. This value minus either StartingVcn (for the first Extents array member) or the NextVcn of the previous member of the array (for all other Extents array members) is the length, in clusters, of the current extent. The length is an input to the FSCTL_MOVE_FILE operation.
对于第一个簇流,NextVcn 减去 StartingVcn 即得到第一个簇流的大小,而对于后续的簇流,使用此NextVcn 减去上一个簇流的NextVcn即上一个簇流的大小。
所以根据此信息,我们能够得到文件在磁盘中簇流链的信息,从而定位文件,从磁盘中直接读取文件,具体代码如下(基本参考网上已有代码):
-
-
-
-
- #include <windows.h>
- #include <WinIoCtl.h>
- #include <stdio.h>
-
-
- ULONGLONG *GetFileClusters(PCHAR lpFilename, ULONG *ClusterSize, ULONG *ClusterCount, ULONG *FileSize)
- {
- HANDLE hFile = NULL;
-
- ULONG SectorsPerCluster;
- ULONG BytesPerSector;
-
- STARTING_VCN_INPUT_BUFFER InVcvBuffer;
- PRETRIEVAL_POINTERS_BUFFER pOutFileBuffer;
- ULONG OutFileSize;
-
- LARGE_INTEGER PreVcn,Lcn;
-
- ULONGLONG *Clusters = NULL;
- BOOLEAN bDeviceIoResult = FALSE;
-
-
- char DriverPath[8];
- memset(DriverPath, 0, sizeof(DriverPath));
- DriverPath[0] = lpFilename[0];
- DriverPath[1] = ':';
- DriverPath[2] = 0;
- GetDiskFreeSpace(DriverPath, &SectorsPerCluster, &BytesPerSector, NULL, NULL);
- *ClusterSize = SectorsPerCluster * BytesPerSector;
-
-
- hFile = CreateFile(lpFilename,
-
- FILE_READ_ATTRIBUTES,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- NULL,
- OPEN_EXISTING,
- 0,
- 0);
- if(hFile == INVALID_HANDLE_VALUE)
- {
- printf("GetFileClusters(): Failed to open file %s ...\n",lpFilename);
- return 0;
- }
- *FileSize = GetFileSize(hFile, NULL);
-
- DWORD dwRead, Cls, CnCount, r;
- OutFileSize = sizeof(RETRIEVAL_POINTERS_BUFFER) + (*FileSize / *ClusterSize) * sizeof(pOutFileBuffer->Extents);
- pOutFileBuffer = (PRETRIEVAL_POINTERS_BUFFER)malloc(OutFileSize);
- InVcvBuffer.StartingVcn.QuadPart = 0;
-
- bDeviceIoResult = DeviceIoControl(hFile,
- FSCTL_GET_RETRIEVAL_POINTERS,
- &InVcvBuffer,
- sizeof(InVcvBuffer),
- pOutFileBuffer,
- OutFileSize,
- &dwRead,
- NULL);
- if(!bDeviceIoResult)
- {
- printf("GetFileClusters(): Failed to call DeviceIocontrol with paramter FSCTL_GET_RETRIEVAL_POINTERS...\n|---errorcode = %d\n",GetLastError());
- CloseHandle(hFile);
- return 0;
- }
-
- *ClusterCount = (*FileSize + *ClusterSize -1) / *ClusterSize;
- Clusters = (ULONGLONG *)malloc(*ClusterCount * sizeof(ULONGLONG));
-
-
- PreVcn = pOutFileBuffer->StartingVcn;
- for(r=0,Cls=0; r<pOutFileBuffer->ExtentCount; r++)
- {
- Lcn = pOutFileBuffer->Extents[r].Lcn;
-
- for(CnCount = (ULONG)(pOutFileBuffer->Extents[r].NextVcn.QuadPart - PreVcn.QuadPart); CnCount; CnCount--,Cls++,Lcn.QuadPart++)
- {
- Clusters[Cls] = Lcn.QuadPart;
- }
-
- PreVcn = pOutFileBuffer->Extents[r].NextVcn;
- }
-
- free(pOutFileBuffer);
- CloseHandle(hFile);
- return Clusters;
- }
-
- int ReadFileFromSectors(PCHAR lpFileName, PCHAR pDstFileName)
- {
- ULONG ClusterSize, BlockSize, ClusterCount, FileSize;
- ULONGLONG *Clusters = NULL;
- DWORD dwReads,dwWrites;
- HANDLE hDriver, hFile;
- ULONG SectorsPerCluster, BytesPerSector, r;
- PVOID FileBuff;
- LARGE_INTEGER offset;
- char DrivePath[10];
- Clusters = GetFileClusters(lpFileName, &ClusterSize, &ClusterCount, &FileSize);
- if(Clusters == NULL)
- {
- printf("ReadFileFromSectors(): Failed to GetFileClusters ...\n|---errrorcode = %d\n",GetLastError());
- return 0;
- }
- DrivePath[0] = '\\';
- DrivePath[1] = '\\';
- DrivePath[2] = '.';
- DrivePath[3] = '\\';
- DrivePath[4] = lpFileName[0];
- DrivePath[5] = ':';
- DrivePath[6] = 0;
-
- hDriver = CreateFile(DrivePath,
- GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE,
- NULL,
- OPEN_EXISTING,
- 0,
- NULL);
- if(hDriver == INVALID_HANDLE_VALUE)
- {
- printf("ReadFileFromSectors(): Failed to CreateFile %s ...\n|---errrorcode = %d\n",DrivePath,GetLastError());
- return 0;
- }
-
- hFile = CreateFile(pDstFileName, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);
- if(hFile == INVALID_HANDLE_VALUE)
- {
- printf("ReadFileFromSectors(): Failed to CreateFile %s ...\n|---errrorcode = %d\n",pDstFileName,GetLastError());
- return 0;
- }
-
-
- FileBuff = malloc(ClusterSize);
-
- for (r=0; r<ClusterCount; r++, FileSize -= BlockSize)
- {
- offset.QuadPart = ClusterSize * Clusters[r];
- SetFilePointer(hDriver, offset.LowPart, &offset.HighPart, FILE_BEGIN);
- ReadFile(hDriver, FileBuff, ClusterSize, &dwReads, NULL);
- BlockSize = FileSize < ClusterSize ? FileSize : ClusterSize;
- WriteFile(hFile, FileBuff, BlockSize, &dwWrites, NULL);
- }
- free(FileBuff);
- free(Clusters);
- CloseHandle(hFile);
- CloseHandle(hDriver);
- }
-
-
-
-
-
-
-
-
- int Usage( CHAR *ProgramName )
- {
- printf("\nusage: %s -f srcfile dstfile ...\n", ProgramName );
- return -1;
- }
-
- int main(int argc, char *argv[])
- {
- if(argc != 4)
- {
- Usage(argv[0]);
- return 0;
- }
-
-
- if(strcmp(argv[1], "-f") == 0)
- {
- ReadFileFromSectors(argv[2], argv[3]);
- }
- else
- {
- Usage(argv[0]);
- }
-
- system("pause");
- return 1;
- }
编译程序,以管理员权限运行,测试结果如下:

Ps: 此方法还有些弊端,文件不能使加密、压缩的文件,而且文件必须是非常驻的(相对大些的文件即要有自己的簇),对于常驻的(小文件),文件内容直接存放到文件的MFT中,此方法是读不到的。
取得文件所在磁盘的扇区大小
- int GetSectorSize(LPCWSTR Path) {
- wchar_t buf[MAX_PATH + 1] = { L"" };
- DWORD SecPerClu, BytePerSec, NumFreeClu, TotalClu;
- UINT DriveType;
-
- if (GetFullPathName(Path, MAX_PATH, buf, NULL) == 0) { return 0; }
- if (!buf[0]) { return 0; }
- buf[3] = 0x00;
-
- DriveType = GetDriveType(buf);
- if (DriveType != DRIVE_FIXED && DriveType != DRIVE_REMOVABLE) { return 0; }
-
- if (GetDiskFreeSpace(buf, &SecPerClu, &BytePerSec, &NumFreeClu, &TotalClu)) {
- return ((int)BytePerSec);
- }
- return 0;
- }
直接读取扇区来拷贝文件的例子
使用函数
DeviceIoControl(hFile, FSCTL_GET_RETRIEVAL_POINTERS, &InBuf,
sizeof(InBuf), OutBuf, OutSize, &Bytes, NULL))
就可以查询文件的簇链
下面是个例如直接读取扇区来拷贝文件的例子
用法:FileCopy("C://boot.ini", "D://boot.ini");
- C/C++ code
ULONGLONG *GetFileClusters(
LPCSTR lpFileName,
ULONG ClusterSize,
ULONG *ClCount,
ULONG *FileSize
)
{
HANDLE hFile;
ULONG OutSize;
ULONG Bytes, Cls, CnCount, r;
ULONGLONG *Clusters = NULL;
BOOLEAN Result = FALSE;
LARGE_INTEGER PrevVCN, Lcn;
STARTING_VCN_INPUT_BUFFER InBuf;
PRETRIEVAL_POINTERS_BUFFER OutBuf;
hFile = CreateFile(lpFileName, FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
*FileSize = GetFileSize(hFile, NULL);
OutSize = sizeof(RETRIEVAL_POINTERS_BUFFER) + (*FileSize / ClusterSize) * sizeof(OutBuf->Extents);
OutBuf = (PRETRIEVAL_POINTERS_BUFFER)malloc(OutSize);
InBuf.StartingVcn.QuadPart = 0;
if (DeviceIoControl(hFile, FSCTL_GET_RETRIEVAL_POINTERS, &InBuf,
sizeof(InBuf), OutBuf, OutSize, &Bytes, NULL))
{
*ClCount = (*FileSize + ClusterSize - 1) / ClusterSize;
Clusters = (PULONGLONG)malloc(*ClCount * sizeof(ULONGLONG));
PrevVCN = OutBuf->StartingVcn;
for (r = 0, Cls = 0; r < OutBuf->ExtentCount; r++)
{
Lcn = OutBuf->Extents[r].Lcn;
for (CnCount = (ULONG)(OutBuf->Extents[r].NextVcn.QuadPart - PrevVCN.QuadPart);
CnCount; CnCount--, Cls++, Lcn.QuadPart++) Clusters[Cls] = Lcn.QuadPart;
PrevVCN = OutBuf->Extents[r].NextVcn;
}
}
free(OutBuf);
CloseHandle(hFile);
}
return Clusters;
}
BOOL FileCopy(LPCSTR lpSrcName, LPCSTR lpDstName)
{
BOOL bResult = FALSE;
ULONG ClusterSize, BlockSize;
ULONGLONG *Clusters;
ULONG ClCount, FileSize, Bytes;
HANDLE hDrive, hFile;
ULONG SecPerCl, BtPerSec, r;
PVOID Buff;
LARGE_INTEGER Offset;
CHAR Name[7];
Name[0] = lpSrcName[0];
Name[1] = ':';
Name[2] = 0;
GetDiskFreeSpace(Name, &SecPerCl, &BtPerSec, NULL, NULL);
ClusterSize = SecPerCl * BtPerSec;
Clusters = GetFileClusters(lpSrcName, ClusterSize, &ClCount, &FileSize);
if(Clusters)
{
Name[0] = '//';
Name[1] = '//';
Name[2] = '.';
Name[3] = '//';
Name[4] = lpSrcName[0];
Name[5] = ':';
Name[6] = 0;
hDrive = CreateFile(Name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
if (hDrive != INVALID_HANDLE_VALUE)
{
hFile = CreateFile(lpDstName, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
Buff = malloc(ClusterSize);
for (r = 0; r < ClCount; r++, FileSize -= BlockSize)
{
Offset.QuadPart = ClusterSize * Clusters[r];
SetFilePointer(hDrive, Offset.LowPart, &Offset.HighPart, FILE_BEGIN);
ReadFile(hDrive, Buff, ClusterSize, &Bytes, NULL);
BlockSize = FileSize < ClusterSize ? FileSize : ClusterSize;
WriteFile(hFile, Buff, BlockSize, &Bytes, NULL);
}
free(Buff);
CloseHandle(hFile);
bResult = TRUE;
}
CloseHandle(hDrive);
}
free(Clusters);
}
else
{
printf("GetFileClusters fail./n");
}
return bResult;
}