如何构造一个简单的USB过滤驱动程序

本文分三部分来介绍如何构造一个简单的USB过滤驱动程序,包括“基本原理”、“程序的实现”、“使用INF安装”。此文的目的在于希望读者了解基本原理后,可以使用除DDK以外最流行也最方便的驱动开发工具DriverStudio来实现一个自己的过滤驱动,并正确地安装。

一、基本原理    
我们知道,WDM(和KDM)是分层的,在构造设备栈时,IO管理器可以使一个设备对象附加到另外一个初始驱动程序创建的设备对象上。与初始设备对象相关的驱动程序决定的IRP,也将被发送到附加的设备对象相关的驱动程序上。这个被附加的驱动程序便是过滤驱动程序。如右图,过滤驱动可以在设备栈的任何层次中插入。IO管理器发出的IRP将会沿着右图的顺序从上往下传递并返回。因此,我们可以使用过滤驱动程序来检查、修改、完成它接收到的IRP,或者构造自己的IRP。    
     上面这种文字是很枯燥的,好在“前人”已经写过一些范例以供我们更好地理解这些概念。读过Waltz Oney的《Programming Windows Driver Mode》一书的读者大概都知道Waltz Oney提供的范例中有一个关于USB过滤器(第九章)的例子,而在此基础上,《USB Design By Example》(http://www.usb-by-example.com)的作者John Hyde实现了一个USB键盘过滤驱动程序,即给此程序增加了一个“拦截(Intercept)”功能来处理USB键盘的Report以实现特定的功能:当驱动程序在IRP_MJ_INTERNAL_DEVICE_CONTROL设置的完成例程从USB设备拦截到一个Get_Report_Descriptor时,拦截程序将此Descriptor中的USAGE值从“Keyboard”改为“UserDefined”,再返回给系统。
我们可以从这个例子中获得一些灵感,比如,在Win2k下,键盘是由OS独占访问的,我们可以通过这种方式使之可以让用户自由访问;我们也可以拦截其他Report_Descriptor,将部分键重新定义,以满足特殊的要求;如果你愿意再做一个用户态的程序,你还可以将你拦截到的键值传递给你的用户态程序,以实现象联想、实达等国内电脑大厂出品的那些键盘上的各种实用的功能。

二、程序的实现
Waltz Oney和John Hyde的例子已经写得很详细了,读者可以不用修改一个字节便顺利地编译生成一个过滤驱动程序。本文的目的在于使用DriverStudio组件Driverworks来实现同样的功能。
相信读者读到这篇文章时,已经对DriverStudio有了很多的了解。DriverStudio作为一个以C++为基础的“快速”驱动开发工具,它封装了基本上所有的DDK的函数,其集成在VC++中的DriverWizard,可以很方便地引导你完成设备驱动程序开发的全过程,能根据你的硬件种类自动生成设备驱动程序源代码,并提供了很多范例程序。当然,这些例子中便包含一个USB Filter驱动程序的框架。在不侵犯版权的前提下,充分利用现有共享的、免费的、授权的代码是我们的一贯作法。我们下面便以此范例为基础来作修改。
我们的目的是做一个HID小驱动程序hidusb.sys的Lower Filter,它附加在“人机接口设备” ,通过拦截USB的Get_Report_Descriptor来修改其返回值,当它发现该Descriptor的Usage 为“Keyboard”时,将其改为“UserDefined”,如此我们便可以完全控制这只键盘。具体做法是,拦截IRP_MJ_INTERNAL_DEVICE_CONTROL,并检查其IOCTL代码及URB,如果满足IOCTRL功能代码为IOCTL_INTERNAL_USB_SUBMIT_URB以及URB功能代码为URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE的条件,即上层驱动发来Get_Report_Descriptor请求时,设置一个完成例程,在这个完成例程中,我们将判断Usage的值,将Usage由“6(Keyboard)”时,将其改为“0(UserDefined)”。
打开C:\Program Files\NuMega\DriverStudio\DriverWorks\Examples\wdm\usbfilt目录(具体目录依你的DriverStudio所安装的目录不同而不同) ,再打开工程文件usbfilt.dsw,我们先看一下代码。
程序由两个类组成,一个是Driver类,一个是Device类。Driver类包括:
    入口函数DriverEntry:
DECLARE_DRIVER_CLASS(UsbFilterDriver, NULL)
/////////////////////////////////////////////////////////////////////
// Driver Entry
//
NTSTATUS UsbFilterDriver::DriverEntry(PUNICODE_STRING RegistryPath)
{
     T << "UsbFilterDriver::DriverEntry\n";

     m_Unit = 0;
     return STATUS_SUCCESS;

     // The following macro simply allows compilation at Warning Level 4
     // If you reference this parameter in the function simply remove the macro.
     UNREFERENCED_PARAMETER(RegistryPath);
}
    AddDevice函数
NTSTATUS UsbFilterDriver::AddDevice(PDEVICE_OBJECT Pdo)
{
     T << "UsbFilterDriver::AddDevice\n";
     UsbFilterDevice * pFilterDevice = new (
             static_cast<PCWSTR>(NULL),
             FILE_DEVICE_UNKNOWN,
             static_cast<PCWSTR>(NULL),
             0,
             DO_DIRECT_IO
             )
         UsbFilterDevice(Pdo, m_Unit);
     if (pFilterDevice)
     {
         NTSTATUS status = pFilterDevice->ConstructorStatus();
         if ( !NT_SUCCESS(status) )
         {
             T << "Failed to construct UsbFilterDevice"
               << (ULONG) m_Unit
               << " status = "
               << status
               << "\n";

             delete pFilterDevice;
         }
         else
         {
             m_Unit++;
         }
         return status;
     }
     else
     {
         T << "Failed to allocate UsbFilterDevice"
           << (ULONG) m_Unit
           << "\n";
         return STATUS_INSUFFICIENT_RESOURCES;
     }
}

     这两段代码基本上和自动生成的代码差不多。AddDevice的作用是构造一个过滤器的实例。
关键的代码在Device类。在这个类里,我们把过滤器插入设备栈,并拦截IRP,用自己的完成例程来实现特定的功能。
Device构造函数
UsbFilterDevice::UsbFilterDevice(PDEVICE_OBJECT Pdo, ULONG Unit) :
     KWdmFilterDevice(Pdo, NULL)
{
     T << "UsbFilterDevice::UsbFilterDevice\n";
     // Check constructor status
     if ( ! NT_SUCCESS(m_ConstructorStatus) )
     {
         return;
     }
     // Remember our unit number
     m_Unit = Unit;
     // initialize the USB lower device
     m_Usb.Initialize(this, Pdo);
     NTSTATUS status = AttachFilter(&m_Usb); //Attach the filter
     if(!NT_SUCCESS(status))
         {
         m_ConstructorStatus = status;
         return;
         }
     SetFilterPowerPolicy();
     SetFilterPnpPolicy();
}
在DDK中,我们用IoAttachDevice将设备对象插入设备栈中。DriverStudio封装了这个函数。在DriverStudio中,其他驱动程序需要用Initialize来初始化设备对象和接口,对于过滤驱动,我们关键是需要Attachfilter将其附加在堆栈中。
对于大部分如IRP_MJ_SYSTEM_CONTROL等IRP,我们所做的只需用PassThrough(Irp)将其直接往设备栈下层传递,不需要做任何工作。这些代码我们就不一一列举了。下面的部分才是本文的关键。
     我们知道,HIDUSB.SYS是使用内部IOCTRL发出URB给USB类驱动程序(USBD)读取数据的,那么,HIDUSB首先必须构造一个IRP_MJ_INTERNAL_DEVICE_CONTROL,它的IOCTL功能码为IOCTL_INTERNAL_USB_SUBMIT_URB(发出URB的内部IOCTL)。另外,因为我们要检查并修改的是USB键盘某个接口的报告描述,那么这个URB应该是URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE,如下:
NTSTATUS UsbFilterDevice::InternalDeviceControl(KIrp I)
{
     T << "UsbFilterDevice::InternalDeviceControl\n";
     // Pass through IOCTLs that are not submitting an URB
//不是我们感兴趣的IOCTL不要理它
     if (I.IoctlCode() != IOCTL_INTERNAL_USB_SUBMIT_URB)
         return DefaultPnp(I);

     PURB p = I.Urb(CURRENT);     // get URB pointer from IRP

//不是我们感兴趣的URB,也不要理它,
     if (p->UrbHeader.Function !=
URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE)
         return DefaultPnp(I);
//符合要求的IRP才被设置完成例程
     return PassThrough(I, LinkTo(DeviceControlComplete), this);
}
在设置好条件以后,再来实现完成例程。所有的检查、修改等动作都是在完成例程里面完成的。
NTSTATUS UsbFilterDevice::DeviceControlComplete(KIrp I)
{
     PURB p = I.Urb(CURRENT);
     if(p)
     {
//拦截到设备返回的描述表,
         char* DescriptorBuffer = (char*)p->UrbControlDescriptorRequest.TransferBuffer;
//指向第三个字节,表示设备Usage属性的值
         DescriptorBuffer += 3;
//如果值为6则改成0,6表示hid键盘,0表示未知设备
//在设备管理器里面,原来的hid兼容键盘就不复存在了,取而代之的则是hid兼容设备
         if ((*DescriptorBuffer&0xff) == 6)
             *DescriptorBuffer = 0;
     }
     return I.Status();
}
读者可以对照DriverWorks中的例子,直接替换掉(或者修改)上面这两个函数,再编译一下,便可以得到一个完整的键盘过滤器驱动程序。
其实,只要弄清楚了我们需要做些什么动作,在DriverStudio里面只需要写少量的关键代码,便可实现我们的要求,其余的大部分工作,或有范例可供参考,或有Driver Wizard自动生成。
     从上面可以看出,我们只需要修改这两个函数,拦截合适的IRP,便可以在完成例程里面实现我们特定的要求。正如开头所说,我们也可以拦截其他的IRP,拦截其他的URB,或者拦截特定键盘的按键键值,将之传递到用户态,以方便实现联想、实达等随机配备的多功能键盘的功能。

三、使用INF安装驱动
     在完成了驱动以后,还必须把它安装到系统里面,驱动程序才会起作用。一般来说,我们都必须为我们的驱动程序提供一个inf文件,以便于用户安装或者维护。对于新手来说,过滤驱动程序的inf或许有些棘手。所以,针对本文所描述的驱动,我们提供一个Win98下的安装范例usbkey.inf,范例中“;”后的文字是注解,以方便读者理解。

; usbkey.INF  
;
; Installs Lower Level Filter for a HID keyboard device
;
; (c) Copyright 2001 SINO Co., Ltd.
;     
[Version]
;”CHICAGO”表示Win9x平台
Signature="$CHICAGO$"
;键盘所属类名
Class=HID
ClassGUID={745a17a0-74d3-11d0-b6fe-00a0c90f57da}
;驱动程序提供者,此信息会显示在设备属性的“常规”页
Provider=%USBDBE%
LayoutFile=layout.inf
;显示在驱动程序文件详细资料窗口
DriverVer=11/12/2001,4.10.2222.12

;[ControlFlags]
;ExcludeFromSelect = *

;驱动程序安装目录,inf会将我们的驱动程序安装到如下目录
;记得Destinationdir后面一定要带一个“s”
[DestinationDirs]
DefaultDestDir = 10,system32\drivers

;要增加的注册表项
[ClassInstall]
Addreg=HIDClassReg

[HIDClassReg]
HKR,,,,%HID.ClassName%
HKR,,Icon,,-20

;制造商
[Manufacturer]
%USBDBE%=USBDBE

[USBDBE]
;我们所要附加过滤驱动程序的设备ID。这个ID可以从IC的规范上得来,也可以
;用hidview.exe读出,或者从注册表HKLM\Enum\hid和usb项找出
%HID.DeviceDesc%     = Keypad_Inst, USB\VID_05AF&PID_0805&MI_00

;要安装的文件和需要修改的注册表项
;Install usbkey driver
[Keypad_Inst]
CopyFiles=Keypad_Inst.CopyFiles
AddReg=Keypad_Inst.AddReg

[Keypad_Inst.CopyFiles]
hidusb.sys
hidparse.sys
hidclass.sys
usbfilt.sys

[Keypad_Inst.AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,"hidusb.sys"

[Keypad_Inst.HW]
AddReg=Keypad_Inst.AddReg.HW

;Lowerfilters表示是低层过滤驱动,如果是上层过滤驱动,则必须改为upperfilters
[Keypad_Inst.AddReg.HW]
HKR,,"LowerFilters",0x00010000,"usbfilt.sys"

;HID设备所需要安装的文件和注册表中需要修改的地方
;Install USBHIDDevice
[USBHIDDevice]
CopyFiles=USBHIDDevice.Copy
AddReg=USBHIDDevice.AddReg

[USBHIDDevice.Copy]
hidclass.sys
hidusb.sys
hidparse.sys

[USBHIDDevice.AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,"hidusb.sys"

;以下定义需要在上面某些地方使用时替换的字符串
[strings]
USBDBE              = "SINO Co., Ltd."
HID.DeviceDesc        = "SINO USB MultiKeyboard"
HID.HIDDeviceDesc     = "Human Interface Devices"
HID.DefaultDevice     = "HID Default Device"
HID.ClassName         = "Human Input Devices (HID)"
HID.SvcDesc           = "Microsoft HID Class Driver"

其实最简单的写inf的方式,是找一些类似设备的inf文件或范例来修改。在不侵权的前提下,充分利用现有资源是我们的一贯原则。

时间: 2024-08-04 05:55:37

如何构造一个简单的USB过滤驱动程序的相关文章

使用DriverStudio构造USB过滤驱动程序

本文分三部分来介绍如何构造一个简单的USB过滤驱动程序,包括"基本原理"."程序的实现"."使用INF安装".此文的目的在于希望读者了解基本原理后,可以使用除DDK以外最流行也最方便的驱动开发工具DriverStudio来实现一个自己的过滤驱动,并正确地安装. 一.基本原理 我们知道,WDM(和KDM)是分层的,在构造设备栈时,IO管理器可以使一个设备对象附加到另外一个初始驱动程序创建的设备对象上.与初始设备对象相关的驱动程序决定的IRP,也将被

一个简单的关键字过滤算法

早上看到老赵的<一个较完整的关键字过滤解决方案(上)>文章,讲到怎样在项目中嵌 入过滤方案的问题,以及提到 xingd 和 sumtec 两位大师发表的系列互拼的文章,在此我也 忍不住谈谈自己遇到的问题以及一个的简化版的算法. 因为过滤关键字机制到处可见,于是聪明的网友就会想到各种各样的方法突破,例如: 1.中文会用繁体字的方法避开关键字扫描 2.在关键字中间插入无意思的特殊字符,例如 * & # @ 等,而且个数可变 3.使用谐音或拆字法变换关键字 在实现自己的算法时也有些问题: 4

艾伟:一个简单的关键字过滤算法

早上看到老赵的<一个较完整的关键字过滤解决方案(上)>文章,讲到怎样在项目中嵌入过滤方案的问题,以及提到 xingd 和 sumtec 两位大师发表的系列互拼的文章,在此我也忍不住谈谈自己遇到的问题以及一个的简化版的算法. 因为过滤关键字机制到处可见,于是聪明的网友就会想到各种各样的方法突破,例如: 1.中文会用繁体字的方法避开关键字扫描2.在关键字中间插入无意思的特殊字符,例如 * & # @ 等,而且个数可变3.使用谐音或拆字法变换关键字 在实现自己的算法时也有些问题: 4.随着时

创建一个简单的 Compute Grid 并行批处理应用程序

简介 批处理是业务系统的一个重要方面,它用在帐单系统或报告生成,以及一天 结束时的结算系统等领域中.随着业务系统在全球被夜以继日的使用,批处理窗口变得越来 越窄,这使高效的批处理系统成为一种切实的需求.WebSphere Extended Deployment Compute Grid(下文简称 Compute Grid)是一个完整的.开箱即用的批处理平台,提供了一 个高效.可靠.可扩展.高度可用和安全的批执行环境. 本文基于 WebSphere Compute Grid V8.我们使用 Rat

容器就像监狱,让我们来构造一个监狱吧!(含代码下载)

前言   容器,近年来最火的话题,在后端的开发中,容器的运用已经是主流技术了.今天,我们就来说说容器技术.首先,虽然目前Docker技术如此火爆,但其实容器技术本质上并不是什么高大上的东西,总的来讲,就是对目前的Linux底层的几个API的封装,然后围绕着这几个API开发出了一套周边的环境.   目前来说,普遍讨论关于容器的文章,一开始都会讲到UTC隔离.PID隔离.IPC隔离.文件系统隔离.CGroups系统,今天这一篇,我们换一个视角,从以下几个方面来聊一下容器技术.   第一部分,从容器和

[MySQL5.6] 一个简单的optimizer_trace示例

前面已经介绍了如何使用和配置MySQL5.6中optimizer_trace(点击博客),本篇我们以一个相对简单的例子来跟踪optimizer_trace的产生过程. 本文的目的不是深究查询优化器的实现,只是跟踪optimizer trace在优化器的那一部分输出,因此很多部分只是一带而过,对于需要深究的部分,暂时标注为红色,后续再扩展阅读;之前一直没看过这部分代码,理解起来还是比较困难的- 我们以一个简单的表为例过一下optimizer trace的产生过程: mysql> show crea

用C#做简单的信息过滤

 现在网上比较大型的论坛都得备案,一旦有什么不太合理的信息,都可能受到有关部门的那啥...所以在信息过滤显得有点地位了.下面向大家介绍一个简单的信息硬过滤的办法.其实就是自动匹配.代码如下: public void CheckWords(string str){    //创建一数组,写入要过滤之字符串    string[] BadWords=new String[2];     BadWords[0]="C";    BadWords[1]=".net";   

运用的一个简单示例对3DES加密

加密|示例 提要 命名空间:System.Security.Cryptography.TripleDES 类简单说明: 表示三重数据加密标准算法的基类,TripleDES 的所有实现都必须从此基类派生,但TripleDES是从 SymmetricAlgorithm 类里继承出来.TripleDES 使用 DES 算法的三次连续迭代.它可以使用两个或三个 56 位密钥.      使用目的:比较安全的加密一种方式,密钥和矢量的不同,会生产不同的加密字串.因为是DES算法的三次连续迭代,而且算法可逆

Windows 8 Store Apps学习(64) 后台任务: 开发一个简单的后台任务

介绍 重新想象 Windows 8 Store Apps 之 后台任务 开发一个简单的后台任务 示例 1.通过"Windows 运行时组件"新建一个后台任务 BackgroundTaskLib/Demo.cs /* * 后台任务 * * 注: * 后台任务项目的输出类型需要设置为"Windows 运行时组件",其会生成 .winmd 文件,winmd - Windows Metadata */ using System; using System.Threading