背景
在 XP 系统下下,我们可以直接调用 WirteFile 函数对磁盘写入数据,但到了 Win7 以及 Win7 版本以上的系统,就已经开始变得不那么简单了。
在 Win7 及以上版本中,对文件系统和存储堆栈进行的更改,限制对磁盘和卷的直接访问。
但是,在以下情况,存储驱动器可以写入磁盘句柄:
1、正要写入的扇区不位于卷内(空隙块与分区表)。注意:程序使用卷之外的扇区来存储元数据。分区表也位于卷之外的扇区中。由于这些扇区不受任何文件系统的控制,因此没有理由阻止对这些扇区的访问。
2、已经通过锁定请求或卸载请求显式锁定卷。
3、正要写入的扇区位于未安装或者无文件系统的卷内(这个是原生磁盘块,没有受操作系统的管理,当然可以随便写)。
实现原理
对于磁盘空隙块与分区表等磁盘位置是可以像 XP 系统那样直接使用 WriteFile 函数写的,但对于盘的数据区的写入则应该采用显式锁定的已安装卷或卸载请求显式锁定卷。
对磁盘物理扇区进行写有2种解决办法:
1、使用 DeviceIoControl 传递 FSCTL_LOCK_VOLUME 控制码,锁定请求显式锁定卷;写完后再传递 FSCTL_UNLOCK_VOLUME 控制码解锁请求显式锁定卷。
2、使用 DeviceIoControl 传递 FSCTL_DISMOUNT_VOLUME 控制码,卸载请求显式锁定卷(强制性);写完后关闭对象句柄即可。
注 FSCTL_LOCK_VOLUME 和 FSCTL_DISMOUNT_VOLUME 使用限制:
参考SDK:https://learn.microsoft.com/zh-cn/windows/win32/api/winioctl/ni-winioctl-fsctl_lock_volume
1、传递给 DeviceIoControl 的 hDevice 句柄必须是卷的句柄(调用 CreateFile 的 lpFileName 参数字符串格式为:"\\.\X:",而不是"\\.\PhysicalDrive2")。
2、必须在 CreateFile 的 dwShareMode 参数中指定 FILE_SHARE_READ 和 FILE_SHARE_WRITE 标志。
3、必须在 CreateFile 的 dwFlagsAndAttributes 参数中指定 FILE_FLAG_NO_BUFFERING 标志。
4、ReadFile() WriteFile() 的 lpBuffer 大小必须是512(扇区大小)的倍数。
代码
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <stdio.h>
#define SECTOR_SIZE 512 // 注4:ReadFile WriteFile函数 的 lpBuffer 大小必须是512(扇区大小)的倍数
// 方法1:锁定请求显式锁定卷
BOOL WriteDisk_Lock(char cDriver, ULONGLONG ullOffsetSector, BYTE* pData)
{
DWORD dwRet = 0;
BOOL bRet = FALSE;
char szDriver[MAX_PATH] = { 0 };
// "\\\\.\\X:"
wsprintf(szDriver, "\\\\.\\%c:", cDriver);
// 打开硬盘物理设备
// 注1:只能对逻辑分区("\\\\.\\X:")锁定与DISMOUNT操作,名称不能为"\\\\.\\PHYSICALDRIVEn"(n为0-256)即物理磁盘。
// 注2:指定 FILE_SHARE_READ | FILE_SHARE_WRITE 标志,
// 注3:指定 FILE_FLAG_NO_BUFFERING 标志。
HANDLE hDisk = CreateFile(szDriver, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL);
if (INVALID_HANDLE_VALUE == hDisk)
{
printf("CreateFile Error %u。\n", GetLastError());
return FALSE;
}
// 锁定请求显式锁定卷
bRet = DeviceIoControl(hDisk, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
if (FALSE == bRet)
{
printf("DeivceIoControl Error %u。\n", GetLastError());
return FALSE;
}
// 移动文件指针到指定扇区偏移
LARGE_INTEGER li = { 0 };
li.QuadPart = SECTOR_SIZE * ullOffsetSector;
SetFilePointer(hDisk, li.LowPart, &li.HighPart, FILE_BEGIN);
// 写入数据
bRet = WriteFile(hDisk, pData, SECTOR_SIZE, &dwRet, NULL);
if (FALSE == bRet)
{
printf("WriteFile Error %u。\n", GetLastError());
return FALSE;
}
// 解锁请求显式锁定卷
bRet = DeviceIoControl(hDisk, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
if (FALSE == bRet)
{
printf("DeivceIoControl Error %u。\n", GetLastError());
return FALSE;
}
// 关闭句柄
CloseHandle(hDisk);
return TRUE;
}
// 方法2:卸载请求显式锁定卷
BOOL WriteDisk_Dismount(char cDriver, ULONGLONG ullOffsetSector, BYTE* pData)
{
DWORD dwRet = 0;
BOOL bRet = FALSE;
char szDriver[MAX_PATH] = { 0 };
// "\\\\.\\X:"
wsprintf(szDriver, "\\\\.\\%c:", cDriver);
// 打开硬盘物理设备
// 注1:只能对逻辑分区("\\\\.\\X:")锁定与DISMOUNT操作,名称不能为"\\\\.\\PHYSICALDRIVEn"(n为0-256)即物理磁盘。
// 注2:指定 FILE_SHARE_READ | FILE_SHARE_WRITE 标志,
// 注3:指定 FILE_FLAG_NO_BUFFERING 标志。
HANDLE hDisk = CreateFile(szDriver, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL);
if (INVALID_HANDLE_VALUE == hDisk)
{
printf("CreateFile Error %u。\n", GetLastError());
return FALSE;
}
// 卸载请求显式锁定卷
bRet = DeviceIoControl(hDisk, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwRet, NULL);
if (FALSE == bRet)
{
printf("DeivceIoControl Error %u。\n", GetLastError());
return FALSE;
}
// 移动文件指针到指定扇区偏移
LARGE_INTEGER li = { 0 };
li.QuadPart = SECTOR_SIZE * ullOffsetSector;
SetFilePointer(hDisk, li.LowPart, &li.HighPart, FILE_BEGIN);
// 写入数据
bRet = WriteFile(hDisk, pData, SECTOR_SIZE, &dwRet, NULL);
if (FALSE == bRet)
{
printf("WriteFile Error %u。\n", GetLastError());
return FALSE;
}
// 关闭句柄
CloseHandle(hDisk);
return TRUE;
}
int wmain(int argc, wchar_t** argv)
{
return 0;
}
如何对物理磁盘("\\.\PhysicalDrive2")进行锁定读写操作呢?
1、首先对该物理磁盘的卷("\\.\X:")进行锁定。
2、使用 DeviceIoControl 传递 IOCTL_STORAGE_GET_DEVICE_NUMBER 控制码 获得磁盘的物理序号("\\.\PhysicalDrive2"中的2)。
3、CreateFile 打开 "\\.\PhysicalDrive2" 获得句柄。随后使用该句柄进行一系列的操作即可。




