最近看 《樹木和獨釣 windows核心程式設計》,看到鍵盤過濾部分,做筆記,僅供參考,那裡被理解為是不正確的,同時,我們也希望大家指正。
如今來說一下傳統型鍵盤過濾,就是把自己的裝置物件繫結在KbdClass裝置物件之上。那麼傳送到KbdClass的IRP都會先經過自己的裝置物件,我們能夠在讀派遣函式中設定完畢例程,當IRP完畢後在完畢歷程中得到按鍵資訊。
KbdClass被稱為鍵盤類驅動,在windows中,類驅動一般是指統管一類裝置的驅動程式。無論是USB鍵盤,還是PS/2鍵盤均經過它,所以在這層做攔截,能獲得非常好的通用性。
用工具Devicetree檢視
安裝 《寒江獨釣 windows核心程式設計》中例項 ctl2cap。再次檢視
程式碼例如以下:
#include <wdm.h>
// Kbdclass驅動的名字
#define KBD_DRIVER_NAME L"\\Driver\\Kbdclass"
typedef struct _C2P_DEV_EXT
{
// 這個結構的大小
ULONG NodeSize;
// 過濾裝置物件
PDEVICE_OBJECT pFilterDeviceObject;
// 同一時候呼叫時的保護鎖
KSPIN_LOCK IoRequestsSpinLock;
// 程式間同步處理
KEVENT IoInProgressEvent;
// 繫結的裝置物件
PDEVICE_OBJECT TargetDeviceObject;
// 繫結前底層裝置物件
PDEVICE_OBJECT LowerDeviceObject;
} C2P_DEV_EXT, *PC2P_DEV_EXT;
NTSTATUS
c2pDevExtInit(
IN PC2P_DEV_EXT devExt,
IN PDEVICE_OBJECT pFilterDeviceObject,
IN PDEVICE_OBJECT pTargetDeviceObject,
IN PDEVICE_OBJECT pLowerDeviceObject )
{
memset(devExt, 0, sizeof(C2P_DEV_EXT));
devExt->NodeSize = sizeof(C2P_DEV_EXT);
devExt->pFilterDeviceObject = pFilterDeviceObject;
KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent, FALSE);
devExt->TargetDeviceObject = pTargetDeviceObject;
devExt->LowerDeviceObject = pLowerDeviceObject;
return( STATUS_SUCCESS );
}
// 這個函式是事實存在的。僅僅是文件中沒有公開。宣告一下
// 就能夠直接使用了。
NTSTATUS ObReferenceObjectByName( PUNICODE_STRING ObjectName, ULONG Attributes, PACCESS_STATE AccessState, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID ParseContext, PVOID *Object ); extern POBJECT_TYPE IoDriverObjectType; ULONG gC2pKeyCount = 0; PDRIVER_OBJECT gDriverObject = NULL; // 這個函式經過改造。能開啟驅動物件Kbdclass,然後繫結 // 它以下的全部的裝置: NTSTATUS c2pAttachDevices( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { NTSTATUS status = 0; UNICODE_STRING uniNtNameString; PC2P_DEV_EXT devExt; PDEVICE_OBJECT pFilterDeviceObject = NULL; PDEVICE_OBJECT pTargetDeviceObject = NULL; PDEVICE_OBJECT pLowerDeviceObject = NULL; PDRIVER_OBJECT KbdDriverObject = NULL; KdPrint(("MyAttach\n")); // 初始化一個字串。就是Kdbclass驅動的名字。 RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME); // 請參照前面開啟裝置物件的樣例。僅僅是這裡開啟的是驅動物件。 status = ObReferenceObjectByName ( &uniNtNameString, OBJ_CASE_INSENSITIVE, NULL, 0, IoDriverObjectType, KernelMode, NULL, &KbdDriverObject ); // 假設失敗了就直接返回 if(!NT_SUCCESS(status)) { KdPrint(("MyAttach: Couldn't get the MyTest Device Object\n")); return( status ); } else { // 這個開啟須要解應用。
早點解除了免得之後忘記。 ObDereferenceObject(DriverObject); } // 這是裝置鏈中的第一個裝置 pTargetDeviceObject = KbdDriverObject->DeviceObject; // 如今開始遍歷這個裝置鏈 while (pTargetDeviceObject) { // 生成一個過濾裝置。這是前面讀者學習過的。這裡的IN巨集和OUT巨集都是 // 空巨集,僅僅有標誌性意義。表明這個引數是一個輸入或者輸出引數。
status = IoCreateDevice( IN DriverObject, IN sizeof(C2P_DEV_EXT), IN NULL, IN pTargetDeviceObject->DeviceType, IN pTargetDeviceObject->Characteristics, IN FALSE, OUT &pFilterDeviceObject ); // 假設失敗了就直接退出。
if (!NT_SUCCESS(status)) { KdPrint(("MyAttach: Couldn't create the MyFilter Filter Device Object\n")); return (status); } // 繫結。pLowerDeviceObject是繫結之後得到的下一個裝置。也就是 // 前面經常說的所謂真實裝置。 pLowerDeviceObject = IoAttachDeviceToDeviceStack(pFilterDeviceObject, pTargetDeviceObject); // 假設繫結失敗了,放棄之前的操作,退出。 if(!pLowerDeviceObject) { KdPrint(("MyAttach: Couldn't attach to MyTest Device Object\n")); IoDeleteDevice(pFilterDeviceObject); pFilterDeviceObject = NULL; return( status ); } // 裝置擴充套件!以下要具體講述裝置擴充套件的應用。
devExt = (PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension); c2pDevExtInit( devExt, pFilterDeviceObject, pTargetDeviceObject, pLowerDeviceObject ); // 以下的操作和前面過濾串列埠的操作基本一致。這裡不再解釋了。 pFilterDeviceObject->DeviceType=pLowerDeviceObject->DeviceType; pFilterDeviceObject->Characteristics=pLowerDeviceObject->Characteristics; pFilterDeviceObject->StackSize=pLowerDeviceObject->StackSize+1; pFilterDeviceObject->Flags |= pLowerDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE) ; //next device pTargetDeviceObject = pTargetDeviceObject->NextDevice; } return status; } VOID c2pDetach(IN PDEVICE_OBJECT pDeviceObject) { PC2P_DEV_EXT devExt; BOOLEAN NoRequestsOutstanding = FALSE; devExt = (PC2P_DEV_EXT)pDeviceObject->DeviceExtension; __try { __try { IoDetachDevice(devExt->TargetDeviceObject); devExt->TargetDeviceObject = NULL; IoDeleteDevice(pDeviceObject); devExt->pFilterDeviceObject = NULL; DbgPrint(("Detach Finished\n")); } __except (EXCEPTION_EXECUTE_HANDLER){} } __finally{} return; } #define DELAY_ONE_MICROSECOND (-10) #define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000) #define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000) VOID c2pUnload(IN PDRIVER_OBJECT DriverObject) { PDEVICE_OBJECT DeviceObject; PDEVICE_OBJECT OldDeviceObject; PC2P_DEV_EXT devExt; LARGE_INTEGER lDelay; PRKTHREAD CurrentThread; //delay some time lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND); CurrentThread = KeGetCurrentThread(); // 把當前執行緒設定為低實時模式。以便讓它的執行儘量少影響其它程式。 KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY); UNREFERENCED_PARAMETER(DriverObject); KdPrint(("DriverEntry unLoading...\n")); // 遍歷全部裝置並一律解除繫結 DeviceObject = DriverObject->DeviceObject; while (DeviceObject) { // 解除繫結並刪除全部的裝置 c2pDetach(DeviceObject); DeviceObject = DeviceObject->NextDevice; } ASSERT(NULL == DriverObject->DeviceObject); while (gC2pKeyCount) { KeDelayExecutionThread(KernelMode, FALSE, &lDelay); } KdPrint(("DriverEntry unLoad OK!\n")); return; } NTSTATUS c2pDispatchGeneral( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { // 其它的分發函式,直接skip然後用IoCallDriver把IRP傳送到真實裝置 // 的裝置物件。 KdPrint(("Other Diapatch!")); IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(((PC2P_DEV_EXT) DeviceObject->DeviceExtension)->LowerDeviceObject, Irp); } NTSTATUS c2pPower( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PC2P_DEV_EXT devExt; devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension; PoStartNextPowerIrp( Irp ); IoSkipCurrentIrpStackLocation( Irp ); return PoCallDriver(devExt->LowerDeviceObject, Irp ); } NTSTATUS c2pPnP( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PC2P_DEV_EXT devExt; PIO_STACK_LOCATION irpStack; NTSTATUS status = STATUS_SUCCESS; KIRQL oldIrql; KEVENT event; // 獲得真實裝置。
devExt = (PC2P_DEV_EXT)(DeviceObject->DeviceExtension); irpStack = IoGetCurrentIrpStackLocation(Irp); switch (irpStack->MinorFunction) { case IRP_MN_REMOVE_DEVICE: KdPrint(("IRP_MN_REMOVE_DEVICE\n")); // 首先把請求發下去 IoSkipCurrentIrpStackLocation(Irp); IoCallDriver(devExt->LowerDeviceObject, Irp); // 然後解除繫結。 IoDetachDevice(devExt->LowerDeviceObject); // 刪除我們自己生成的虛擬裝置。 IoDeleteDevice(DeviceObject); status = STATUS_SUCCESS; break; default: // 對於其它型別的IRP。全部都直接下發就可以。 IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->LowerDeviceObject, Irp); } return status; } // 這是一個IRP完畢回撥函式的原型 NTSTATUS c2pReadComplete( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PIO_STACK_LOCATION IrpSp; ULONG buf_len = 0; PUCHAR buf = NULL; size_t i; IrpSp = IoGetCurrentIrpStackLocation( Irp ); // 假設這個請求是成功的。非常顯然。假設請求失敗了,這麼獲取 // 進一步的資訊是沒意義的。
if( NT_SUCCESS( Irp->IoStatus.Status ) ) { // 獲得讀請求完畢後輸出的緩衝區 buf = Irp->AssociatedIrp.SystemBuffer; // 獲得這個緩衝區的長度。一般的說返回值有多長都儲存在 // Information中。 buf_len = Irp->IoStatus.Information; //… 這裡能夠做進一步的處理。
我這裡非常easy的列印出全部的掃 // 描碼。 for(i=0;i<buf_len;++i) { DbgPrint("ctrl2cap: %2x\r\n", buf[i]); } } gC2pKeyCount--; if( Irp->PendingReturned ) { IoMarkIrpPending( Irp ); } return Irp->IoStatus.Status; } NTSTATUS c2pDispatchRead( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { NTSTATUS status = STATUS_SUCCESS; PC2P_DEV_EXT devExt; PIO_STACK_LOCATION currentIrpStack; KEVENT waitEvent; KeInitializeEvent( &waitEvent, NotificationEvent, FALSE ); if (Irp->CurrentLocation == 1) { ULONG ReturnedInformation = 0; KdPrint(("Dispatch encountered bogus current location\n")); status = STATUS_INVALID_DEVICE_REQUEST; Irp->IoStatus.Status = status; Irp->IoStatus.Information = ReturnedInformation; IoCompleteRequest(Irp, IO_NO_INCREMENT); return(status); } // 全域性變數鍵計數器加1 gC2pKeyCount++; // 得到裝置擴充套件。目的是之後為了獲得下一個裝置的指標。 devExt = (PC2P_DEV_EXT)DeviceObject->DeviceExtension; // 設定回撥函式並把IRP傳遞下去。 之後讀的處理也就結束了。 // 剩下的任務是要等待讀請求完畢。
currentIrpStack = IoGetCurrentIrpStackLocation(Irp); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine( Irp, c2pReadComplete, DeviceObject, TRUE, TRUE, TRUE ); return IoCallDriver( devExt->LowerDeviceObject, Irp ); } NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { ULONG i; NTSTATUS status; KdPrint (("c2p.SYS: entering DriverEntry\n")); // 填寫全部的分發函式的指標 for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DriverObject->MajorFunction[i] = c2pDispatchGeneral; } // 單獨的填寫一個Read分發函式。由於要的過濾就是讀取來的按鍵資訊 // 其它的都不重要。這個分發函式單獨寫。 DriverObject->MajorFunction[IRP_MJ_READ] = c2pDispatchRead; // 單獨的填寫一個IRP_MJ_POWER函式。這是由於這類請求中間要呼叫 // 一個PoCallDriver和一個PoStartNextPowerIrp,比較特殊。 DriverObject->MajorFunction [IRP_MJ_POWER] = c2pPower; // 我們想知道什麼時候一個我們繫結過的裝置被解除安裝了(比方從機器上 // 被拔掉了?)所以專門寫一個PNP(即插即用)分發函式 DriverObject->MajorFunction [IRP_MJ_PNP] = c2pPnP; // 解除安裝函式。 DriverObject->DriverUnload = c2pUnload; gDriverObject = DriverObject; // 繫結全部鍵盤裝置 status =c2pAttachDevices(DriverObject, RegistryPath); return status; }
版權宣告:本文部落格原創文章,部落格,未經同意,不得轉載。