最近幾天學習了下檔案過濾驅動, 這方面的資料確實很少
這篇文章是我這幾天學習的總結, 特此貼出來供大家一起學習,
畢竟是初學檔案過濾驅動, 錯誤之處難免, 還請多多見諒.
下面進入正題
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;
}
沒有留言:
張貼留言