进行WDM/IFS开发时,常需要开发一个GUI来同用户互动,并借此控制驱动程序的行为,或者向用户汇报底层信息。一个著名的例子是FileMon。这个程序功能全面,但其不足也很致命:通过轮询法来交互ring0/ring3的数据,经常造成系统资源不足,或者干脆挂起。
在用户模态下的进程间通信模式,可以移植到ring0/ring3的通信中,使得kernel进程中的流程与用户态进程进行同步。这种可能性来自于内核对象管理技术——所有的Mutex/Semaphore都是内核对象(这也是多线程程序设计所依赖的基本服务)。由于ring0/ring3中的进程都能访问这些“基本”内核对象,利用他们来做同步是理所当然的。这样的同步模式和平常使用没有多少区别,只是ring0程序在访问Mutex/Semaphore是步骤不同。
下文摘自一篇本科毕业论文,这个片断剖析了杀毒引擎中内核/用户态通信技术,十分有借鉴价值。
注:文中"Hooksys.sys"为NT Service的binary image, "guidll.dll"为用户态GUI进程的组件
Hooksys.sys中使用命名的信号量来唤醒ring3级线程。具体做法如下:首先在guidll.dll中调用CreateSemaphore创建一个命名信号量Hookopen并设为无信号状态,同时调用CreateThread创建一个线程。线程代码的入口处通过调用WaitForSingleObject在此信号量上等待被ring0钩子函数唤醒查毒。驱动程序这边则在初始化过程中通过未公开的例程ObReferenceObjectByName(\BaseNamedObjects\Hookopen)得到命名信号量对象Hookopen的指针,当它拦截到文件打开请求时调用KeReleaseSemaphore将Hookopen置为有信号状态唤醒ring3级等待检查打开文件的线程。其实guidll.dll共创建了两个命名信号量,还有一个Hookclose用于唤醒ring3级等待检查关闭文件的线程。
guidll.dll中使用命名的事件来唤醒暂时挂起等待查毒完毕的ring0钩子函数。具体做法如下:Hooksys.sys在其初始化过程中通过ZwCreateEvent函数创建一组命名事件对象(此处必须合理设置安全描述符,否则ring3线程将无法使用事件句柄)并得到其句柄,同时通过ObReferenceObjectByHandle得到句柄引用的事件对象的指针。然后Hooksys.sys将这一组事件句柄和指针对以及事件名保存在备用链表的每个元素中:ring3使用句柄,ring0使用指针。当钩子函数拦截到文件请求时它首先唤醒ring3查毒线程,然后马上调用KeWaitForSingleObject在一个事件\BaseNamedObjects\Hookxxxx上等待查毒的完成。而被唤醒的ring3查毒线程通过OpenEventA函数由事件名字得到其句柄,在结束查毒后发出一个SetEvent调用将事件置为有信号状态从而唤醒ring0挂起的钩子函数。当然,以上讨论仅限于打开文件操作,钩子函数在拦截到其它文件请求时并不调用KeWaitForSingleObject等待查毒的完成,而是唤醒ring3查毒线程后直接返回;相应的ring3查毒线程也就不必在查毒完成后调用SetEvent进行远程唤醒。