寒江独钓-键盘过滤学习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键盘中断的例子
- 寒江独钓-键盘过滤学习1传统型的键盘过滤
- windows7 《寒江独钓》传统键盘过滤驱动学习
- 驱动开发之 键盘过滤驱动--传统型键盘过滤
- 键盘过滤驱动(学习)
- 键盘过滤驱动学习
- <寒江独钓>Windows内核安全编程__传统键盘过滤程序
- 键盘过滤
- 第四章 键盘的过滤(1)
- 基于键盘过滤驱动的键盘助手
- 寒江独钓键盘过滤卸载蓝屏
- Windows内核安全编程:传统键盘过滤程序
- 键盘过滤驱动
- 键盘过滤驱动
- 键盘过滤驱动源代码
- 键盘过滤驱动
- 键盘过滤驱动
- 寒江独钓-键盘过滤学习2修改IDT + IOAPIC重定位表截获PS2键盘中断
- 8.2.3 键盘过滤模块的DriverEntry
- c#创建Dll动态链接库、C#使用动态链接库
- have a close looke at kideny of huamn
- x64 调用约定概述
- Deferred Shading
- Android 通过PHP连接Mysql
- 寒江独钓-键盘过滤学习1传统型的键盘过滤
- Centos6.5下升级Python 2.6.6 to python 2.7.3
- fork调用的形象描述
- ARM 中 CPSR_cxsf的意义
- 在PL/SQL中调用存储过程--oracle
- Errors running builder 'DeploymentBuilder' on project
- FLEX学习-4 FABridge技术
- 关于信噪比SNR【转载】1.http://well3216.blog.sohu.com/96624202.html
- android 四大组件之一 BroadcastReceiver 广播接收者