背景
在 XP 系统下下,我们可以直接调用 WirteFile 函数对磁盘写入数据,但到了 Windows 7 以及 Windows 7 版本以上的系统,就已经开始变得不那么简单了。
在 Windows 7 及以上版本中,对文件系统和存储堆栈进行的更改,限制对磁盘和卷的直接访问,但是,在以下情况,存储驱动器可以写入磁盘句柄:
正要写入的扇区不位于卷内(空隙块与分区表)。注意:程序使用卷之外的扇区来存储元数据。分区表也位于卷之外的扇区中。由于这些扇区不受任何文件系统的控制,因此没有理由阻止对这些扇区的访问
已经通过锁定请求或卸载请求显式锁定卷
正要写入的扇区位于未安装或者无文件系统的卷内(这个是原生磁盘块,没有受操作系统的管理,当然可以随便写)
本文介绍的是使用第 2 种方法,通过锁定请求或卸载请求显式锁定卷来实现对磁盘数据的读写。现在把实现过程的实现原理整理成文档,分享给大家。
实现原理
我们对于磁盘空隙块与分区表等磁盘位置是可以像 XP 系统那样直接使用 WriteFile 函数写的,但对于盘的数据区的写入则应该采用显式锁定的已安装卷或卸载请求显式锁定卷。
一般是采用 FSCTL_DISMOUNT_VOLUME,FSCTL_LOCK_VOLUME 的前提是没有程序占用该盘的文件,所以可能会失败,FSCTL_DISMOUNT_VOLUME 是强制型的,当我们强制卸载请求显式锁定卷后,一些正在占用磁盘的程序会出现崩溃,原因是它不能读盘上资源。但是,要注意的是,对于FSCTL_DISMOUNT_VOLUME, 如果指定的卷是系统卷或包含页面文件,则操作失败,所以不能对系统盘强制卸载成功。
首先,我们先介绍第一种方式:锁定请求显式锁定卷。
首先,我们使用 CreateFile 打开逻辑卷,获取句柄
然后,我们就可以使用 DeviceIoControl 传递 FSCTL_LOCK_VOLUME 控制码,锁定请求显式锁定卷
那么,这时,我们就可以通过 SetFilePointer 一定文件指针,使用 WriteFile 函数对磁盘数据进行写入
写入完成后,还需使用 DeviceIoControl 传递 FSCTL_UNLOCK_VOLUME 控制码,解锁请求显式锁定卷
最后,我们便可以关闭文件句柄,完成操作
然后,我们介绍第二种方式:卸载请求显式锁定卷。
首先,我们使用 CreateFile 打开逻辑卷,获取句柄
然后,我们就可以使用 DeviceIoControl 传递 FSCTL_DISMOUNT_VOLUME 控制码,卸载请求显式锁定卷
那么,这时,我们就可以通过 SetFilePointer 一定文件指针,使用 WriteFile 函数对磁盘数据进行写入
最后,我们便可以关闭文件句柄,完成操作
这就是本文介绍的两种方法。要注意,使用CreateFile打开设备的时候,名称不能为\\\\.\\PHYSICALDRIVEn(n为0-256),即物理磁盘,只能对逻辑分区锁定与卸载操作。而且,这两种方法,都不能对系统盘进行操作。
编码实现
数据读取// 读取指定扇区数据
BOOLReadDisk(charcDriver,ULONGLONG ullOffsetSector,BYTE*pData)
{
DWORD dwRet=0;
BOOL bRet=FALSE;
charszDriver[MAX_PATH]={0};
// \\\\.\\C:
::wsprintf(szDriver,"\\\\.\\%c:",cDriver);
// 打开硬盘物理设备
HANDLE hDisk=::CreateFile(szDriver,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,NULL);
if(INVALID_HANDLE_VALUE==hDisk)
{
ShowError("CreateFile");
returnFALSE;
}
// 移动文件指针到指定扇区偏移
LARGE_INTEGER li={0};
li.QuadPart=SECTOR_SIZE*ullOffsetSector;
::SetFilePointer(hDisk,li.LowPart,&li.HighPart,FILE_BEGIN);
// 读取数据
bRet=::ReadFile(hDisk,pData,SECTOR_SIZE,&dwRet,NULL);
if(FALSE==bRet)
{
ShowError("ReadFile");
returnFALSE;
}
// 关闭句柄
::CloseHandle(hDisk);
returnTRUE;
}
锁定请求显式锁定卷方式写入数据// 写入指定扇区数据 方法一 锁定请求显式锁定卷
BOOLWriteDisk_Lock(charcDriver,ULONGLONG ullOffsetSector,BYTE*pData)
{
DWORD dwRet=0;
BOOL bRet=FALSE;
charszDriver[MAX_PATH]={0};
// \\\\.\\C:
::wsprintf(szDriver,"\\\\.\\%c:",cDriver);
// 打开硬盘物理设备
// 这里名称不能为\\\\.\\PHYSICALDRIVEn(n为0-256),即物理磁盘,只能对逻辑分区锁定与DISMOUNT操作
HANDLE hDisk=::CreateFile(szDriver,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,NULL);
if(INVALID_HANDLE_VALUE==hDisk)
{
ShowError("CreateFile");
returnFALSE;
}
// 锁定请求显式锁定卷
bRet=::DeviceIoControl(hDisk,FSCTL_LOCK_VOLUME,NULL,0,NULL,0,&dwRet,NULL);
if(FALSE==bRet)
{
ShowError("DeivceIoControl");
returnFALSE;
}
// 移动文件指针到指定扇区偏移
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)
{
ShowError("WriteFile");
returnFALSE;
}
// 解锁请求显式锁定卷
bRet=::DeviceIoControl(hDisk,FSCTL_UNLOCK_VOLUME,NULL,0,NULL,0,&dwRet,NULL);
if(FALSE==bRet)
{
ShowError("DeivceIoControl");
returnFALSE;
}
// 关闭句柄
::CloseHandle(hDisk);
returnTRUE;
}
卸载请求显式锁定卷方式写入数据// 写入指定扇区数据 方法二 卸载请求显式锁定卷
BOOLWriteDisk_Dismount(charcDriver,ULONGLONG ullOffsetSector,BYTE*pData)
{
DWORD dwRet=0;
BOOL bRet=FALSE;
charszDriver[MAX_PATH]={0};
// \\\\.\\C:
::wsprintf(szDriver,"\\\\.\\%c:",cDriver);
// 打开硬盘物理设备
// 这里名称不能为\\\\.\\PHYSICALDRIVEn(n为0-256),即物理磁盘,只能对逻辑分区锁定与DISMOUNT操作
HANDLE hDisk=::CreateFile(szDriver,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,NULL);
if(INVALID_HANDLE_VALUE==hDisk)
{
ShowError("CreateFile");
returnFALSE;
}
// 卸载请求显式锁定卷
bRet=::DeviceIoControl(hDisk,FSCTL_DISMOUNT_VOLUME,NULL,0,NULL,0,&dwRet,NULL);
if(FALSE==bRet)
{
ShowError("DeivceIoControl");
returnFALSE;
}
// 移动文件指针到指定扇区偏移
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)
{
ShowError("WriteFile");
returnFALSE;
}
// 关闭句柄
::CloseHandle(hDisk);
returnTRUE;
}
程序测试
我们在 main 函数中调用上面的函数进行测试,向非系统盘 E 盘读取并使用 3 种方式写入数据,测试结果如下所示:
数据读取成功,直接调用 WriteFile 函数写入数据方式失败;使用锁定请求显式锁定卷方式失败;使用卸载请求显式锁定卷方式成功。
总结
要注意两点:一是使用 CreateFile 打开设备的时候,名称不能为\\\\.\\PHYSICALDRIVEn(n为0-256),即物理磁盘,只能对逻辑分区锁定与卸载操作。二是,本文介绍的这两种方法,都不能对系统盘进行操作。
参考