鍵盤的過濾(綁定驅動kdbclass的所有設備對象)

一、首先irp的傳遞流程

1、I/O管理器創建一個空的IRP,然後將該IRP沿設備堆棧向下傳遞,比如IRP_MJ_CREATE、IRP_MJ_READ

2、如果驅動程序設置的分發函數捕獲到IRP_MJ_CREATE,那麼可以在這個分發函數(CREATEdispatch)中對該IRP進行操作,比如IoSetCompletionRoutine,這樣當該IRP返回的時候,我們的驅動程序就可以再次捕捉到該IRP,這時候可以用來讀取其中的數據,設置返回值等。

       如果沒有設置分發函數來捕獲這個IRP,那麼就直接將該IRP傳遞給下層的驅動程序,這時候需要將lower DeviceObject的堆棧環境設置成和我們的過濾驅動的堆棧環境相同。需調用兩個函數
IoGetCurrentIrpStackLocation and IoGetNextIrpStackLocation. 然後調用IoCallDriver將IRP傳遞給lower設備對象

3、IRP繼續向下傳遞

4、如果該IRP完成了,那麼CompletionRoutine就會被再次調用,之後沿着設備堆棧一直往上傳遞,返回給用戶態的應用程序。

二、鍵盤過濾的基礎

1、符號連接:
符號連接,其實就是一個別名.可以用一個不同的名字代表一個設備對象.

2.、csrss.exe中的win32!RawInputThread通過一個GUID(GUID_CLASS_KEYBOARD)

獲得鍵盤設備棧中PDO符號連接名.

3、PS/2鍵盤設備棧,
最頂層的設備對象是驅動Kbdclass生成的設備對象
中間層的設備對象是驅動i8042prt生成的設備對象
最底層的設備對象是驅動ACPI生成的設備對象.


CPU與鍵盤交互方式是中斷和讀取端口

一個鍵需要兩個掃描碼.
按下的掃描碼爲x,則同一個鍵彈起爲x+0x80

windows xp下端口號與中斷號固定
中斷號爲0x93,端口號爲0x60

三、鍵盤過濾的代碼(基礎)

///
/// @file		    ctrl2cap.c
/// @author	wowocock
/// @date		2009-1-27
/// 

#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;

		if(Irp->MdlAddress != NULL)
			buf = 
			(PUCHAR)
			MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority);
		else
			buf = (PUCHAR)Irp->UserBuffer;
		if(buf == NULL)
			buf = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
        // 獲得這個緩衝區的長度。一般的說返回值有多長都保存在
        // Information中。
        buf_len = Irp->IoStatus.Information;



        //… 這裏可以做進一步的處理。我這裏很簡單的打印出所有的掃
        // 描碼。
        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")); 

	__asm int 3;
    // 填寫所有的分發函數的指針
    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; 
}











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章