读写原始磁盘扇区
绕过类磁盘驱动程序的上层过滤器来读写磁盘
引言
这是一个用于在 Windows 系统(NT5.0, 5.1 内核)上读取和写入原始磁盘扇区的工具。
我之所以产生写这个工具的灵感,是因为我的笔记本电脑感染了某种恶意软件,该软件位于磁盘类驱动程序的上方作为上层过滤器,导致我无法使用 WinHex 等用户模式磁盘编辑工具写入磁盘扇区。
几天后,我想到应该编写一个实用程序,通过直接
与磁盘类驱动程序通信来读取和写入原始磁盘扇区。
背景
要理解本文,应具备 C 编程和 Windows 驱动程序编程知识。
我们将通过以下主题来更好地理解该实用程序:
- 存储驱动程序的设备堆栈
- 枚举代表磁盘和分区的设备对象
- 如何读/写扇区
1. 存储驱动程序的设备堆栈
Microsoft 提供了通用的存储驱动程序,用于在逻辑级别管理存储,从而将硬件细节抽象化,以便上层文件系统和其他文件驱动程序使用。这被称为磁盘类驱动程序(用于处理磁盘类硬件的驱动程序,即“disk.sys”)。
类似地,为了处理 SCSI、IDE 硬件设备,Microsoft 提供了通用的端口接口驱动程序,特定供应商为其磁盘设备提供的驱动程序可以动态链接到这些驱动程序。
例如,scsiport.sys(旧接口)storport.sys(新接口)用作 SCSI 端口的接口,而 Pciidex.sys 用作 IDE 端口的接口。
2. 枚举代表磁盘和分区的设备对象
首先需要回答一个问题。
操作系统是如何知道硬盘已连接到系统的?
每当新的磁盘设备连接到系统时,SCSI 和 IDE 端口驱动程序会创建设备对象(尽管 PCI 驱动程序是第一个出现的)来表示 SCSI/IDE 设备,并将其通知 I/O 管理器。I/O 管理器进而查询设备以了解其设备 ID 和供应商 ID。根据设备 ID 和供应商 ID,I/O 管理器(通过注册表或 INF 文件机制)决定哪个驱动程序适合处理此设备(由供应商提供的驱动程序),并加载硬件设备驱动程序,该驱动程序创建代表设备的函数设备对象,并将其附加到由相应端口驱动程序创建的较低层设备。
I/O 管理器将新添加到系统的磁盘通知给磁盘类驱动程序(disk.sys)。然后,磁盘类驱动程序会创建代表原始磁盘的设备对象。
如果系统中存在有效的分区,它也会为相应分区创建设备对象。
例如,磁盘类驱动程序创建的设备对象如下:
- \Device\Harddisk0\DR(0) --> 表示原始硬盘 0
- \Device\Harddisk0\DP(1)0x7e000-0x7ff50c00+2 --> 表示硬盘 0 的分区 2
第一个十六进制数字显示分区的起始位置,第二个数字显示分区的长度。
这意味着所有代表磁盘和分区的设备对象都链接在磁盘类驱动程序(即 disk.sys)的驱动程序对象中。
现在要枚举创建的设备对象,首先需要访问磁盘类驱动程序的驱动程序对象。
解决方案是使用未公开的内核对象管理函数“ObReferenceObjectByName
”原型。
NTSTATUS ObReferenceObjectByName(
PUNICODE_STRING,
DWORD,
PACCESS_STATE,
ACCESS_MASK,
POBJECT_TYPE,
KPROCESSOR_MODE,
PVOID,
PVOID *Object);
第一个参数是一个 Unicode 字符串
,即“\Driver\disk”,object 接收 disk.sys 的 DRIVER_OBJECT
的指针。
从 DRIVER_OBJECT
中,您可以枚举磁盘类驱动程序创建的所有设备对象,并存储负责原始磁盘和分区的设备对象的指针。下面的代码片段将使事情更清晰。
PDEVICE_OBJECT pDeviceObject;
.....
// DeviceType 7 corresponds to FILE_DISK_DEVICE Type Device Object and
// It should have name too that's why Flags checks for 0x40 (DO_DEVICE_HAS_NAME )
if (pDeviceObject->DeviceType == 7
&& (pDeviceObjectTemp->Flags & 0x40))
3. 如何读/写扇区
一旦您获得了原始磁盘和分区的设备对象的指针,读取和写入这些原始磁盘/分区并不困难。您只需对相应的设备对象执行 IoCallDriver
,并在 IRP 中初始化 IRP_MJ_READ
/IRP_MJ_WRITE
函数代码。
以下代码将使事情更清晰。
LARGET_INTEGER lDiskOffset;
PDEVICE_OBJECT pDevObj; //Device object representing disk/partition
KEVENT Event;
// Trying to read some arbitrary sector number 1169944 and
// by default assuming sector size
// 512
..........
..........
lDiskOffset.QuadPart = 1169944*512;
sBuf = ExAllocatePool(NonPagedPool, size);
if (!sBuf) {
ObDereferenceObject(pFileObj);
return STATUS_INSUFFICIENT_RESOURCES;
}
KeInitializeEvent(&Event, NotificationEvent, FALSE);
memset(sBuf, '0x00', size);
pIrp = IoBuildSynchronousFsdRequest(IRP_MJ_WRITE/*IRP_MJ_READ*/,
pDevObj, sBuf, size, &lDiskOffset, &Event, &ioStatus);
if (!pIrp) {
ExFreePool(sBuf);
return STATUS_INSUFFICIENT_RESOURCES;
}
status = IoCallDriver(pDevObj, pIrp);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
status = ioStatus.Status;
}
ExFreePool(sBuf);
..........
上面给出的只是将写入操作发送到扇区号 1169944 的示例代码。
关注点
在编写代码时,我只是执行了 READ
操作来验证我的结果。在设计中传递写入操作的数据缓冲区时,我没有太在意(请参阅驱动程序代码以获得更多解释)。因此,我实现了一个丑陋的 hack 来传递用户模式缓冲区用于写入操作。我将在未来的版本中改进它。
历史
- 2008 年 8 月 2 日:初始发布