最近几天学习了下文件过滤驱动, 这方面的资料确实很少, 国内也就是楚狂人教程比较详细.
这篇文章是我这几天学习的总结, 有些内容楚狂人教程中没有提到, 特此贴出来供大家一起学习,
毕竟是初学文件过滤驱动, 错误之处难免, 还请多多见谅.
ps:1.第一次写文章, 如果斑竹大大觉得小弟的文章对大家有点帮助, 那么, 你懂的, 嘿嘿.
2.文档中的代码可以在我的另一个帖子中下载到.http://bbs.pediy.com/showthread.php?p=958810
下面进入正题
1.DriverEntry例程
(1)创建过滤驱动的控制设备, 以后我们的IO控制码就是发到这个设备上面
//这里的设备名与普通设备有所不同.
//当然, 最简单的可以直接写成 L"\\Device\\Filemontor";(FileMon中是这么写的, 调试过可行)
RtlInitUnicodeString( &nameString, L"\\FileSystem\\Filters\\FileMonitor" );
status = IoCreateDevice( DriverObject,
0, //has no device extension
//这是与其他Attach到别的设备上的设备的不同之处
&nameString,
FILE_DEVICE_DISK_FILE_SYSTEM,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&gSFilterControlDeviceObject );
if (status == STATUS_OBJECT_PATH_NOT_FOUND) {
RtlInitUnicodeString( &nameString, L"\\FileSystem\\FileMonitor" );
status = IoCreateDevice( DriverObject,
0,
&nameString,
FILE_DEVICE_DISK_FILE_SYSTEM,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&gSFilterControlDeviceObject );
}
//创建符号链接
RtlInitUnicodeString(&syblnkString, L"\\DosDevices\\FileMonitor");
status = IoCreateSymbolicLink( &syblnkString, &nameString );
if (!NT_SUCCESS(status)) {
IoDeleteSymbolicLink( &syblnkString );
status = IoCreateSymbolicLink( &syblnkString, &nameString );
if (!NT_SUCCESS(status)) {
KdPrint(("创建符号链接失败~\n"));
IoDeleteDevice(gSFilterControlDeviceObject);
return status;
}
}
(2)设置例程
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
DriverObject->MajorFunction[i] = SfDispatch;
}
DriverObject->MajorFunction[IRP_MJ_CREATE] = SfCreate;
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SfFsControl;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = SfCleanupClose;
(3)调用IoRegisterFsRegistrationChange函数来通知我们文件系统的加载和卷的mount.
2.SfCreate 例程
(1)sfilter的原版中是这么写的
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Information = 0;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return STATUS_INVALID_DEVICE_REQUEST;
}
这样写的后果是我们用CreateFile函数在R3下打开此控制设备符号链接的时候失败
我刚开始学习文件过滤驱动的时候对此不是很了解, CreateFile老是失败,
起初还以为是符号链接名写错了, 后来参看了FileMon的代码才反应过来
于是修改如下:
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = FILE_OPENED;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return STATUS_SUCCESS;
}
(2)根据不同软件的需要, 编写此函数的接下来部分
[1]比如我们要阻止病毒在Windows目录下创建文件, 那么我们就要在此文件还没创建的时候得到此文件的全路径.
要在这个时候得到文件的路径, 楚狂人也说了有点麻烦. 下面提供一个函数给大家, 用于在文件创建前得到路径.
BOOLEAN MzfGetFileFullPathPreCreate(PFILE_OBJECT pFile, PUNICODE_STRING path )
{
NTSTATUS status;
POBJECT_NAME_INFORMATION pObjName = NULL;
WCHAR buf[256] = {0};
void *obj_ptr = NULL;
ULONG ulRet = 0;
BOOLEAN bSplit = FALSE;
if (pFile == NULL) return FALSE;
if (pFile->FileName.Buffer == NULL) return FALSE;
pObjName = (POBJECT_NAME_INFORMATION)buf;
if (pFile->RelatedFileObject != NULL)
obj_ptr = (void *)pFile->RelatedFileObject;
else
obj_ptr = (void *)pFile->DeviceObject;
status = ObQueryNameString(obj_ptr, pObjName, 256*sizeof(WCHAR), &ulRet);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
pObjName = (POBJECT_NAME_INFORMATION)ExAllocatePool(NonPagedPool, ulRet);
if (pObjName == NULL) return FALSE;
RtlZeroMemory(pObjName, ulRet);
status = ObQueryNameString(obj_ptr, pObjName, ulRet, &ulRet);
if (!NT_SUCCESS(status)) return FALSE;
}
//拼接的时候, 判断是否需要加 '\\'
if (pFile->FileName.Length > 2 &&
pFile->FileName.Buffer[0] != L'\\' &&
pObjName->Name.Buffer[pObjName->Name.Length/sizeof(WCHAR) -1] != L'\\')
bSplit = TRUE;
ulRet = pObjName->Name.Length + pFile->FileName.Length;
if (path->MaximumLength < ulRet) return FALSE;
RtlCopyUnicodeString(path, &pObjName->Name);
if (bSplit)
RtlAppendUnicodeToString(path, L"\\");
RtlAppendUnicodeStringToString(path, &pFile->FileName);
if ((void*)pObjName != (void*)buf)
ExFreePool(pObjName);
return TRUE;
}
至此, 得到了文件的路径以后, 我们就可以做出判断了, 如果要阻止文件创建,
那么直接用IoCompleteRequest函数结束此IRP即可, 否则下发
IoSkipCurrentIrpStackLocation( Irp );
return IoCallDriver( ((PSFILTER_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp );
[2]要是像FileMon那样只是记录系统中创建了哪些文件的话, 我们可以设置此函数的完成例程.
然后等文件创建完成了之后, 只要调用 IoQueryFileDosDeviceName 函数即可知道文件的全路径了.
设置完成例程如下:
{
KEVENT waitEvent;
KeInitializeEvent( &waitEvent, NotificationEvent, FALSE );
IoCopyCurrentIrpStackLocationToNext( Irp );
IoSetCompletionRoutine(Irp, SfCreateCompletion, &waitEvent, TRUE, TRUE, TRUE );
status = IoCallDriver( ((PSFILTER_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp );
if (STATUS_PENDING == status) {
NTSTATUS localStatus = KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE, NULL);
ASSERT(STATUS_SUCCESS == localStatus);
}
//此处文件已经创建完成了, 我们可以调用IoQueryFileDosDeviceName函数得到文件的全路径
//最后结束此IRP
status = Irp->IoStatus.Status;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return status;
}
3.SfDispatch例程
在此例程中要判断是不是我们的控制设备, 如果使我们的控制设备, 则要处理相应的IO控制码.
否则, 下发此IRP
NTSTATUS SfDispatch ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION irpStack;
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
Irp->IoStatus.Information = 0;
irpStack = IoGetCurrentIrpStackLocation( Irp );
switch (irpStack->MajorFunction) {
case IRP_MJ_DEVICE_CONTROL:
// 此函数用来执行相应的控制码
status = SpyCommonDeviceIoControl( Irp->AssociatedIrp.SystemBuffer,
irpStack->Parameters.DeviceIoControl.InputBufferLength,
Irp->AssociatedIrp.SystemBuffer,
irpStack->Parameters.DeviceIoControl.OutputBufferLength,
irpStack->Parameters.DeviceIoControl.IoControlCode,
&Irp->IoStatus );
break;
case IRP_MJ_CLEANUP
status = STATUS_SUCCESS;
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
}
Irp->IoStatus.Status = status;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return status;
}
//不是我们的控制设备则下发此IRP
return SfPassThrough( DeviceObject, Irp );
}
IO控制码也可以在FASTIO例程的SfFastIoDeviceControl函数中处理,如下:
当然, 最简单的还是在FASTIO例程中返回FALSE. 这样, 系统便会调用我们上面的SfDispatch函数.
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
SpyCommonDeviceIoControl( InputBuffer,
InputBufferLength,
OutputBuffer,
OutputBufferLength,
IoControlCode,
IoStatus );
return TRUE;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)