在Windows系統上與安全軟件相關的驅動開發過程當中,「過濾(filter)」是極其重要的一個概念。過濾是在不影響上層和下層接口的狀況下,在Windows系統內核中加入新的層,從而不須要修改上層的軟件和下層的真實驅動,就加入了新的功能。算法
過濾的概念和基礎編程
1.設備綁定的內核API之一數組
進行過濾的最主要方法是對一個設備對象(Device Object)進行綁定。經過編程生成一個虛擬設備,並「綁定」(Attach)在一個真實的設備上。一旦綁定,則原本操做系統發送給真實設備的請求,就會首先發送的這個虛擬設備。安全
在WDK中,有多個內核API能實現綁定功能。下面是其中一個函數的原型:數據結構
- NTSTATUS
- IoAttachDevice(
- IN PDEVICE_OBJECT SourceDevice,
- IN PUNICODE_STRING TargetDevice,
- OUT PDEVICE_OBJECT *AttachedDevice
- );
IoAttachDevice的參數以下:函數
SouceDevice是調用者生成的用來過濾的虛擬設備;而TargetDevice是要被綁定的目標設備。請注意這裏的TargetDevice並非一個PDEVICE_OBJECT,而是設備的名字。測試
若是一個設備被其餘設備綁定了,他們在一塊兒的一組設備,被稱爲設備棧。實際上,IoAttachDevice總會綁定設備棧最上層的那個設備。spa
AttachedDevice是一個用來返回的指針的指針。綁定成功後,被綁定的設備的指針返回到這個地址。操作系統
下面這個例子綁定串口1。之因此這裏綁定很方便,是由於在Windows中,串口設備有固定的名字。第一個串口名字爲「\Device\Serial0」,第二個爲「\Device\Serial1」,以此類推。請注意實際編程時C語言中的「\」要寫成「\\」。.net
- UNICODE_STRING com_name = RLT_CONSTANT_STRING(L"\\Device\\Serial0");
- NTSTATUS status = IoAttachDevice(
- com_filter_device,
- &com_device_name,
- &attached_device
- );
2.綁定設備的內核API之二
並非全部設備都有設備名字,因此依靠IoAttachDevice沒法綁定沒有名字的設備。另外還有兩個API:一個是IoAttachDeviceToDeviceStack,另外一個是IoAttachDeivceToDeviceStackSafe。這兩個函數功能同樣,都是根據設備對象的指針(而不是名字)進行綁定;區別是後者更加安全,並且只有在Windows2000SP4和Windows XP以上的系統中才有。
- NTSTATUS
- IoAttachDeviceToDeviceStackSafe(
- IN PDEVICE_OBJECT SourceDevice,
- IN PDEVICE_OBJECT TargetDevice,
- IN OUT PDEVICE_OBJECT *AttachedToDeviceObject
- );
和第一個API相似,只是TargetDevice換成了一個指針。另外,AttachedToDeviceObject一樣也是返回最終被綁定的設備,實際上也就是以前設備棧上最頂端的那個設備。
3.生成過濾設備並綁定
在綁定一個設備以前,先要知道如何生成一個用於過濾的過濾設備。函數IoCreateDevice被用於生成設備:
- NTSTATUS
- IoCreateDevice(
- IN PDRIVER_OBJECT DriverObject,
- IN ULONG DeviceExtensionSize,
- IN PUNICODE_STRING DeviceName,
- IN DEVICE_TYPE DeviceType,
- IN ULONG DeviceCharacteristics,
- IN BOOLEAN Exclusive,
- OUT PDEVICE_OBJECT *DeviceObject
- );
DriverObject:輸入參數,每一個驅動程序中會有惟一的驅動對象與之對應,但每一個驅動對象會有若干個設備對象。DriverObject指向的就是驅動對象指針。
DeviceExtensionSize:輸入參數,指定設備擴展的大小,I/O管理器會根據這個大小,在內存中建立設備擴展,並與驅動對象關聯。
DeviceName:輸入參數,設置設備對象的名字。一個規則是,過濾設備通常不須要設備名,傳入NULL便可
DeviceType:輸入參數,設備類型,保持和被綁定的設備類型一致便可。
DeviceCharacterristics:輸入參數,設備對象的特徵。
Exclusive:輸入參數,設置設備對象是否爲內核模式下使用,通常設置爲TRUE。
DeviceObject:輸出參數,I/O管理器負責建立這個設備對象,並返回設備對象的地址。
但值得注意的是,在綁定一個設備以前,應該把這個設備對象的多個子域設置成和要綁定的目標對象一致,包括標誌和特徵。下面是一個示例函數,這個函數能夠生成一個設備,並綁定到另外一個設備上。
- NTSTATUS
- ccpAttachDevice(
- PDRIVER_OBJECT driver,
- PDEVICE_OBJECT oldobj,
- PDEVICE_OBJECT *fltobj,
- PDEVICE_OBJECT *next)
- {
- NTSTATUS status;
- PDEVICE_OBJECT topdev = NULL;
-
-
- status = IoCreateDevice(driver,
- 0,
- NULL,
- oldobj->DeviceType,
- 0,
- FALSE,
- fltobj);
- if (status != STATUS_SUCCESS)
- {
- return status;
- }
-
-
- if (oldobj->Flags & DO_BUFFERED_IO)
- {
- (*fltobj)->Flags |= DO_BUFFERED_IO;
- }
- if(oldobj->Flags & DO_DIRECT_IO)
- {
- (*fltobj)->Flags |= DO_DIRECT_IO;
- }
- if (oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)
- {
- (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
- }
- (*fltobj)->Flags |= DO_POWER_PAGABLE;
-
- topdev = IoAttachDeviceToDeviceStack(*fltobj,oldobj);
- if (topdev == NULL)
- {
-
- IoDeleteDevice(*fltobj);
- *float = NULL;
- status = STATUS_UNSUCCESSFUL;
- return status;
- }
- *next = topdev;
-
-
- (*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;
- return STATUS_SUCCESS;
- }
4.從名字得到設備對象
在知道一個設備名字的狀況下,使用IoGetDeviceObjectPointer能夠得到這個設備對象的指針。這個函數的原型以下:
- NTSTATUS
- IoGetDeviceObjectPointer(
- IN PUNICODE_STRING ObjectName,
- IN ACCESS_MASK DesiredAccess,
- OUT PFILE_OBJECT *FileObject,
- OUT PDEVICE_OBJECT *DeviceObject
- );
其中ObjectName就是設備名字。
DesireAccess是指望訪問的權限。實際使用時不要顧慮那麼多,直接填寫FILE_ACCESS_ALL便可。
FileObject是一個返回參數,即得到設備對象的同時會獲得一個文件對象(File Object)。就打開串口這件事而言,這個文件對象沒有什麼用處。但必須注意:在使用這個函數以後必須把這個文件對象「解除引用」,不然會引發內存泄露。
要獲得的設備對象就返回在參數DeviceObject中了。
- PDEVICE_OBJECT ccpOpenCom(ULONG id,NTSTATUS *status)
- {
-
- UNICODE_STRING name_str;
- static WCHAR name[32] = {0};
- PFILE_OBJECT fileObj = NULL;
- PDEVICE_OBJECT devObj = NULL;
-
-
- memset(name,0,sizeof(WCHAR)*32);
- RtlStringCchPrintfW(
- name,32,
- L"\\Device\\Serial%d",id);
- RtlInitUnicodeString(&name_str,name);
-
-
- *status = IoGetDeviceObjectPointer(
- &name_str,
- FILE_ALL_ACCESS,
- &fileObj,&devObj);
-
-
- if (*status == NT_SUCCESS)
- {
- ObDereferenceObject(fileObj);
- }
-
-
- return devObj;
- }
5.綁定全部端口
下面是一個簡單的函數,實現了綁定本機上全部串口的功能。這個函數用到了前面提供的ccpOpenCom和ccpAttachDevice這兩個函數
- #define CCP_MAX_COM_IO 32
- static PDEVICE_OBJECT s_fltObj[CCP_MAX_COM_IO] = {0};
- static PDEVICE_OBJECT s_nextObj[CCP_MAX_COM_IO] = {0};
-
- void ccpAttachAllComs(PDRIVER_OBJECT driver)
- {
- ULONG i;
- PDEVICE_OBJECT com_ob;
- NTSTATUS status;
- for(i = 0 ; i < CCP_MAX_COM_IO ; i++)
- {
-
- com_ob = ccpOpenCom(i,&status);
- if (com_ob == NULL)
- {
- continue;
- }
-
- ccpAttachDevice(driver,com_ob,&s_fltObj[i],&s_nextObj[i]);
- }
- }
不必關心這個綁定是否成功,就算失敗,看下一個s_fltObj便可。這個數組中不爲NULL的成員表示已經綁定,爲NULL的成員則是沒有綁定成功或者綁定失敗的。這個函數須要一個DRIVER_OBJECT的指針。
得到實際數據
1.請求的區分
Windows的內核開發者們肯定了不少數據結構,例如:驅動對象(DriverObject),設備對象(DeviceObject),文件對象(FileObject)等,須要瞭解的是:
(1)每一個驅動程序只有一個驅動對象
(2)每一個驅動程序能夠生成若干個設備對象,這些設備對象從屬於一個驅動對象
(3)若干個設備(他們能夠屬於不一樣的驅動)依次綁定造成一個設備棧,老是最頂端的設備先接收到請求。
(4)IRP是上層設備之間傳遞請求的常見數據結構,但不是惟一的數據結構
串口設備接收到的都是IRP,所以只要對全部IRP進行過濾,就能夠獲得串口流過的數據。請求能夠經過IRP的主功能號區分。例以下面代碼:
- PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
- if (irpsp->MajorFunction == IRP_MJ_WRITE)
- {
-
- }
- else if (irpsp->MajorFunction == IRP_MJ_READ)
- {
-
- }
2.請求的結局
對請求的過濾,最終的結局有3種:
(1)請求被經過了,過濾不作任何事情,或者簡單的獲取請求的一些信息。可是請求自己不受干擾。
(2)請求直接被否決了,下層驅動根本收不到這個請求。
(3)過濾完成了這個請求。
串口過濾要捕獲兩種數據:一種是發送出的數據(也就是寫請求的數據),另外一種是接收的數據(也就是讀請求的數據)。爲了簡單起見,咱們只捕獲發送出去的請求。這樣,只須要採起第一種處理方法便可。
這種處理最爲簡單。首先調用IoSkipCurrentIrpStackLocation跳到當前棧空間;而後調用IoCallDriver把這個請求發送給真實的設備。請注意:由於真實的設備已經被過濾設備綁定,因此首先接收到IRP的是過濾設備對象。代碼以下:
- IoSkipCurrentIrpStackLocation(irp);
- status = IoCallDriver(s_nextObj[i],irp);
3.寫請求的數據
那麼,一個寫請求(也就是串口一次發送的數據)保存在哪呢?IRP的結構中有三個地方能夠描述緩衝區:一個是irp->MDLAddress,一個是irp->UserBuffer,一個是irp->AssociatedIrp.SystemBuffer.三種結構的具體區別參見(Windows驅動技術開發詳解__派遣函數)。
回到串口的問題,那麼串口的寫請求究竟是用哪一種方式呢?咱們不知道,可是能夠用下面方法得到:
- PBYTE buffer = NULL;
- if (IRP->MdlAddress != NULL)
- buffer = (PBYTE)MmGetSystemAddressForMdlSafe(IRP->MdlAddress);
- else
- buffer = (PBYTE)IRP->UserBuffer;
- if (buffer == NULL)
- buffer = (PBYTE)IRP->AssociatedIrp.SystemBuffer;
完整的代碼
1.完整的分發函數(派遣函數)
- NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)
- {
- PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
- NTSTATUS status;
- ULONG i,j;
-
-
-
- for (i = 0 ; i < CCP_MAX_COM_ID ; i++)
- {
- if (s_fltObj[i] == device)
- {
-
- if (irpsp->MajorFunction == IRP_MJ_POWER)
- {
-
- PoStartNextPowerIrp(irp);
- IoSkipCurrentIrpStackLocation(irp);
- return PoCallDriver(s_nextObj[i],irp);
- }
-
- }
-
- if (irpsp->MajorFunction == IRP_MJ_WRITE)
- {
-
- ULONG len = irpsp->Parameters.Write.Length;
-
- PUCHAR buf = NULL;
- if(irp->MdlAddress != NULL)
- buf = (PUCHAR)
- MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);
- else
- buf = (PUCHAR)irp->UserBuffer;
- if(buf == NULL)
- buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
-
-
- for (j = 0 ; j < len ; j++)
- {
- DbgPrint("comcap:Send Data:%2x\r\n",buf[j]);
- }
- }
-
- IoSkipCurrentIrpStackLocation(irp);
- return IoCallDriver(s_nextObj[i],irp);
-
- }
-
- irp->IoStatus.Information = 0;
- irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
- IoCompleteRequest(irp,IO_NO_INCREMENT);
- return STATUS_SUCCESS;
-
- }
2.動態卸載
前面說了如何綁定,可是沒說如何解除綁定。若是要把這個模塊作成能夠動態卸載的模塊,則必須提供一個卸載函數。咱們應該在卸載函數中完成解除綁定的功能,不然,一旦卸載必定會藍屏。
- #define DELAY_ONE_MICROSECOND (-10)
- #define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
- #define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
-
- VOID ccpUnload(PDRIVER_OBJECT drv)
- {
- ULONG i;
- LARGE_INTEGER interval;
-
-
- for (i = 0 ; i < CCP_MAX_COM_ID ; i++)
- {
- if(s_nextObj[i] != NULL)
- IoDeleteDevice(s_nextObj[i]);
- }
-
-
- interval.QuadPart = (5*1000 *DELAY_ONE_MICROSECOND);
- KeDelayExecutionThread(KernelMode,FALSE,&interval);
-
-
- for (i = 0 ; i < CCP_MAX_COM_ID ; i++)
- {
- if(s_fltObj[i] != NULL)
- IoDeleteDevice(s_fltObj[i]);
- }
- }
DriverEntry函數代碼:
- NTSTATUS DriverEntry(
- IN OUT PDRIVER_OBJECT DriverObject,
- IN PUNICODE_STRING RegistryPath
- )
- {
- DbgPrint("Enter Driver\r\n");
- size_t i;
-
- for (i = 0 ; i < IRP_MJ_MAXIMUM_FUNCTION ; i++)
- {
- DriverObject->MajorFunction[i] = ccpDispatch;
- }
-
- DriverObject->DriverUnload = ccpUnload;
-
-
- ccpAttachAllComs(DriverObject);
-
- return STATUS_SUCCESS;
- }
測試效果: