DeviceIoControl 直接从磁盘扇区读文件


好久没写博客了,最近看了下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,              // handle to volume
  FSCTL_GET_RETRIEVAL_POINTERS,  // dwIoControlCode
  (LPVOID) lpInBuffer,           // input buffer
  (DWORD) nInBufferSize,         // size of input buffer
  (LPVOID) lpOutBuffer,          // output buffer
  (DWORD) nOutBufferSize,        // size of output buffer
  (LPDWORD) lpBytesReturned,     // number of bytes returned
  (LPOVERLAPPED) lpOverlapped ); // OVERLAPPED structure

函数第三个参数对应上述第一个结构,此结构比较简单,需要传入文件的其实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即上一个簇流的大小。
所以根据此信息,我们能够得到文件在磁盘中簇流链的信息,从而定位文件,从磁盘中直接读取文件,具体代码如下(基本参考网上已有代码):
 
//
/// ReadFileFromSectors.cpp


#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;   //输入的开始vcn号
	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,
					//GENERIC_READ | GENERIC_WRITE,
					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);
	//初始化IO相关参数
	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;   //Cluster 数组的大小,一个簇占一个元素
	Clusters = (ULONGLONG *)malloc(*ClusterCount * sizeof(ULONGLONG));   //分配簇数组空间

	//开始遍历返回结果
	PreVcn = pOutFileBuffer->StartingVcn;
	for(r=0,Cls=0; r<pOutFileBuffer->ExtentCount; r++)   //ExtentCount 簇流的个数(每个簇流中有几个连续的簇)
	{
		Lcn = pOutFileBuffer->Extents[r].Lcn;
		//簇流中连续簇的个数等于 下一个簇流的起始 Vcn 号 减去 上一个 簇流的 起始 Vcn号 
		for(CnCount = (ULONG)(pOutFileBuffer->Extents[r].NextVcn.QuadPart - PreVcn.QuadPart); CnCount; CnCount--,Cls++,Lcn.QuadPart++)
		{
			Clusters[Cls] = Lcn.QuadPart;  //保存每个簇流中簇的 Lcn 号
		}

		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);
}

//--------------------------------------------------------------------
//
// Usage
//
// Tell user how to use the program.
//
//--------------------------------------------------------------------
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中,此方法是读不到的。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用DeviceIOControl函数获取物理磁盘的个数。具体实现方法如下: 1. 打开一个物理磁盘设备句柄,调用CreateFile函数,设置参数为: 文件名:物理磁盘设备路径,例如"\\\\.\\PhysicalDrive0",数字0表示第一个物理磁盘。 访问模式:GENERIC_READ | GENERIC_WRITE 共享模式:FILE_SHARE_READ | FILE_SHARE_WRITE 安全性描述符:NULL 2. 调用DeviceIOControl函数,设置参数为: 设备句柄:上一步中返回的句柄 控制码:IOCTL_STORAGE_QUERY_PROPERTY 输入缓冲区:STORAGE_PROPERTY_QUERY结构体,其中的PropertyId字段设置为StorageDeviceProperty,QueryType字段设置为PropertyStandardQuery 输入缓冲区大小:sizeof(STORAGE_PROPERTY_QUERY) 输出缓冲区:存放设备属性信息的缓冲区,可以先设置为一定大小,如果返回的信息超过缓冲区大小,则继续调用DeviceIOControl函数,并增大缓冲区大小,直到返回的信息能够全部存放在缓冲区中 输出缓冲区大小:缓冲区大小 返回值:如果函数调用成功,则返回非零值,否则返回0 3. 解析返回的设备属性信息,判断是否为物理磁盘,并统计物理磁盘的个数。 4. 关闭设备句柄,调用CloseHandle函数。 参考代码如下: ```c++ #include <windows.h> #include <winioctl.h> #include <stdio.h> int main() { HANDLE hDevice = CreateFile("\\\\.\\PhysicalDrive0", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("CreateFile failed, error code: %d\n", GetLastError()); return 0; } STORAGE_PROPERTY_QUERY query; query.PropertyId = StorageDeviceProperty; query.QueryType = PropertyStandardQuery; STORAGE_DESCRIPTOR_HEADER header; DWORD dwBytesReturned = 0; BOOL bRet = DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &header, sizeof(header), &dwBytesReturned, NULL); if (!bRet) { printf("DeviceIoControl failed, error code: %d\n", GetLastError()); CloseHandle(hDevice); return 0; } DWORD dwBufferSize = sizeof(STORAGE_DEVICE_DESCRIPTOR) + 512; PSTORAGE_DEVICE_DESCRIPTOR pDesc = (PSTORAGE_DEVICE_DESCRIPTOR)malloc(dwBufferSize); ZeroMemory(pDesc, dwBufferSize); pDesc->Size = dwBufferSize; bRet = DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), pDesc, dwBufferSize, &dwBytesReturned, NULL); if (!bRet) { printf("DeviceIoControl failed, error code: %d\n", GetLastError()); CloseHandle(hDevice); free(pDesc); return 0; } if ((pDesc->DeviceType == DIRECT_ACCESS_DEVICE) && (pDesc->BusType == BusTypeSata)) { printf("This is a physical disk.\n"); } else { printf("This is not a physical disk.\n"); } CloseHandle(hDevice); free(pDesc); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值