寒江独钓-键盘过滤学习1传统型的键盘过滤

来源:互联网 发布:网络聊天搭讪 编辑:程序博客网 时间:2024/05/16 06:02

最顶层的设备对象是驱动  Kbdclass 生成的设备对象 

中间层的设备对象是驱动i8042prt生成的设备对象

最底层的设备对象是驱动ACPI生成的设备对象


学习参考:   http://bbs.pediy.com/showthread.php?t=96205&highlight=IOAPIC

     http://bbs.pediy.com/showthread.php?t=135230&highlight=IOAPIC

键盘过滤分为几种方式:

1 传统型的键盘过滤(本次笔记)


2 修改IDT + IOAPIC重定位表截获PS2键盘中断的例子

CPP文件:

#include <wdm.h>#include <ntddkbd.h>#include "ctrl2cap.h"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;// flags for keyboard status#defineS_SHIFT1#defineS_CAPS2#defineS_NUM4//这里是一个标记,用来保存当前键盘的状态,其中有3个位,//CAPS LOCK  NUM LOCK  SHIFT 是否按下//shift 是按下生效,释放则无效//CAPS LOCK 是按一次生效,再按一次无效static int kb_status = S_NUM;void __stdcall print_keystroke(UCHAR sch){UCHARch = 0;intoff = 0;//一个键是扫描码是X  那么弹起就是X+0x80if ((sch & 0x80) == 0)//按下{if ((sch < 0x47) || //如果按下了字母/数字等可见字符((sch >= 0x47 && sch < 0x54) && (kb_status & S_NUM))) // Num Lock{//最终得到哪个字符由CAPS LOCK , NUM LOCK, SHIFT 这几个键的状态来决定//所以写在一张表中ch = asciiTbl[off+sch];}switch (sch){//caps lock 和NUM lock 键类似,都是按下两次 = 没按    //所以这里用XOR 来设备标志,按一次起作用,再按一次就不起作用了case 0x3A:kb_status ^= S_CAPS;break;//1 有两个键,左右各一个,扫描码互不相同//2 shift 键是按下起作用,弹起则作用消失,这里用 | 来设置case 0x2A:case 0x36:kb_status |= S_SHIFT;break;//num lock键case 0x45:kb_status ^= S_NUM;}}else//弹起{if (sch == 0xAA || sch == 0xB6)kb_status &= ~S_SHIFT;}if (ch >= 0x20 && ch < 0x7F)//32-126{DbgPrint("%C \n",ch);}}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 ); }// 这个函数是事实存在的,只是文档中没有公开。声明一下// 就可以直接使用了。NTSTATUSObReferenceObjectByName(                        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(KbdDriverObject);    }    // 这是设备链中的第一个设备    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_INTEGERlDelay;    PRKTHREAD CurrentThread;    //delay some time     lDelay = RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);    CurrentThread = KeGetCurrentThread();    // 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。    KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);    UNREFERENCED_PARAMETER(DriverObject); //换句话说 UNREFERENCED_PARAMETER 展开传递的参数或表达式。  //其目的是避免编译器关于未引用参数的警告    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,numKeys; PKEYBOARD_INPUT_DATA KeyData;      IrpSp = IoGetCurrentIrpStackLocation( Irp );     //  如果这个请求是成功的。很显然,如果请求失败了,这么获取     //   进一步的信息是没意义的。     if( NT_SUCCESS( Irp->IoStatus.Status ) )      {        // 获得读请求完成后输出的缓冲区        buf = Irp->AssociatedIrp.SystemBuffer;KeyData = (PKEYBOARD_INPUT_DATA)buf;        // 获得这个缓冲区的长度。一般的说返回值有多长都保存在        // Information中。        buf_len = Irp->IoStatus.Information;        numKeys = buf_len / sizeof(KEYBOARD_INPUT_DATA);        //… 这里可以做进一步的处理。我这里很简单的打印出所有的扫        // 描码。        //for(i=0;i<buf_len;++i)for(i=0;i<numKeys;++i)        {            //DbgPrint("ctrl2cap: %2x\r\n", buf[i]);DbgPrint("\n");DbgPrint("numKeys : %d",numKeys);DbgPrint("ScanCode: %x ", KeyData->MakeCode ); DbgPrint("%s\n", KeyData->Flags ?"Up" : "Down" );print_keystroke((UCHAR)KeyData->MakeCode);//这里是一个小测试,发现CAPS LOCK键 就改写为CTRL键if( KeyData->MakeCode == CAPS_LOCK) { KeyData->MakeCode = LCONTROL; }         }    }    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);/*1,完成例程本来就是设置在当前堆栈的下一层堆栈里,这相当于是一个规范,也可以用实际的IRP的返回来理解。在完成例程里,根据返回不同的状态值,IRP的控制流可能会发生相应的变化,比如:...STATUS_MORE_PROCESSING,这样,下层堆栈执行完成例程后,会将IRP的控制权交付给本层堆栈。从这个意义上讲,完成例程,只能放在下层堆栈,实际上,设计也是这样的。  2,拷贝当前堆栈的内容到下层堆栈,只是为了保证执行环境一样。在一个设备栈中,高层设备只能访问自己的设备栈或者下层设备栈,这就要求这个驱动必须要为下层设置IO堆栈,但不是必须的。每个堆栈中,context字段的值是唯一的,会标识一些pending等状态位,表示不同的完成状态,所以这个字段不可以随意复制*/    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; }

H文件:

#pragma once//概述这是一个比较常用的C/C++杂注,只要在头文件的最开始加入这条杂注,就能够保证头文件只被编译一次// Kbdclass驱动的名字#define KBD_DRIVER_NAME  L"\\Driver\\Kbdclass"#define KEY_UP 1 #define KEY_DOWN 0 #define LCONTROL ((USHORT)0x1D) #define CAPS_LOCK ((USHORT)0x3A) #define DELAY_ONE_MICROSECOND   (-10)#define DELAY_ONE_MILLISECOND   (DELAY_ONE_MICROSECOND*1000)#define DELAY_ONE_SECOND        (DELAY_ONE_MILLISECOND*1000)unsigned char asciiTbl[]={0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09,//normal0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x5B, 0x5D, 0x0D, 0x00, 0x61, 0x73,0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3B, 0x27, 0x60, 0x00, 0x5C, 0x7A, 0x78, 0x63, 0x76,0x62, 0x6E, 0x6D, 0x2C, 0x2E, 0x2F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,0x32, 0x33, 0x30, 0x2E,0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09,//caps0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x5B, 0x5D, 0x0D, 0x00, 0x41, 0x53,0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3B, 0x27, 0x60, 0x00, 0x5C, 0x5A, 0x58, 0x43, 0x56,0x42, 0x4E, 0x4D, 0x2C, 0x2E, 0x2F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,0x32, 0x33, 0x30, 0x2E,0x00, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x08, 0x09,//shift0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x7B, 0x7D, 0x0D, 0x00, 0x41, 0x53,0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x3A, 0x22, 0x7E, 0x00, 0x7C, 0x5A, 0x58, 0x43, 0x56,0x42, 0x4E, 0x4D, 0x3C, 0x3E, 0x3F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,0x32, 0x33, 0x30, 0x2E,0x00, 0x1B, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x2B, 0x08, 0x09,//caps + shift0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x7B, 0x7D, 0x0D, 0x00, 0x61, 0x73,0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x3A, 0x22, 0x7E, 0x00, 0x7C, 0x7A, 0x78, 0x63, 0x76,0x62, 0x6E, 0x6D, 0x3C, 0x3E, 0x3F, 0x00, 0x2A, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x2D, 0x34, 0x35, 0x36, 0x2B, 0x31,0x32, 0x33, 0x30, 0x2E};


















2 修改IDT + IOAPIC重定位表截获PS2键盘中断的例子
原创粉丝点击