键盘过滤驱动

在笔者接触驱动到如今以来一以后大半个月的时间,从中让我深深的体会到了万事开头难,以及学习持之以恒的重要性。笔者也是个驱动新人,開始接触驱动的时候看着张帆的《Windows驱动开发技术具体解释》讲的挺细,对新手来说是个不错的学习资料,可是更重要的还是自己要多动手练习,笔者在学习到同步操作的相关知识的时候,实在是看天书。最后还是放弃了学习本书。再找了本楚狂人的资料学习,感觉本书对新手来说还是比較吃力的,当中笔者就是这样,非常多知识点不是非常明确,仅仅能凭借自己的感觉去做,只是造成的后果就是无情的蓝屏^_^。终于要的是笔者坚持下来了。

今天来分享下学习过程中,编写键盘过滤的心得。关于工作原理由于笔者也是一知半解,就不在阐述。

我们的目的就是将自己的驱动设备挂接/driver/kbdclass驱动下的全部设备,如图所看到的:

然后通过处理来达到过滤我们想要的按键信息。挂接后的驱动中的第一个设备就是我们的过滤设备,当有按键触发,按键信息首先会被我们自己写的设备所拦截,可是这时候拦截到的是没有处理的按键信息,那改怎么处理呢?我们去问键盘驱动,当我们拦截到按键IRP的时候先不做处理,给IRP设置完毕回调函数并传递给键盘驱动的设备。这样一来,当按键IRP被键盘驱动处理完毕之后就会运行我们的回调函数,这时我们在处理按键信息。当卸载我们的过滤设备的时候会有个麻烦就是会有个IRP已经设备了回调例程,而且在等待按键触发。假设这个IRP在没有处理之前就卸载掉我们的过滤驱动,就会引发按键蓝盘。为什么会蓝屏呢?由于这个IRP是已经被设置了回调函数,当IRP被处理完毕之后去找我们设置的回调函数,由于我们在IRP没有处理之前已经卸载了,所以这时IRP已经找不到回调函数了,所以导致蓝屏。大部分都的解决方式是在处理IRP的时候放置个计数器,当计数器不为0的时候说明还有IRP未完毕,这是卸载的时候就用while来一直等待这个IRP完毕,假设我们要是不按键盘的话,它会无休止的等待下去,而且也影响系统性能。

笔者通过相关资料的查阅,另个解决方式就是做个代理IRP,然后保存原来的IRP,由于我们能够取消自己的IRP。在卸载的时候先卸载我们的代理IRP,然后在发送原来保存的IRP,这样就非常好的攻克了无限的等待的BUG...可是笔者也没有找到相关代码,仅仅好自己动手试。经过一下午的測试,笔者发现我们仅仅须要做一个代理IRP就可以,并不须要保存原来的IRP,卸载的时候直接取消我们的IRP,并不须要又一次发送个IRP。以下我们来通过详细代码学习一下键盘过滤驱动。

首先:

//驱动入口
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath)
{
NTSTATUS status;
DbgPrint("驱动载入開始.../n");
pDriverObject->DriverUnload=FilterUnload;

//设置读取派遣函数
pDriverObject->MajorFunction[IRP_MJ_READ]=FilterDispatchRoutin;

BindDevice(pDriverObject);

DbgPrint("驱动载入结束.../n");

return STATUS_SUCCESS;
}

在主函数中,调用BindDevice来实现过滤驱动的创建与绑定,代码例如以下:

//设备类型
extern "C" POBJECT_TYPE IoDriverObjectType;

NTSTATUS BindDevice(PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
UNICODE_STRING uniNtNameString;

//要打开的驱动对象
PDRIVER_OBJECT KbdDriverObject = NULL;
//驱动对象的设备
PDEVICE_OBJECT kbdDeviceOjbect;

//初始化一个字符串,就是kbdclass驱动的名子
RtlInitUnicodeString(&uniNtNameString,KBD_DRIVER_NAME);

//依据名字打开驱动对象
status=ObReferenceObjectByName(
&uniNtNameString,
OBJ_CASE_INSENSITIVE,
NULL,
0,
IoDriverObjectType,
KernelMode,
NULL,
(PVOID*)&KbdDriverObject);

//假设失败了就直接返回
if(!NT_SUCCESS(status))
{
DbgPrint("打开设备失败.../n");
return status;
}

//调用ObReferenceObjectByName会导致对驱动对象的引用计数添加
//必须响应的调用解引用ObDereferenceObject
ObDereferenceObject(pDriverObject);
DbgPrint("打开成功,解除引用.../n");

//键盘驱动的第一个设备
kbdDeviceOjbect=KbdDriverObject->DeviceObject;
while(kbdDeviceOjbect!=NULL)
{
//创建并绑定过滤设备
CreateDevice(pDriverObject,kbdDeviceOjbect);
//下一个设备
kbdDeviceOjbect=kbdDeviceOjbect->NextDevice;
}

return status;
}

在这里说一下ObReferenceObjectByName函数,该方法没有被导出,知我我们在头文件里声明一下就可以使用,声明例如以下:

//依据名字获取设备对象,此函数没有公开,声明一下就能够直接使用了

extern "C" NTSTATUS ObReferenceObjectByName(
PUNICODE_STRING objectName,
ULONG Attributes,
PACCESS_STATE AccessState,
ACCESS_MASK DesiredAccess,
POBJECT_TYPE objectType,
KPROCESSOR_MODE accessMode,
PVOID ParseContext,
PVOID *Object);

在BindDevice方法中,调用了一个CreateDevice方法,该方法负责创建过滤设备,而且附加在目标设备上,详细代码例如以下:

//创建设备
NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject,IN PDEVICE_OBJECT oldDevObj)
{
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
//谁被扩展
PDEVICE_EXTENSION pDevExt;

status=IoCreateDevice(pDriverObject,
sizeof(PDEVICE_EXTENSION),
NULL,
oldDevObj->DeviceType,//设备类型须要和被附加的设备类型相等
0,
FALSE,//假设指定设备是独占的,大部分驱动程序设置这个值为FALSE,假设不是独占的话设置为TRUE.
&pDevObj);

if(!NT_SUCCESS(status))
{
DbgPrint("创建设备失败..../n");
return NULL;
}
pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension;
//存储设备对象
pDevExt->pDevice=pDevObj;
//绑定前原设备
pDevExt->poldDevice=oldDevObj;

//标志位
pDevObj->Flags |=oldDevObj->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);

//该标识指示I/O管理器对全部发送到控制设备对象的Open请求进行安全检測
pDevObj->Characteristics=oldDevObj->Characteristics;

//绑定设备
PDEVICE_OBJECT topDev = IoAttachDeviceToDeviceStack(pDevObj,oldDevObj);
if(topDev==NULL)
{
//假设绑定失败,销毁设备
IoDeleteDevice(pDevObj);
status=STATUS_UNSUCCESSFUL;
return status;
}

//将绑定的设备和原始设备放入设备扩展中
pDevExt->poldDevice=oldDevObj;
pDevExt->pbindDevice=topDev;

pDevObj->Flags=pDevObj->Flags & ~DO_DEVICE_INITIALIZING;
KdPrint(("绑定成功../n"));
return STATUS_SUCCESS;
}

通过以上代码能够实现过滤设备的绑定,绑定了之后还是主要处理派遣函数,功能例如以下:

//派遣函数
NTSTATUS FilterDispatchRoutin(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)
{
PIO_STACK_LOCATION currentIrpStack;
PDEVICE_EXTENSION pDevExt;

//得到设备扩展
pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension;

//得到当前irp包
currentIrpStack=IoGetCurrentIrpStackLocation(pIrp);
//将当前irp拷贝到下层设备irp堆栈
IoCopyCurrentIrpStackLocationToNext(pIrp);

//保存原来的irp
//pDevExt->tagIrp=pIrp;

//代理irp
pDevExt->proxyIrp=pIrp;

//设置当irp完毕时的回调例程
IoSetCompletionRoutine(pDevExt->proxyIrp,CallBackKbdFilter,pDevObj,TRUE,TRUE,TRUE);
DbgPrint("irp回调例程设置完毕.../n");
return IoCallDriver(pDevExt->poldDevice,pDevExt->proxyIrp);
}

注意的是在处理派遣函数的时候我们将IRP换成我们自己的IRP,这样就能达到取消IRP的目的,我们给IRP设置了回调函数,当IRP处理完毕的时候就去运行回调函数,回调函数例如以下:

// flags for keyboard status
#define S_SHIFT 1
#define S_CAPS 2
#define S_NUM 4
static int kb_status = S_NUM;
void __stdcall print_keystroke(UCHAR sch)
{
UCHAR ch = 0;
int off = 0;

if ((sch & 0x80) == 0) //make
{
if ((sch < 0x47) ||
((sch >= 0x47 && sch < 0x54) && (kb_status & S_NUM))) // Num Lock
{
ch = asciiTbl[off+sch];
}

switch (sch)
{
case 0x3A:
kb_status ^= S_CAPS;
break;

case 0x2A:
case 0x36:
kb_status |= S_SHIFT;
break;

case 0x45:
kb_status ^= S_NUM;
}
}
else //break
{
if (sch == 0xAA || sch == 0xB6)
kb_status &= ~S_SHIFT;
}

if (ch >= 0x20 && ch < 0x7F)
{
DbgPrint("%C /n",ch);
}

}

NTSTATUS CallBackKbdFilter( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context )
{
PIO_STACK_LOCATION currentIrp;
PKEYBOARD_INPUT_DATA keyData;

currentIrp=IoGetCurrentIrpStackLocation(Irp);

if(NT_SUCCESS(Irp->IoStatus.Status))
{
keyData=(PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer;

//DbgPrint("扫描码:%x",keyData->MakeCode);
DbgPrint("键盘 :%s",keyData->Flags?"弹起":"按下");
print_keystroke((UCHAR)keyData->MakeCode);
}
if( Irp->PendingReturned )
{
IoMarkIrpPending( Irp );
}
return Irp->IoStatus.Status;
}

函数就不说明了,主要就是对makecode的处理,只是在回调函数中引用了对比表,例如以下:

unsigned char asciiTbl[]={
0x00, 0x1B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x3D, 0x08, 0x09, //normal
0x71, 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, //caps
0x51, 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, //shift
0x51, 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 + shift
0x71, 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
};

就是卸载函数,在卸载的时候我们要删除设备和附加的设备,然后取消最后一个IRP,代码例如以下:

//卸载例程
void FilterUnload(IN PDRIVER_OBJECT pDriverObject)
{

//得到设备
PDEVICE_OBJECT pDevObj=pDriverObject->DeviceObject;
while(pDevObj!=NULL)
{
//设备扩展
PDEVICE_EXTENSION pDevExt=(PDEVICE_EXTENSION)pDevObj->DeviceExtension;

PDEVICE_OBJECT pTagObj=pDevExt->pbindDevice;

//解除绑定
if(pDevExt->pbindDevice!=NULL)
{
IoDetachDevice(pDevExt->pbindDevice);
}

//删除设备
if(pDevExt->pDevice!=NULL)
{
IoDeleteDevice(pDevExt->pDevice);
}

if(pDevExt->proxyIrp!=NULL)
{
if(CancelIrp(pDevExt->proxyIrp))
{
DbgPrint("取消成功。。。/n");
}
else
{
DbgPrint("取消失败。。。/n");
}
}
//下一个设备
pDevObj=pDevObj->NextDevice;

}
}

载函数中调用了个取消IRP的方法,代码例如以下:

BOOLEAN CancelIrp(PIRP pIrp)
{
if(pIrp==NULL)
{
DbgPrint("取消irp错误.../n");
return FALSE;
}
if(pIrp->Cancel || pIrp->CancelRoutine==NULL)
{
DbgPrint("取消irp错误.../n");
return FALSE;
}
if(FALSE==IoCancelIrp(pIrp))
{
DbgPrint("IoCancelIrp to irp错误.../n");
return FALSE;
}

//取消后重设此例为空
IoSetCancelRoutine(pIrp,NULL);
return TRUE;
}

整个键盘过滤驱动就完毕了,以后还得多多学习,多多总结。

转载请注明来自:http://blog.csdn.net/ms2146

时间: 2024-10-25 10:08:21

键盘过滤驱动的相关文章

键盘过滤驱动源码

#include <wdm.h> // Kbdclass驱动的名字 #define KBD_DRIVER_NAME L"\\Driver\\Kbdclass" typedef struct _C2P_DEV_EXT { // 这个结构的大小 ULONG NodeSize; // 过滤设备对象 PDEVICE_OBJECT pFilterDeviceObject; // 同时调用时的保护锁 KSPIN_LOCK IoRequestsSpinLock; // 进程间同步处理 K

4.1 技术原理 &amp; 4.2 键盘过滤框架

4.1 预备知识 符号链接:符号链接其实就是一个“别名”.可以用一个不同的名字来代表一个设备对象(实际上),符号链接可以指向任何有名字的对象. ZwCreateFile是很重要的函数.同名的函数实际上有两个:一个在内核中,一个在应用层.所以在应用程序中直接调用CreateFile,就可以引发对这个函数的调用. 它不但可以打开文件,而且可以打开设备对象(返回得到一个类似文件句柄的句柄).所以后面会常常看见应用程序为了交互内核而调用这个函数,这个函数最终调用NtCreateFile. PDO:Phs

4.3 键盘过滤的请求处理

4.3 键盘过滤的请求处理 4.3.1 通常处理 (1) 最通常的处理就是直接发到真实设备,跳过虚拟设备的处理.这里和前面的串口过滤的方法一样. NTSTATUS c2pDispatchGeneral( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { // 其他的分发函数,直接skip然后用IoCallDriver把IRP发送到真实设备 // 的设备对象. KdPrint(("Other Diapatch!")); IoSkipCurre

文件过滤驱动开发

文件过滤驱动 一.文件透明加解密 关键字:透明.文件过滤驱动.加密标识,缓存 文件过滤驱动最重要的两点是搞定加密标识和缓存管理 1.透明概念: 透明指的是用户在操作的时候,虽然后台在自动的进行加解密,但是用户根本就不知道加密的存在,就像中间隔了一层透明的玻璃一样. 透明的好处在于不改变用户的操作,一切都和加密之前一样,甚至在有些企业安装加密后都无需通知所有的员工,就像加密并不存在一样,只是加密文件到了企业安全环境的外部才会发现文件无法打开. 透明的程度也是加密软件一个很重要的方面,例如:正在编辑

摘除过滤驱动

开始本文之前先膜拜一下老V~ "无法F5的驱动,不是好驱动啊~不是好驱动啊~ " -----killvxk语录 这两天下午的时间,破解那个驱动保护搞得好痛苦,每次关机都要蓝一次,有时候设备还不工作,郁闷... 而且VMware用得很伤心,每次开GuestOS都得6分钟左右,要不是时间不多懒得换它,早就把它扔到垃圾堆里去了~~~~~~~~~~ 忘了今天主题了,摘除过滤驱动来着~ 某些驱动创建自己的Device之后调用IoAttachDeviceXXX函数来将自己的设备附加到目标设备上,成

Windbg对过滤驱动DriverEntry函数下断点技巧

方法1: 1> 先用DeviceTree.exe查看指定的过滤驱动的Load Address(加载地址) 2> 再用LordPE.EXE查看指定过滤驱动文件的入口点地址 3> 计算过滤驱动的DriverEntry函数内存地址 DriverEntry函数内存地址 = Load Address + 入口点地址 例子: 1> Load Address = 0xFAABF000 2> 入口地址 = 0x3400 3> Windbg下断点 bu 0xFAABF000+0x3400

文件过滤驱动实现目录重定向(一)good

文件过滤驱动拦截的IRP主要包括以下几个:IRP_MJ_CREATE,文件创建操作,文件的任何操作,都是从这里开始的.IRP_MJ_CLEANUP,文件的HANDLE句柄全部关闭会触发这个消息IRP_MJ_CLOSE,文件对象 FILE_OBJECT引用减为0,文件对象即将被删除时触发.IRP_MJ_READ.IRP_MJ_WRITE, 文件的读写操作IRP_MJ_QUERY_INFORMATION 查询文件信息,比如文件创建修改时间,文件大小等等.IRP_MJ_SET_INFORMATION

[转载]windows过滤驱动程序设计入门(驱动程序基本结构,设备栈,IRP栈和工作原理)

本文转载自: http://blog.csdn.net/arvon2012/article/details/7789724 最近在学习windows驱动设计,认真看了些教材后总结了我认为驱动中都会涉及到,也最重要的概念,和大家分享.如果有说的不对的请大家留言指出.谢谢! 这里主要是写概念,代码涉及的不多也不详细,但是我会说出涉及到的API,详细的使用细节大家可以自己动手搜搜.掌握下面的概念之后,看驱动开发的教材里的代码,或者理解教材里说的内容应该就顺利很多! 过滤驱动程序概括: 对于window

File System Minifilter Drivers(文件系统微型过滤驱动)

问题: 公司之前有一套文件过滤驱动,但是在实施过程中经常出现问题,现在交由我维护.于是在边看代码的过程中,一边查看官方资料,进行整理. 这套文件过滤驱动的目的只要是根据应用层下发的策略来控制对某些特定文件的控制,例如根据后缀名来决定是否允许查看,是否允许查看指定目录啊之类的功能. 介绍: MSDN上对可安装的文件系统驱动介绍http://msdn.microsoft.com/en-us/library/windows/hardware/ff548143(v=vs.85).aspx:其中树形结构菜