磁盘的过滤
10.3 驱动分析
10.3.5 DeviceIoControl 请求的处理
该请求的处理函数是 DPDispatchDeviceControl ,作为一个磁盘过滤设备,理论上是不需要对 DeciceControl 做任何处理的,只需要转发给下层设备即可。但是这里本驱动需要截获一个特殊的请求——IOCTL_VOLUME_ONLINE,这个请求是由Windows系统发出的,它本身的作用是把目标卷设备设置为在线状态,在这个在线状态设置完成之后,才会有对这个卷的读写等操作发生。
对于这个以还原为目的的驱动来说,最好是尽量在早的时候就对读写操作进行处理。基于这个理由,IOCTL_VOLUME_ONLINE 需要尽量早的被设置好。所以在该驱动中初始化完成。
我们在初始化的时候,需要目标卷的一些信息,例如需要这个卷的卷标,但是获取这一切的信息又必须要等过滤驱动的下层设备也就是真正的卷设备开始运行之后才能够提供,而卷设备开始运行缺需要这个 IOCTL_VOLUME_ONLINE 的请求发下去。所以,我们采用了同步的方式。就是让请求先发下去,等下层设备处理完毕之后再进行初始化工作,同时由于下发是采用了同步的方式,因此在完成请求之前是不会有其他的请求发生的。
WDM 驱动框架为实现上述的操作提供了相当方便的操作,只需要复制一份 irp stack ,设置好完成函数和一个等待时间,在调用下层设备之后就开始等待这个时间,当下层设备处理完成之后设置的完成函数就会被调用,在完成函数中会唤醒刚才所说的等待时间。
NTSTATUS
DPDispatchDeviceControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
//用来指向过滤设备的设备扩展的指针
PDP_FILTER_DEV_EXTENSION DevExt = DeviceObject->DeviceExtension;
//返回值
NTSTATUS ntStatus = STATUS_SUCCESS;
//用来指向irp stack的指针
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
//用来同步IOCTL_VOLUME_ONLINE处理的事件
KEVENT Event;
//用来传给IOCTL_VOLUME_ONLINE的完成函数的上下文
VOLUME_ONLINE_CONTEXT context;
switch (irpsp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_VOLUME_ONLINE:
{
//如果是卷设备的IOCTL_VOLUME_ONLINE,会进入到这里
//我们打算自己处理这个irp请求,这里先初始化一个事件用来在这个请求的完成函数里面做同步信号
KeInitializeEvent(&Event, NotificationEvent, FALSE);
//给这个请求的完成函数初始化参数
context.DevExt = DevExt;
context.Event = &Event;
//这里copy一份irp stack
IoCopyCurrentIrpStackLocationToNext(Irp);
//设置完成函数
IoSetCompletionRoutine(
Irp,
DPVolumeOnLineCompleteRoutine,
&context,
TRUE,
TRUE,
TRUE);
//调用下层设备来处理这个irp
ntStatus = IoCallDriver(DevExt->LowerDevObj, Irp);
//等待下层设备处理结束这个irp
KeWaitForSingleObject(
&Event,
Executive,
KernelMode,
FALSE,
NULL);
//返回
return ntStatus;
}
default:
//对于其它DeviceIoControl,我们一律调用下层设备去处理
break;
}
return DPSendToNextDriver(DevExt->LowerDevObj,Irp);
}
在这里设置了DPVolumeOnLineCompleteRoutine 作为下层驱动完成后的完成函数。需要注意的是,在该完成驱动里,下层设备所对应的磁盘卷设备已经可以工作了。因为请求已经被完成。
在完成函数里,首先获取了卷的名词,即常见的 C D E等盘符。这是通过系统调用获取到的,这个系统调用无法在 IOCTL_VOLUME_ONLINE 被下发之前使用。在获取了盘符之后,就可以选择我们感兴趣的盘符获取对应的信息。这些信息大部分都存储在 DBR 中。在获取了卷的信息之后,还需要初始化一个 bitmap,这个 bitmap 是还原功能的核心数据结构,具体的作用和实现在下面介绍。目前只需要知道初始化该结构的时候需要卷的总大小作为参数即可。在这些工作都完成之后,将用来标识还原卷的全局变量赋值,在今后运行的读写分发函数和boot驱动回调函数等众多函数中,都会引用这个全局变量,并根据它的内容来确定哪个是需要保护的卷。在该完成函数中,还唤醒了上述的事件等待,使得 DeviceControl 可以继续执行。代码如下:
NTSTATUS
DPVolumeOnLineCompleteRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOLUME_ONLINE_CONTEXT Context
)
{
//返回值
NTSTATUS ntStatus = STATUS_SUCCESS;
//这个卷设备的dos名字,也就是C,D等
UNICODE_STRING DosName = { 0 };
//在这里Context是不可能为空的,为空就是出错了
ASSERT(Context!=NULL);
//下面调用我们自己的VolumeOnline处理
//获取这个卷的dos名字
ntStatus = IoVolumeDeviceToDosName(Context->DevExt->PhyDevObj, &DosName);
if (!NT_SUCCESS(ntStatus))
goto ERROUT;
//将dos名字变成大写形式
Context->DevExt->VolumeLetter = DosName.Buffer[0];
if (Context->DevExt->VolumeLetter > L'Z')
Context->DevExt->VolumeLetter -= (L'a' - L'A');
//我们只保护“D”盘
if (Context->DevExt->VolumeLetter == L'D')
{
//获取这个卷的基本信息
ntStatus = DPQueryVolumeInformation(
Context->DevExt->PhyDevObj,
&(Context->DevExt->TotalSizeInByte),
&(Context->DevExt->ClusterSizeInByte),
&(Context->DevExt->SectorSizeInByte));
if (!NT_SUCCESS(ntStatus))
{
goto ERROUT;
}
//建立这个卷对应的位图
ntStatus = DPBitmapInit(
&Context->DevExt->Bitmap,
Context->DevExt->SectorSizeInByte,
8,
25600,
(DWORD)(Context->DevExt->TotalSizeInByte.QuadPart /
(LONGLONG)(25600 * 8 * Context->DevExt->SectorSizeInByte)) + 1);
if (!NT_SUCCESS(ntStatus))
goto ERROUT;
//对全局量赋值,说明我们找到需要保护的那个设备了
gProtectDevExt = Context->DevExt;
}
ERROUT:
if (!NT_SUCCESS(ntStatus))
{
if (NULL != Context->DevExt->Bitmap)
{
DPBitmapFree(Context->DevExt->Bitmap);
}
if (NULL != Context->DevExt->TempFile)
{
ZwClose(Context->DevExt->TempFile);
}
}
if (NULL != DosName.Buffer)
{
ExFreePool(DosName.Buffer);
}
//设置等待同步事件,这样可以让我们等待的DeviceIoControl处理过程继续运行
KeSetEvent(
Context->Event,
0,
FALSE);
return STATUS_SUCCESS;
}
明日计划
不得不说在搞论文的时候,有点没心思学别的。。论文好折磨。
除了论文就是想划水休息。但是培养习惯还是要坚持。
明天继续学习 bitmap的作用和分析 以及最主要的论文。俺要毕业。