作者:無心化語 文章來源:driverdevelo
如何構造一個簡單的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請求時,設定桓鐾瓿衫蹋謖飧鐾瓿衫討校頤牆卸蟄sage的值,將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 foll wing 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::A dDevice(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 dev ce
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檔案或範例來修改。在不侵權的前提下,充分利用現有資源是我們的一貫原則。