摘 要:針對在不影響系統(tǒng)現(xiàn)有驅(qū)動程序接口的前提下實(shí)現(xiàn)串行端口傳輸數(shù)據(jù)過濾功能的目的,通過對Win32驅(qū)動程序模型的驅(qū)動程序分層和IRP結(jié)構(gòu)的分析,結(jié)合驅(qū)動程序處理IRP結(jié)構(gòu)數(shù)據(jù)的方法,得出了在功能驅(qū)動程序的上層增加過濾器可以過濾串行端口傳輸數(shù)據(jù)的結(jié)論,并介紹了串行端口過濾器驅(qū)動程序的開發(fā)方法。
關(guān)鍵詞:串行端口;過濾器;驅(qū)動程序;IRP
中圖分類號:TP309
用戶使用計(jì)算機(jī)串行端口傳輸數(shù)據(jù)信息時,可能存在監(jiān)視傳輸數(shù)據(jù)的內(nèi)容或防止敏感數(shù)據(jù)通過串行端口流出等需求。禁止使用串行端口的方法很多,如從物理上封堵串行端口,或者編寫一個始終獨(dú)占串行端口的程序等。但是如果用戶希望記錄串行端口傳輸?shù)臄?shù)據(jù)而不影響其它程序使用串行端口,或者希望有區(qū)別地處理數(shù)據(jù),允許普通數(shù)據(jù)流出而禁止敏感數(shù)據(jù)流出,就存在一定的技術(shù)難度,這通常需要使用過濾器驅(qū)動程序開發(fā)的相關(guān)技術(shù)。
1 過濾器技術(shù)基礎(chǔ)
Windows系統(tǒng)的每個通信資源都有一個由庫或驅(qū)動構(gòu)成的服務(wù)提供者,使應(yīng)用程序能夠訪問該資源。該服務(wù)提供者即通信資源對應(yīng)的Windows系統(tǒng)的設(shè)備對象,用于接收外部中斷請求并完成實(shí)際硬件功能。對于串行端口的數(shù)據(jù)傳輸而言,應(yīng)用程序進(jìn)程可以調(diào)用CreateFile、ReadFile、WriteFile和DeviceIoControl等相關(guān)的Win32 API函數(shù)訪問相應(yīng)的系統(tǒng)設(shè)備對象,設(shè)備驅(qū)動程序向串行端口發(fā)出中斷請求完成數(shù)據(jù)傳輸操作并返回給API函數(shù)。為了使用戶能夠監(jiān)控串行端口的傳輸數(shù)據(jù),可以考慮在應(yīng)用程序進(jìn)程和系統(tǒng)設(shè)備對象之間增加一個過濾器來實(shí)現(xiàn)該功能。
1997年微軟公司推出了名為Win32驅(qū)動程序模型(Win32 Driver Model,WDM)的技術(shù)[1],該項(xiàng)技術(shù)引入了硬件抽象層(HAL)的概念,并在后續(xù)的Windows系統(tǒng)中的得到了廣泛應(yīng)用。WDM是一個基于設(shè)備對象的分層化的驅(qū)動程序模型,驅(qū)動程序的層或堆棧一起工作處理I/O請求[2]。
1.1 驅(qū)動程序分層
WDM根據(jù)設(shè)備對象堆棧進(jìn)行分層。設(shè)備對象是系統(tǒng)為幫助軟件為管理硬件而創(chuàng)建的數(shù)據(jù)結(jié)構(gòu),代表了系統(tǒng)中一個物理的或邏輯的設(shè)備,并描述其特征。處于堆棧最底層的設(shè)備對象稱為物理設(shè)備對象(Physical Device Object,PDO),在設(shè)備對象堆棧中間的某處有一個對象稱為功能設(shè)備對象(Functional Device Object,F(xiàn)DO),在FDO的上面和下面可以有一些過濾器設(shè)備對象(Filter Device Object,F(xiàn)iDO),一個硬件設(shè)備可以有多個FDO或FiDO,但只允許有一個PDO。每個硬件設(shè)備至少有兩個驅(qū)動程序,一個是總線驅(qū)動程序,負(fù)責(zé)管理PDO代表的設(shè)備與計(jì)算機(jī)的連接;一個是功能驅(qū)動程序,負(fù)責(zé)管理FDO代表的設(shè)備,初始化I/O操作,處理I/O操作完成時產(chǎn)生的中斷事件。設(shè)備除了上述的兩種驅(qū)動程序外,還可以有過濾器驅(qū)動程序。過濾器驅(qū)動程序可以監(jiān)視或修改功能驅(qū)動程序執(zhí)行I/O操作時的行為。位于功能驅(qū)動程序上面和下面的過濾器驅(qū)動程序分別稱為上層過濾器和下層過濾器。上層過濾器可以提供功能驅(qū)動程序之外的特性,而下層過濾器則可以修改功能驅(qū)動程序?qū)⒁獔?zhí)行的總線操作流[3]。
1.2 I/O Request Packet(IRP)
Windows的I/O子系統(tǒng)是一個I/O請求包(I/O Request Packet,IRP)驅(qū)動的系統(tǒng),每個I/O操作都使用一個IRP結(jié)構(gòu)數(shù)據(jù)描述。I/O管理器響應(yīng)一個I/O請求并進(jìn)行參數(shù)匹配和操作的安全性檢查,然后從系統(tǒng)內(nèi)存分配的一塊大小可變的IRP結(jié)構(gòu)內(nèi)存,存放I/O請求的類型、用戶緩沖區(qū)的首地址、用戶請求數(shù)據(jù)的長度等信息。
IRP結(jié)構(gòu)數(shù)據(jù)由兩部分組成:固定部分和一個I/O堆棧。IRP的固定部分包含如下一些請求的相關(guān)信息:該請求的類型和大小,同步或異步,一個用于緩沖類型I/O的緩沖區(qū)指針,以及一些在請求處理過程中會被改變的狀態(tài)信息。I/O堆棧包含一系列I/O堆棧單元,單元數(shù)目與驅(qū)動程序堆棧中處理該I/O請求的驅(qū)動程序數(shù)目相同,每個單元對應(yīng)一個將處理該IRP的驅(qū)動程序。IRP當(dāng)前堆棧單元中包含一個I/O功能代碼、與功能相關(guān)的參數(shù),以及一個指向調(diào)用者文件對象的指針,用以判定即將發(fā)生在特定文件對象上的特定I/O操作。I/O功能代碼由一個主功能代碼和一個次功能代碼組成。主功能代碼標(biāo)識了當(dāng)I/O管理器將一個IRP傳遞給一個驅(qū)動程序時應(yīng)該調(diào)用該驅(qū)動程序的分發(fā)例程,以IRP_MJ_開頭的符號常量進(jìn)行定義。次功能代碼是可選的,有時被用作主功能代碼的一個修飾符,以IRP_MN_開頭的符號常量進(jìn)行定義。
I/O管理器接收一個I/O請求后,分配并初始化一個IRP結(jié)構(gòu)數(shù)據(jù),并把該數(shù)據(jù)發(fā)送到合適的設(shè)備對象堆棧的最上層驅(qū)動程序,然后逐步進(jìn)行過濾到下面的各層驅(qū)動程序,每層驅(qū)動程序都可以處理IRP結(jié)構(gòu)數(shù)據(jù)。根據(jù)硬件設(shè)備本身和IRP的內(nèi)容,驅(qū)動程序可以只向下層傳遞IRP,也可以處理完IRP后不再向下層傳遞,或者既處理IRP又向下層傳遞。驅(qū)動程序處理完一個I/O請求后,在IRP結(jié)構(gòu)中添加處理結(jié)果的有關(guān)信息,并將其返回I/O管理器,設(shè)備操作的I/O請求隨即返回[4]。
2 串行端口過濾器的實(shí)現(xiàn)
要對串行端口的傳輸數(shù)據(jù)進(jìn)行監(jiān)視,可以在系統(tǒng)的通用串行端口驅(qū)動程序上面增加一個上層過濾器,截取流經(jīng)它的IRP中的串行端口數(shù)據(jù),將所截取的數(shù)據(jù)通過進(jìn)程間通信的方式傳送到應(yīng)用進(jìn)程進(jìn)行分析顯示,同時將數(shù)據(jù)向下層驅(qū)動程序發(fā)送完成正常的數(shù)據(jù)傳輸,這樣可以在不影響串行端口功能的情況下實(shí)現(xiàn)監(jiān)視數(shù)據(jù)的功能。如果要對串行端口的傳輸數(shù)據(jù)進(jìn)行區(qū)別處理,可以先將所截取的數(shù)據(jù)傳送到應(yīng)用進(jìn)程根據(jù)某種規(guī)則進(jìn)行處理后,再傳送回過濾器并向下層驅(qū)動程序發(fā)送。
串行端口過濾器可以按照功能分為三部分實(shí)現(xiàn):(1)生成過濾器設(shè)備對象并綁定;(2)獲取傳輸數(shù)據(jù)并處理;(3)入口例程DriverEntry。
2.1 生成過濾器設(shè)備對象并綁定
Windows系統(tǒng)中已經(jīng)許多提供了各種功能的設(shè)備對象來接收中斷請求,并完成對應(yīng)的硬件設(shè)備的功能。為了截取中斷請求,可以生成一個虛擬的設(shè)備對象,并綁定到真實(shí)的設(shè)備對象上。綁定后,要發(fā)送到真實(shí)的設(shè)備對象的中斷請求會首先被發(fā)送到該虛擬的設(shè)備對象上。實(shí)現(xiàn)該功能需要自定義一個函數(shù),如定義為FilterAttachCom(PDRIVER_OBJECT device),其參數(shù)device是一個驅(qū)動程序?qū)ο蟮闹羔?,由系統(tǒng)在入口例程DriverEntry中傳入。
首先,調(diào)用內(nèi)核API函數(shù)IoGetDeviceObjectPointer獲取一個串行端口的設(shè)備對象指針。
函數(shù)參數(shù)中,ObjectName是命名設(shè)備的名稱,Windows系統(tǒng)的第一個串行端口名稱是”\\Device\\Serial0”,第二個是”\\Device\\Serial1”,以此類推;DesiredAccess是期望訪問權(quán)限,可以賦值為FILE_ALL_ACCESS;FileObject是返回的文件對象指針,實(shí)現(xiàn)串行端口過濾器不需要使用該參數(shù),但調(diào)用該函數(shù)后必須調(diào)用函數(shù)ObDereferenceObject以解除對此文件對象的引用,否則會發(fā)生內(nèi)存泄漏;DeviceObject返回希望獲取的設(shè)備對象指針。
其次,調(diào)用內(nèi)核API函數(shù)IoCreateDevice生成串行端口過濾器的虛擬設(shè)備對象,并把該虛擬設(shè)備對象的多個子域(主要是標(biāo)志和特征)設(shè)置成和要綁定的設(shè)備對象一致。
函數(shù)參數(shù)中,DriverObject是系統(tǒng)中一個單獨(dú)的驅(qū)動程序?qū)ο蟮闹羔?,描述了?qū)動程序?qū)ο笤谖锢韮?nèi)存中的載入位置、大小和主要入口點(diǎn)等信息,每個驅(qū)動程序都要從DriverEntry例程接收該指針;DeviceExtensionSize是設(shè)備對象擴(kuò)展的大小,置為0;DeviceName是設(shè)備名稱,因?yàn)檫^濾器設(shè)備通常不需要名稱,所以置為NULL;DeviceType是設(shè)備類型,應(yīng)與被綁定的設(shè)備類型一致;DeviceObject返回希望生成的過濾器虛擬設(shè)備對象。
該函數(shù)調(diào)用結(jié)束后,應(yīng)測試要綁定的設(shè)備對象的Flags子域是否有DO_BUFFERED_IO和DO_DIRECT_IO標(biāo)志,Characteristics子域是否有FILE_DEVICE_SECURE_OPEN標(biāo)志,如果有這些標(biāo)志,需要為過濾器的虛擬設(shè)備對象的相應(yīng)子域設(shè)置這些標(biāo)志。
此外,還需要為過濾器的虛擬設(shè)備對象的Flags子域設(shè)置DO_POWER_PAGABLE標(biāo)志。
最后,調(diào)用內(nèi)核API函數(shù)IoAttachDeviceToDeviceStack把過濾器虛擬設(shè)備對象綁定到串行端口的設(shè)備對象上。
函數(shù)參數(shù)中,SourceDevice是生成的過濾器虛擬設(shè)備對象的指針;TargetDevice是要被綁定的命名設(shè)備對象的指針;函數(shù)返回值是被綁定后的設(shè)備對象指針。該函數(shù)總是綁定設(shè)備對象堆棧的最頂層的設(shè)備對象。
2.2 獲取傳輸數(shù)據(jù)并處理
I/O管理器在響應(yīng)來自串行端口的I/O請求時構(gòu)造一個IRP,并將其作為參數(shù)傳給驅(qū)動程序的分發(fā)例程。IRP在系統(tǒng)處理I/O請求的過程中代表此請求,請求中包含要發(fā)送的數(shù)據(jù),對請求的響應(yīng)中則包含要接收的數(shù)據(jù)。在過濾器虛擬設(shè)備對象綁定到串行端口設(shè)備對象后,首先接收到IRP的是過濾器設(shè)備對象,通過分析流經(jīng)過濾器的IRP結(jié)構(gòu)數(shù)據(jù)可以得到流經(jīng)串行端口的所有數(shù)據(jù)并進(jìn)行處理。實(shí)現(xiàn)該功能需要自定義一個分發(fā)函數(shù),如定義為ComFilterDispatch(PDEVICE_OBJECT device,PIRP irp),其參數(shù)device和irp由系統(tǒng)從入口例程DriverEntry傳入。
IRP中的I/O堆棧由一系列I/O堆棧單元構(gòu)成。I/O堆棧單元由IO_STACK_LOCATION結(jié)構(gòu)定義,每個堆棧單元都對應(yīng)一個將處理該IRP的驅(qū)動程序。I/O管理器在構(gòu)造IRP時,分配IRP結(jié)構(gòu)內(nèi)存并初始化IRP固定部分和第一個I/O堆棧單元,這個單元中的信息與要傳遞到驅(qū)動程序堆棧的第一個驅(qū)動程序的信息相對應(yīng),第一個驅(qū)動程序?qū)⒁幚碓揑/O請求。當(dāng)IRP到達(dá)最高層的驅(qū)動程序時,調(diào)用內(nèi)核API函數(shù)IoGetCurrentIrpStackLocation返回指向當(dāng)前的I/O堆棧單元的指針。
驅(qū)動程序接收到IRP后可以有三種選擇:
2.2.1 允許IRP通過
驅(qū)動程序不做任何事情或僅僅獲取IRP的一些信息,將IRP傳遞給下一層的驅(qū)動程序。這種情況下,驅(qū)動程序需要為下一層的驅(qū)動程序設(shè)置堆棧單元,多數(shù)情況下,它會調(diào)用內(nèi)核API函數(shù)IoSkipCurrentIrpStackLocation把當(dāng)前堆棧單元復(fù)制給下一個堆棧單元。如果需要更改下一個堆棧單元,則調(diào)用內(nèi)核API函數(shù)IoGetNextIrpStackLocation得到指向下一個堆棧單元的指針。驅(qū)動程序通過調(diào)用內(nèi)核API函數(shù)IoCallDriver調(diào)用下一層驅(qū)動程序,I/O管理器會改變當(dāng)前IRP堆棧單元指針;繼續(xù)上述過程,直到最底層的驅(qū)動程序完成對此IRP的處理。
2.2.2 禁止IRP通過
驅(qū)動程序不向下一層驅(qū)動程序傳遞IRP,因此,下層驅(qū)動程序不會接收到這個IRP,可能導(dǎo)致系統(tǒng)會出現(xiàn)權(quán)限錯誤或讀取文件失敗等信息提示。
2.2.3 完成IRP的處理
驅(qū)動程序?qū)RP的信息進(jìn)行處理后不繼續(xù)向下一層驅(qū)動程序傳遞,調(diào)用內(nèi)核API函數(shù)IoCompleteRequest表示已經(jīng)完成該IRP的處理。串行端口的I/O請求包括打開、關(guān)閉、設(shè)置波特率、讀和寫等。因?yàn)榇卸丝谶^濾器的功能是用來過濾通過串行端口的數(shù)據(jù),所以只需要關(guān)心讀請求和寫請求,在IRP中對應(yīng)的主功能代碼分別為IRP_MJ_READ和IRP_MJ_WRITE[5]。綁定后的串行端口過濾器設(shè)備對象位于設(shè)備對象堆棧的最頂端,IRP會首先被發(fā)送到過濾器設(shè)備對象。在過濾器的分發(fā)函數(shù)中調(diào)用內(nèi)核API函數(shù)IoGetCurrentIrpStackLocation返回指向IRP當(dāng)前I/O堆棧單元的指針,通過該指針獲取IRP的主功能代碼來判定當(dāng)前的I/O請求類型。為了簡單說明過濾器的實(shí)現(xiàn)方法,如果是讀請求或?qū)懻埱?,則捕獲數(shù)據(jù)并允許IRP通過。
IRP中共有三個域描述緩沖區(qū):MDLAddress、UserBuffer和AssociatedIrp.SystemBuffer。I/O請求的類別不同,IRP存儲數(shù)據(jù)使用的緩沖區(qū)不同。MDLAddress域指向內(nèi)核空間的緩沖區(qū)的虛擬地址;UserBuffer域指向應(yīng)用層緩沖區(qū)的地址,但該地址需在內(nèi)核空間中訪問;AssociatedIrp.SystemBuffer域指向應(yīng)用層緩沖區(qū)的地址,但需要將此緩沖區(qū)的數(shù)據(jù)拷貝到內(nèi)核空間訪問。要捕獲串行端口的數(shù)據(jù),需要依次測試這三個域是否為空,如果某個域不為空,就可以從該域指向的緩沖區(qū)中讀取數(shù)據(jù)。
2.3 入口例程DriverEntry
驅(qū)動程序被加載到系統(tǒng)中時,I/O管理器創(chuàng)建一個驅(qū)動程序?qū)ο?,然后調(diào)用該驅(qū)動程序的入口例程DriverEntry[6]。DriverEntry例程是每個設(shè)備驅(qū)動程序的入口,完成大部分設(shè)備初始化工作,包括設(shè)置響應(yīng)各種用戶請求的分發(fā)例程與I/O控制例程的入口。驅(qū)動程序中只有DriverEntry例程的名字是固定的,其它所有例程都要由該例程向系統(tǒng)注冊。
串行端口過濾器的DriverEntry例程的代碼如下:
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING path)
{
driver->DriverUnload = UnloadComFilter;
driver->MajorFunction[0] = ComFilterDispatch;
FilterAttachCom(driver);
return STATUS_SUSCCESS;
}
系統(tǒng)開始加載設(shè)備驅(qū)動程序時傳入DriverEntry例程的參數(shù),參數(shù)driver指向一個驅(qū)動程序?qū)ο?,path是設(shè)備服務(wù)鍵的鍵名。
DriverEntry例程負(fù)責(zé)初始化驅(qū)動程序?qū)ο骴river,參數(shù)DriverUnload是指向驅(qū)動程序動態(tài)卸載函數(shù)UnloadComFilter的指針;MajorFunction是對應(yīng)多種類型IRP處理的分發(fā)函數(shù)的指針數(shù)組,缺省情況下I/O管理器把數(shù)組的每個指針都初始化為啞分發(fā)函數(shù),由于過濾器要處理串行端口的I/O請求,所以要設(shè)置MajorFunction[0]指向分發(fā)函數(shù)ComFilterDispatch。
完成串行端口驅(qū)動程序?qū)ο蟪跏蓟螅纯烧{(diào)用函數(shù)FilterAttachCom(driver)獲取串行端口設(shè)備對象,生成過濾器虛擬設(shè)備對象并綁定到串行端口設(shè)備對象上。當(dāng)I/O管理器接收到串行端口的I/O請求時,創(chuàng)建一個IRP結(jié)構(gòu)并將其作為傳送給分發(fā)函數(shù)ComFilterDispatch進(jìn)行分析處理,實(shí)現(xiàn)對串行端口傳輸數(shù)據(jù)的過濾功能。
3 結(jié)束語
在開發(fā)與安全軟件相關(guān)的驅(qū)動程序時,過濾器是一種非常重要的技術(shù)。過濾器是在系統(tǒng)內(nèi)核的驅(qū)動程序分層中增加的一層驅(qū)動程序。過濾器并不影響其它驅(qū)動程序接口,因此過濾器通常用于擴(kuò)展系統(tǒng)硬件設(shè)備的功能。為了過濾串行端口的傳輸數(shù)據(jù),可以在應(yīng)用程序和串行端口功能驅(qū)動程序之間增加一個過濾器,表示I/O請求的IRP會先被發(fā)送到過濾器,分析IRP就可以獲取到串行端口的傳輸數(shù)據(jù)進(jìn)行處理,從而實(shí)現(xiàn)數(shù)據(jù)過濾功能。從串行端口過濾器的開發(fā)方法可以看出,運(yùn)用過濾器開發(fā)技術(shù)能夠以很小的代價(jià)來擴(kuò)展使用通用驅(qū)動程序的硬件設(shè)備的功能。
參考文獻(xiàn):
[1]姜元建,王斌,徐偉.虛擬串口管理軟件的設(shè)計(jì)與應(yīng)用[J].微型電腦應(yīng)用,2010(04):55-56.
[2]張小川,陳最,涂飛.基于過濾驅(qū)動的透明加密文件系統(tǒng)研究與實(shí)現(xiàn)[J].計(jì)算機(jī)應(yīng)用與軟件,2013(04):44-47.
[3]尤晉元,史美林,陳向群等.Windows操作系統(tǒng)原理[M].北京:機(jī)械工業(yè)出版社,2001.
[4]龔建偉,熊光明.Visual C++/Turbo C串口通信編程實(shí)踐[M].北京:電子工業(yè)出版社,2004.
[5]譚文,楊瀟,邵堅(jiān)磊.寒江獨(dú)釣:Windows內(nèi)核安全編程[M].北京:電子工業(yè)出版社,2009.
[6]竹武林,范惠林,龐帥.串行通信接口卡驅(qū)動及測試軟件設(shè)計(jì)和實(shí)現(xiàn)[J].長春理工大學(xué)學(xué)報(bào)(自然科學(xué)版),2011(03):156-159.
作者簡介:周鵬(1978-),男,山東膠州人,工程師,畢業(yè)于武漢測繪科技大學(xué)計(jì)算機(jī)軟件專業(yè),現(xiàn)從事研究分布式系統(tǒng)、軟件工程工作。
作者單位:92124部隊(duì),遼寧大連 116023