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:
view sourceprint?
01.DECLARE_DRIVER_CLASS(UsbFilterDriver, NULL)
02./
03.// Driver Entry
04.//
05.NTSTATUS UsbFilterDriver::DriverEntry(PUNICODE_STRING RegistryPath)
06.{
07.????T <<?"UsbFilterDriver::DriverEntry\n";
08.?
09.????m_Unit = 0;
10.????return?STATUS_SUCCESS;
11.?
12.????// The following macro simply allows compilation at Warning Level 4
13.????// If you reference this parameter in the function simply remove the macro.
14.????UNREFERENCED_PARAMETER(RegistryPath);
15.}
16.???AddDevice函數
17.NTSTATUS UsbFilterDriver::AddDevice(PDEVICE_OBJECT Pdo)
18.{
19.????T <<?"UsbFilterDriver::AddDevice\n";
20.????UsbFilterDevice * pFilterDevice =?new?(
21.????????????static_cast(NULL),
22.????????????FILE_DEVICE_UNKNOWN,
23.????????????static_cast(NULL),
24.????????????0,
25.????????????DO_DIRECT_IO
26.????????????)
27.????????UsbFilterDevice(Pdo, m_Unit);
28.????if?(pFilterDevice)
29.????{
30.????????NTSTATUS status = pFilterDevice->ConstructorStatus();
31.????????if?( !NT_SUCCESS(status) )
32.????????{
33.????????????T <<?"Failed to construct UsbFilterDevice"
34.??????????????<< (ULONG) m_Unit
35.??????????????<<?" status = "
36.??????????????<< status
37.??????????????<<?"\n";
38.?
39.????????????delete?pFilterDevice;
40.????????}
41.????????else
42.????????{
43.????????????m_Unit++;
44.????????}
45.????????return?status;
46.????}
47.????else
48.????{
49.????????T <<?"Failed to allocate UsbFilterDevice"
50.??????????<< (ULONG) m_Unit
51.??????????<<?"\n";
52.????????return?STATUS_INSUFFICIENT_RESOURCES;
53.????}
54.}
這兩段代碼基本上和自動生成的代碼差不多。AddDevice的作用是構造一個過濾器的實例。
關鍵的代碼在Device類。在這個類里,我們把過濾器插入設備棧,并攔截IRP,用自己的完成例程來實現特定的功能。
Device構造函數
view sourceprint?
01.UsbFilterDevice::UsbFilterDevice(PDEVICE_OBJECT Pdo,?ULONG?Unit) :
02.????KWdmFilterDevice(Pdo, NULL)
03.{
04.????T <<?"UsbFilterDevice::UsbFilterDevice\n";
05.????// Check constructor status
06.????if?( ! NT_SUCCESS(m_ConstructorStatus) )
07.????{
08.????????return;
09.????}
10.????// Remember our unit number
11.????m_Unit = Unit;
12.????// initialize the USB lower device
13.????m_Usb.Initialize(this, Pdo);
14.????NTSTATUS status = AttachFilter(&m_Usb);?//Attach the filter
15.????if(!NT_SUCCESS(status))
16.????????{
17.????????m_ConstructorStatus = status;
18.????????return;
19.????????}
20.????SetFilterPowerPolicy();
21.????SetFilterPnpPolicy();
22.}
在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,如下:
view sourceprint?
01.NTSTATUS UsbFilterDevice::InternalDeviceControl(KIrp I)
02.{
03.????T <<?"UsbFilterDevice::InternalDeviceControl\n";
04.????// Pass through IOCTLs that are not submitting an URB
05.//不是我們感興趣的IOCTL不要理它
06.????if?(I.IoctlCode() != IOCTL_INTERNAL_USB_SUBMIT_URB)
07.????????return?DefaultPnp(I);
08.?
09.????PURB p = I.Urb(CURRENT);????// get URB pointer from IRP
10.?
11.//不是我們感興趣的URB,也不要理它,
12.????if?(p->UrbHeader.Function !=
13.URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE)
14.????????return?DefaultPnp(I);
15.//符合要求的IRP才被設置完成例程
16.????return?PassThrough(I, LinkTo(DeviceControlComplete),?this);
17.}
18.在設置好條件以后,再來實現完成例程。所有的檢查、修改等動作都是在完成例程里面完成的。
19.NTSTATUS UsbFilterDevice::DeviceControlComplete(KIrp I)
20.{
21.????PURB p = I.Urb(CURRENT);
22.????if(p)
23.????{
24.//攔截到設備返回的描述表,
25.????????char* DescriptorBuffer = (char*)p->UrbControlDescriptorRequest.TransferBuffer;
26.//指向第三個字節,表示設備Usage屬性的值
27.????????DescriptorBuffer += 3;
28.//如果值為6則改成0,6表示hid鍵盤,0表示未知設備
29.//在設備管理器里面,原來的hid兼容鍵盤就不復存在了,取而代之的則是hid兼容設備
30.????????if?((*DescriptorBuffer&0xff) == 6)
31.????????????*DescriptorBuffer = 0;
32.????}
33.????return?I.Status();
34.}
讀者可以對照DriverWorks中的例子,直接替換掉(或者修改)上面這兩個函數,再編譯一下,便可以得到一個完整的鍵盤過濾器驅動程序。
其實,只要弄清楚了我們需要做些什么動作,在DriverStudio里面只需要寫少量的關鍵代碼,便可實現我們的要求,其余的大部分工作,或有范例可供參考,或有Driver?Wizard自動生成。
從上面可以看出,我們只需要修改這兩個函數,攔截合適的IRP,便可以在完成例程里面實現我們特定的要求。正如開頭所說,我們也可以攔截其他的IRP,攔截其他的URB,或者攔截特定鍵盤的按鍵鍵值,將之傳遞到用戶態,以方便實現聯想、實達等隨機配備的多功能鍵盤的功能。
三、使用INF安裝驅動
在完成了驅動以后,還必須把它安裝到系統里面,驅動程序才會起作用。一般來說,我們都必須為我們的驅動程序提供一個inf文件,以便于用戶安裝或者維護。對于新手來說,過濾驅動程序的inf或許有些棘手。所以,針對本文所描述的驅動,我們提供一個Win98下的安裝范例usbkey.inf,范例中“;”后的文字是注解,以方便讀者理解。
view sourceprint?
01.; usbkey.INF?
02.;
03.; Installs Lower Level Filter?for?a HID keyboard device
04.;
05.; (c) Copyright 2001 SINO Co., Ltd.
06.;????
07.[Version]
08.;”CHICAGO”表示Win9x平臺
09.Signature="$CHICAGO$"
10.;鍵盤所屬類名
11.Class=HID
12.ClassGUID={745a17a0-74d3-11d0-b6fe-00a0c90f57da}
13.;驅動程序提供者,此信息會顯示在設備屬性的“常規”頁
14.Provider=%USBDBE%
15.LayoutFile=layout.inf
16.;顯示在驅動程序文件詳細資料窗口
17.DriverVer=11/12/2001,4.10.2222.12
18.?
19.?
20.;[ControlFlags]
21.;ExcludeFromSelect = *
22.?
23.;驅動程序安裝目錄,inf會將我們的驅動程序安裝到如下目錄
24.;記得Destinationdir后面一定要帶一個“s”
25.[DestinationDirs]
26.DefaultDestDir = 10,system32\drivers
27.?
28.;要增加的注冊表項
29.[ClassInstall]
30.Addreg=HIDClassReg
31.?
32.[HIDClassReg]
33.HKR,,,,%HID.ClassName%
34.HKR,,Icon,,-20
35.?
36.;制造商
37.[Manufacturer]
38.%USBDBE%=USBDBE
39.?
40.[USBDBE]
41.;我們所要附加過濾驅動程序的設備ID。這個ID可以從IC的規范上得來,也可以
42.;用hidview.exe讀出,或者從注冊表HKLM\Enum\hid和usb項找出
43.%HID.DeviceDesc%??? = Keypad_Inst, USB\VID_05AF&PID_0805&MI_00
44.?
45.;要安裝的文件和需要修改的注冊表項
46.;Install usbkey driver
47.[Keypad_Inst]
48.CopyFiles=Keypad_Inst.CopyFiles
49.AddReg=Keypad_Inst.AddReg
50.?
51.[Keypad_Inst.CopyFiles]
52.hidusb.sys
53.hidparse.sys
54.hidclass.sys
55.usbfilt.sys
56.?
57.[Keypad_Inst.AddReg]
58.HKR,,DevLoader,,*ntkern
59.HKR,,NTMPDriver,,"hidusb.sys"
60.?
61.?
62.[Keypad_Inst.HW]
63.AddReg=Keypad_Inst.AddReg.HW
64.?
65.;Lowerfilters表示是低層過濾驅動,如果是上層過濾驅動,則必須改為upperfilters
66.[Keypad_Inst.AddReg.HW]
67.HKR,,"LowerFilters",0x00010000,"usbfilt.sys"
68.?
69.;HID設備所需要安裝的文件和注冊表中需要修改的地方
70.;Install USBHIDDevice
71.[USBHIDDevice]
72.CopyFiles=USBHIDDevice.Copy
73.AddReg=USBHIDDevice.AddReg
74.?
75.[USBHIDDevice.Copy]
76.hidclass.sys
77.hidusb.sys
78.hidparse.sys
79.?
80.[USBHIDDevice.AddReg]
81.HKR,,DevLoader,,*ntkern
82.HKR,,NTMPDriver,,"hidusb.sys"
83.?
84.;以下定義需要在上面某些地方使用時替換的字符串
85.[strings]
86.USBDBE???????????? =?"SINO Co., Ltd."
87.HID.DeviceDesc?????? =?"SINO USB MultiKeyboard"
88.HID.HIDDeviceDesc??? =?"Human Interface Devices"
89.HID.DefaultDevice??? =?"HID Default Device"
90.HID.ClassName??????? =?"Human Input Devices (HID)"
91.HID.SvcDesc????????? =?"Microsoft HID Class Driver"
其實最簡單的寫inf的方式,是找一些類似設備的inf文件或范例來修改。在不侵權的前提下,充分利用現有資源是我們的一貫原則。
總結
- 上一篇: pandas的菜鸟级应用(谁都能学会)
- 下一篇: Sanic 接收前端post 提交的js