catalog
0. 引言 1. Windows 2000網絡結構和OSI模型 2. NDIS驅動 3. NDIS微端口驅動編程實例 4. NDIS中間層驅動編程實例 5. NDIS協議層驅動編程實例 6. TDI驅動 7. TDI的過濾框架 8. WFP(Windows Filtering Platform windows過濾平台)
0. 引言
最早出現的網絡驅動應該是網卡驅動,這是Windows的下進行網絡安全攻防常見的需求,為了進一步分割應用程序的網絡數據傳輸與下層協議直到下層硬件的關系,又出現了協議驅動,后來微軟和硬件商聯合制定了NDIS標准,作為從硬件到協議的內核驅動程序的調用接口標准,而協議驅動與應用層的API之間,則出現了TDI接口,即從上到下的關系是
應用層API -> TDI -> 協議驅動 -> NDIS -> 下層硬件
0x1: 網卡驅動
網卡驅動程序(Device Driver)全稱為"設備驅動程序",是一種可以使計算機中央處理器: CPU控制和使用設備的特殊程序,相當於硬件的接口,操作系統通過這個接口,控制硬件設備的工作。所有的硬件都要安裝驅動程序,沒有驅動程序的硬件是運行不了的,就像一輛有輪胎但是沒有傳動軸的汽車一樣跑不起來,控制不了
假如某設備的驅動程序未能正確安裝,便不能正常工作。 網卡驅動程序就是CPU控制和使用網卡的程序
網卡驅動的問題在於它總是一個整體的形式存在,無法提供靈活的Hook、Filter、Callback接口,無法實現除了操作硬件之外更高層次的數據處理需求
0x2: 上層協議驅動
上層協議驅動應用於TDI接口或其它向用戶提供服務的特定應用接口。例如驅動調用NDIS分配包,向包中拷貝數據和向底層發送包。它也在它的底層提供協議接口,來接收下層驅動發送來的包
0x3: 中間協議驅動
中間協議驅動接口位於上層協議驅動和微端口驅動之間。對於上層傳輸驅動程序來書,中間驅動看起來像是微端口驅動。對微端口驅動來說,看起來像是協議驅動。使用中間協議驅動的主要是為了傳輸媒質,存在於對於傳輸驅動未知和微端口管理之間的新的媒質類型
0x4: 基於WDK的windows內核驅動入門編程
首先需要下載WDK(Windows Driver Kit),WDK已經自帶所有需要的頭文件、庫、C/C++語言及匯編語言的編譯器、鏈接器。需要注意的是,因為這並不是應用程序編程,所以所有的Win32 API函數都不能使用,部分C Runtime函數也不能使用,但是文檔中有說明記錄的函數都可以使用
first.c
#include <ntddk.h> //提供Unload函數,讓驅動程序能動態卸載,方便調試 NTSTATUS DriverUnload(PDRIVER_OBJECT DriverObject) { DbgPrint("DriverUnload\n"); return STATUS_SUCCESS; } //DriverEntry入口函數,相當於main NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { DbgPrint("Hello World\n"); //設置一個卸載函數,便於這個函數退出 DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; }
makefile
!IF 0 Copyright (C) Microsoft Corporation, 1999 - 2002 Module Name: makefile. Notes: DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source file to this component. This file merely indirects to the real make file that is shared by all the components of Windows NT (DDK) !ENDIF !INCLUDE $(NTMAKEENV)\makefile.def
SOURCES
TARGETNAME=first TARGETTYPE=DRIVER SOURCES=first.c
菜單->windows driver kits->wdk版本->build environment->windows server 2003->launch windows->x64 Checked Build Environment
pushd C:\Documents and Settings\Administrator\桌面\網絡驅動編程\first
build
net start first
Relevant Link:
http://book.51cto.com/art/200905/125691.htm http://www.installsetupconfig.com/win32programming/windowsdriverkitswdk15_6.html http://blog.chinalxnet.com/?uid-2-action-viewspace-itemid-26 http://guides.wmlcloud.com/technology/windows-7---kernel-mode-installation-and-build---wdk-build-tools,-build-environment,-building-a-project-.aspx
1. Windows 2000網絡結構和OSI模型
Windows 2000網絡結構是以國際標准化組織(ISO)制定的七層網絡模型為基礎的,1978年,ISO制定的開放式系統(OSI)參考模型,將網絡描述為一系列的協議層,在每個協議層中完成一系列的特定功能。每一層都向上一層提供明確的服務,同時將本層服務的實現封裝起來。一個在相鄰層之間完善的接口定義了下層對上層所提供的服務以及如何訪問這些服務(應用層、表示層、會話層、傳輸層、網絡層、鏈路層、物理層、物理介質層)
Windows 2000的網絡驅動程序實現了這個網絡結構的下面四層
0x1: 物理層
這是OSI模型中的最底層,這一層用來通過物理介質接收和發送的原始的沒有結構的二進制數據流,它描述了對於物理介質的電/光,機械和功能的的接口,物理層為所有的上層傳送信號,在Windows 2000中,物理層通過網絡接口卡(NIC)來實現,物理層的收發是依縛於NIC介質的。使用串行端口的網絡組件,物理層也同樣包含了底層的網絡軟件,它定義了串行位流是如何分成數據包的
0x2: 數據鏈路層
電氣電子工程師協會(IEEE)將該層進一步分成了兩個子層,LLC和MAC
1. LLC子層(邏輯鏈路控制子層)用於將數據幀從一個結點無錯的傳輸到另一個結點。LLC子層用來建立和終止邏輯鏈接,控制幀流,對幀排序,接收幀,並且對沒有被接收的幀進行重發。LLC子層使用幀應答和幀的重發為經過鏈路層的上層提供了真正的無錯發送,在Windows 2000網絡結構中,邏輯鏈路控制子層在傳輸驅動程序中實現 2. MAC子層(介質訪問控制子層)控制物理介質的訪問,檢查幀的錯誤,並且管理接收幀的地址認證,介質訪問控制子層則在網絡接口卡(NIC)中實現,NIC由一個名為NIC驅動程序的設備驅動程序軟件控制。Windows 2000的附帶了許多常用的NIC的驅動程序
0x3: 網絡層
這一層控制着子網的運作,它基於以下因素決定數據的物理路徑
1. 網絡狀況 2. 服務優先權 3. 其他因素,包括路由、流量控制、幀的分解和重組、邏輯到物理地址的映射、用戶帳號
0x4: 傳輸層
這一層確保信息傳送的無錯傳輸,連續傳輸和不丟失或不重復。它使得上層協議與上層協議之間或與它同層的協議之間通訊不必關心數據的傳輸。傳輸層所在的協議棧至少應包括一個可靠的網絡層,或在邏輯鏈路控制子層中提供一個虛電路。例如,因為Windows 2000 NetBEUI傳輸驅動程序包括一個與OSI兼容的LLC子層,它的傳輸層的功能就很小。如果協議棧不包括LLC子層,並且網絡層不可靠,並且/或者支持自帶地址信息(例如TCP/IP的IP層或NWLINK的IPX層),那么傳輸層應能進行幀的順序控制和幀的響應,同時要對未響應幀進行重發
在Windows 2000網絡結構中,邏輯鏈路層,物理層和傳輸層都是通過名為傳輸驅動程序的軟件實現的,它有時也稱作協議,協議驅動程序或協議模塊。Windows 2000附帶了TCP/IP,IPX/SPX,NetBEUI和AppleTalk傳輸驅動程序
2. NDIS驅動
0x1: 簡介
NDIS是Network Driver Interface Specification,即網絡驅動接口規范。NDIS的主要目的就 是為NIC(網絡接口卡,Network Interface Cards)制定出標准的API接口。MAC(介質訪問控制,Media Access Controller)設備驅動封裝了所有的NIC硬件 實現,這樣一來所有的使用相同介質的NIC就可以通過通用的編程接口被訪問
NDIS同時也提供一個函數庫(有時也稱作wrapper),這個庫中的函數可以被MAC驅動調用,也可以被高層的協議(例如TCP/IP)驅動調用。這些wrapper函數使得MAC驅動和協議驅動的開發變得更加容易
NDIS(Network Driver Interface Specification)是網絡驅動程序接口規范的簡稱。它橫跨傳輸層、網絡層和數據鏈路層,定義了網卡或網卡驅動程序與上層協議驅動程序之間的通信接口規范,屏蔽了底層物理硬件的不同,使上層的協議驅動程序可以和底層任何型號的網卡通信。
NDIS為網絡驅動程序創建了一個完整的開發環境,只需調用NDIS函數,而不用考慮操作系統的內核以及與其他驅動程序的接口問題,從而使得網絡驅動程序可以從與操作系統的復雜通訊中分離,極大地方便了網絡驅動程序的編寫。另外,利用NDIS的封裝特性,可以專注於一層驅動的設計,減少了設計的復雜性,同時易於擴展驅動程序棧
NDIS支持三種類型的網絡驅動程序
1. 網卡驅動程序(NICdrivers): 微端口驅動程序 網卡驅動程序是網卡與上層驅動程序通信的接口,它負責接收來自上層的數據包,或將數據包發送到上層相應的驅動程序,同時它還完成處理中斷等工作 2. 中間驅動程序(InterMediateProtocolDrivers) 中間驅動程序位於網卡驅動程序和協議驅動程序之間,它向上提供小端口(Minport)函數集,向下提供協議(protocol)函數集 1) 因此對於上層驅動程序而言,它是小端口驅動程序 2) 對於底層的驅動程序,它是協議驅動程序 3. 協議驅動程序(Upper Level Protocol Drivers) 協議驅動程序執行具體的網絡協議(傳輸層),如IPX/SPX、TCP/IP等。協議驅動程序為應用層客戶程序提供服務,接收來自網卡或中間驅動程序的信息
自上而下的關系為
上層驅動程序(協議驅動程序) -> 中間驅動程序 -> 網卡驅動程序(NICdrivers) -> 網卡
為了更好地支持擴展性,網絡驅動模型的每一層都定義了對外公開的公共接口,與它相連接的上層或者下層驅動模型不必關心它的內核如何實現,而只要根據它所提供的公共接口完成相應的工作即可,各個層級之間按照預設好的接口上下連接,協同工作,緊密相連
NDIS定義了網卡或網卡驅動程序與上層協議驅動程序之間的通信接口規范,它屏蔽了底層物理硬件的不同,使上層的協議驅動程序可和底層任何型號的網卡通信
0x2: 網卡驅動程序(NICdrivers): 微端口驅動程序
一個 NDIS 微端口驅動程序(也叫微端口NIC 驅動程序)有兩個基本功能
1. 管理一個網絡接口卡(NIC),包括通過NIC 發送和接收數據 2. 與高層驅動程序相接,例如中間層驅動程序和傳輸協議驅動程序
一個微端口驅動程序與它的NIC通信,並且通過NDIS庫與高層驅動程序通信。NDIS庫對外提供了一整套的函數(NdisXXX 函數),這些函數封裝了微端口需要調用的所有操作系統函數,同時,微端口必須向外提供一組入口(MiniportXxx函數),使NDIS可以為了完成自己或高層驅動程序的任務而訪問微端口
0x3: 中間驅動程序(InterMediateProtocolDrivers)
中間層驅動程序一般位於微端口驅動程序和傳輸協議驅動程序之間,因為它在驅動程序層結構的中間位置,所以既與上層協議驅動程序通信又要與下層微端口驅動程序通信,它是一種混合型驅動程序,它把NDIS協議驅動和NDIS小端口驅動的功能結合在一起
1. 在它的下界,中間層驅動程序提供了協議入口點(ProtocolXxx函數),NDIS調用這些函數傳遞下層微端口的請求。對於一個下層微端口驅動程序,一個中間層驅動程序這時就仿佛是一個 協議驅動程序 2. 在它的上界,中間層驅動程序提供了微端口的入口指針(MiniportXxx函數),一個或多個上層協議驅動程序通過NDIS調用這些函數進行通信。對於上層協議驅動程序,一個中間層驅動程序這時就仿佛是一個微端口驅動程序
對NDIS協議驅動而言,它展示NDIS小端口的特征,讓NDIS協議驅動以為自己是小端口驅動而綁定它,對下層的NDIS小端口驅動而言,它展示協議驅動的特征,自己綁定在小端口驅動上(在windows內核網絡架構中,協議驅動是主動綁定小端口驅動的)
從理論上來講,中間層驅動的個數是沒有限制的,可以不斷疊加(這也是網絡防火牆兼容的架構基礎),如果在windows本身的協議驅動和小端口驅動之間,插入了一個特制的NDIS中間層驅動,所有NDIS協議驅動和NDIS小端口之間的交互活動,都會經過這個中間層過濾,這樣我們就能捕獲這些事件進行處理
0x4: 協議驅動程序(Upper Level Protocol Drivers)
一個網絡協議在NDIS驅動程序層次結構中屬於最高層驅動程序,而它經常在實現傳輸層協議的傳輸驅動程序中被用作最底層的驅動程序,例如TCP/IP 或IPX/SPX
1. 一個傳輸協議驅動程序分配包,從應用程序中將數據拷貝到包中,並且通過調用NDIS函數將這些包發送到低層驅動程序中 2. 協議驅動程序也為從下層驅動程序中接收包提供了接口 3. 一個傳輸協議驅動程序將接收到的數據轉換成相應的客戶應用數據,在它的下層,協議驅動程序與中層網絡驅動程序和微端口NIC驅動程序相連接 1) 協議驅動程序調用NdisXxx函數來發送包,讀取和設置由低層驅動程序所維護的信息,以及使用操作系統服務(NDIS代表下層驅動程序調用這些函數為自己或向上指示接收包、指示下層驅動程序的狀態、以及與協議驅動程序進行通信) 2) 協議驅動程序也提供了一套入口點(ProtocolXxx函數)(對於上層,傳輸協議驅動程序對高層驅動程序提供了一個私有接口)
0x5: NDIS協議驅動和NDIS中間層驅動的區別及其應用場景
中間層驅動在信息安全領域的應用和協議驅動的應用容易產生混淆
1. NDIS協議驅動和NDIS中間層驅動都能截獲"所有本機能接收到的包" 2. 編寫一個小型的協議驅動,可以截獲本機接收到的所有的包,這並不是通過攔截,而是注冊一個協議,並綁定了所有下層可以收包的小端口驅動的緣故,被綁定的小端口驅動總是向綁定者(協議驅動)提交網卡接收到的數據包(中斷),但是一個應用程序用TCP協議發送數據時,顯然不會通過我們注冊的新協議,而是調用TCP/IP協議,這樣,我們注冊的NDIS協議驅動自然沒有機會能攔截到上層應用的發包過程,因此,NDIS協議驅動在安全領域,只適合用來做發包工具、嗅探器(攔截本機接收到的包),而不適合做防火牆(因為防火牆需要雙向的實時攔截) 3. 中間層驅動不僅綁定了所有小端口驅動,而且還"所有協議驅動所綁定"(因為它承上啟下的特性),於是中間層驅動在理論上可以做到攔截所有通過NDIS發送和接收的數據包,無論應用程序使用什么協議都無法繞過,因此適合做包過濾防火牆的核心組件 //能否截獲所有本機發出去的包,成為了NDIS協議驅動和NDIS中間層驅動在信息安全領域的應用方面最本質的區別 4. 在NDIS中間層驅動中,對接收和發送的數據包,可以采用的處理方法幾乎是無限的;可以接受、拒絕、修改,對於大部分安全軟件來說,是采用規則過濾的方式,即用一系列的規則對這個數據包進行考核,對符合規則合法的包讓它通過;對不符合規則的攻擊包/無用的包直接丟棄,同時也有一些安全軟件對以太網包進行加密/解密的工作
0x6: NDIS網絡驅動程序編程要點
1. 可移植性 1) NDIS驅動程序應很容易在Windows的平台間移植。一般說來,從一個硬件平台移植到另一個平台只需要將它在兼容系統的編譯中重新編譯即何 2) 驅動程序開發者應當避免調用與操作系統相關的函數,因為這將使他們的驅動程序不可移植。應用NDIS函數替換這些調用,只調用NDIS函數將使代碼在支持NDIS的微軟操作系統中可移植,NDIS為編寫驅動程序提供了很多支持函數,並且直接調用操作系統是不需要的 3) 驅動程序應用C來編寫。更近一步,驅動程序代碼應當限制在ANSI C標准並且應當避免使用不被其他兼容系統編譯器支持的語言特性。驅動程序代碼編寫不應當使用任何ANSI C標准指明為"implementation defined"的特性來編寫 4) 驅動程序應當避免使用任何在不同平台上大小和結構變化的數據類型,驅動程序代碼不應該調用任何C的運行時庫函數,而是限於調用NDIS提供的函數 5) 在內核模式下漂移指針是不允許的。試圖運行這樣的操作將是一個致命的錯誤 6) 如果驅動程序代碼要支持特殊平台的特性,那么這些代碼應當包含在#ifdef和#endif聲明之間 2. 多處理器支持 1) 編寫的代碼應當可以在多處理器系統中安全運行。這對於編寫可移植的Windows驅動程序是很重要的。一個網絡驅動程序必須使用NDIS函數庫提供的多處理器安全保證 2) 在單處理器環境下,在一個時刻單處理器只運行一條機器指令,既使這樣,當包到達或時間中斷發生時,對於NIC或其他設備執行的中斷也可能發生。典型的,當正在操縱數據結構時,例如它的隊列時,驅動程序對NIC發出停用中斷來操縱數據,隨后再發生可用中斷。許多線程在單處理器環境下表現的好像是在同一時刻運行的,但是實際上它們卻是在獨立的時間片上運行的 3) 在多處理器環境下,處理器同時可運行多條機器指令,一個驅動程序必須異步化,這使得當一個驅動程序函數正在操縱一個數據結構時,同樣的在其他處理器運行的驅動程序函數不能修改共享的數據。在一個SMP機器中,所有的驅動程序代碼都要重新裝入,為了消除這種資源保護問題,Windows驅動程序使用自旋鎖 3. IRQL 所有NDIS調用的驅動程序函數都運行在系統決定的IRQL下,PASSIVE_LEVEL < DLSPATCH_LEVEL<DIRQL中的一個。例如 1) 一個微端口初始化函數、掛起函數、重啟函數和有時的關閉函數通常都運行在PASSIVE_LEVEL下 2) 中斷代碼運行在DIRQL下 所以NDIS中層或協議驅動程序從不運行在DIRQL下。所有其他的NDIS驅動程序函數運行在IRQL <= DISPATCH_LEVEL下 驅動程序函數運行於的IRQL將影響調用什么樣的NDIS函數。特定的函數只可在IRQL PASSIVE_LEVEL下調用,其他的函數可在DISPATCH_LEVEL或更低層調用。一個驅動程序的編寫者應當檢查每一個NDIS函數的IRQL限制 任何與驅動程序的ISR共享資源的驅動程序函數必須能將它的IRQL升級到DTRQL來防止爭用情況的發生,NDIS提供了這種機質 4. 同步和指示 當兩個線程共享可被同時訪問的資源時,無論是單處理機還是SMP,同步是必須的。例如,對於一個單處理機,如果一個驅動程序正在訪問一個共享資源時,被一個運行在更高IRQL(例如ISR)的函數中斷時,必須保護共享資源以阻止這種爭用的發生而使資源處於不確定狀態 在一個SMP中,兩個線程可以在同一時刻運行,在不同處理器上並且試圖來修改同一數據的訪問必須同步。 NDIS提供了自旋鎖可以用來對在同一IRQL下運行的線程間訪問共享資源實現同步。當兩個線程在不同IRQL下訪問共享資源時,NDIS提供了一種機制來臨時提高低IRQL代碼的IRQL,以使得訪問共享資源串行化 NDIS提供下面四種機制來保證同步: 4.1 自旋鎖 自旋鎖提供了一個用來保護共享資源的同步機制,這種資源是單處理器或一個多處理機下的、運行在IRQL > PASSIVE_LEVEL下的、內核模式中的線程所共享使用的。一個自旋鎖在同時運行在一個SMP機上不同的執行線程之間提供同步。一個線程在訪問保護資源前獲得一個自旋鎖。自旋鎖使得任務線程中只有持有自旋鎖的線程可使用資源。一個等待自旋鎖的線程將在試圖獲得鎖時間內循環,直到持有鎖的線程釋放為止 自旋鎖還存在着一個不太明顯但很重要的事實:你僅能在低於或等於DISPATCH_LEVEL級上請求自旋鎖,在你擁有自旋鎖期間,內核將把你的代碼提升到DISPATCH_LEVEL級上運行。在內部,內核能在高於DISPATCH_LEVEL的級上獲取自旋鎖,但你和我都做不到這一點。當KeAcquireSpinLock獲取自旋鎖時,它會把IRQL提升到DISPATCH_LEVEL級上。當KeReleaseSpinLock釋放自旋鎖時,它會把IRQL降低到原來的IRQL級上。如果你知道代碼已經處在DISPATCH_LEVEL級上,你可以調用兩個專用函數來獲取自旋鎖。KeAcquireSpinLockAtDpcLevel及 KeReleaseSpinLockFromDpcLevel。一個編寫很好的網絡驅動程序應該會減少自旋鎖持有的時間 一個典型的使用自旋鎖的例子是保護一個隊列。例如,微端口發送函數MiniportSend將協議驅動程序傳來的包進行排隊。因為其他驅動程序函數也使用這個隊列,MiniportSend必須用一個自旋鎖保護這個隊列使得在一個時刻只有一個線程可操縱這個隊列。Miniport Send獲得自旋鎖,添加包到隊列后釋放自旋鎖。使用自旋鎖保證持鎖線程是唯一修改隊列的線程,同時使得包被安全地添加到隊列中。當NIC驅動程序從隊列中取走包時,通過同樣的自旋鎖保護這個訪問。當執行指令修改隊列頭或任何隊列組成域時,驅動程序必須用自旋鎖保護隊列 4.2 避免死鎖問題 Windows並不限制網絡驅動程序同時持有多於一個的自旋鎖。但是,驅動程序的某部分在持有自旋鎖B時,試圖獲得自旋鎖A,並且其他部分在持有鎖A時,試圖獲得自旋鎖B時,死鎖就會發生。如果要獲得多於一個的自旋鎖,驅動程序應當通過強制以某一順序獲得鎖來避免死鎖,這就是說,如果一個驅動程序強制在獲得自旋鎖A之后才可獲得鎖B,那么上述情況就不會發生 總得來說,使用自旋鎖將對系統性能帶來負面效應,所以驅動程序不應當使用許多鎖 4.3 時鍾 時鍾被用來輪詢或進行超時操作的。一個驅動程序可以產生一個時鍾並與一個函數關聯上。當一個特定周期時鍾期滿時,調用相關函數。時鍾可以是一次的或周期性的,一但設置了一個周期時鍾,當每個周期結束時都會觸發,直到它被完全清除掉為止。一次性時鍾在觸發后必須重新設置 時鍾通過調用NdisMInitializeTimer來產生和初始化,並且通過調用NdisMsetTimer來設置,也可調用NdisMsetPeriodicTimer設置周期時鍾。如果使用了一個非周期時鍾,那么通過調用NdisMSetPeriodicTimer重新設置時鍾。通過調用NdisMCancelTimer可以清除時鍾 4.4 事件 事件在兩個執行線程之間實現同步操作。一個事件通過一個驅動程序裝入並且通過調用NdisInitializeEvent初始化。一個運行在IRQL PASSIVE_LEVEL下的線程調用NdisWaitEvent來將自身轉入等侯狀態。當一個驅動程序線程等待一個事件時,它指定了最大等待時間即等待事件的時間。當調用NdisSetEvent使時間得到信號量,或最大等待時間段結束時,它們兩個無論是誰先發生時都將結束線程等待狀態 典型的,事件是通過相互協調的線程調用NdisSetEvent來設置的。事件被創建時是沒有信號量的,但為了指示等待線程,它必須要設置信號量,事件將一直處於保持有信號狀態,直到NdiResetEvent調用后為止 5. 包結構 通過一個協議驅動程序可以分配NDIS包、填充數據,並且將它傳遞到下層的NDIS驅動程序,以便將數據發送到網絡上。一些最底層的NIC驅動程序分配包用來保存接收到的數據,並將包傳遞到對應的高層驅 6. 使用共享內存 用作總線管理DMA設備的微端口驅動程序必須為NIC和NIC驅動程序分配共享內存。當在一個驅動程序和它的NIC之間共享cache時,特別的預防是必須的。在某種結構下,必須采取特別步驟來保證內存一致,因為NIC可以直接訪問共享的物理內存,而NIC驅動程序卻要通過cache訪問內存。這就引起驅動程序和NIC訪問內存的不同,即使它們看起來在同一位置 7. 異步I/O和完成函數 因為在一些網絡操作中有繼承的因素,許多由NIC驅動程序提供的上層函數和協議驅動程序提供的下層函數被設計成支持異步操作,而不是用CPU消耗一定時間的循環來等待一個任務的完成或硬件事件的指示,網絡驅動程序依賴處理許多異步操作的能力 通過使用完成函數來支持異步網絡I/O。以下的例子將說明網絡的send操作如何使用一個完成函數,同樣的機制也存在一個協議或NIC驅動程序的其他操作中 當協議驅動程序調用NDIS發送一個包時,NDIS調用NIC驅動程序的MiniportSend函數發送請求,NIC驅動程序試圖立即完成這個請求並且返回一個恰當的狀態值。對於同步操作,可能返回NDIS_STATUS_SUCCESS作為發送成功的標志,NDIS_STATUS_RESOURCES和NDIS_STATUS_FAILURE表明有某些失敗。 但是一個發送操作要花費一些時間來完成,此時NIC驅動程序(或NDIS)可將包排隊並且等侯NIC指示發送操作的結果。NIC驅動程序的MiniportSend函數可以通過返回一個NDIS_STATUS_PENDING的狀態值來異步處理這個操作,當NIC驅動程序完成了發送操作后,包調用完成函數NdisMSendComplete在調用中傳遞指向一個已被發送的包的描述符的指針。這個信息會傳給協議驅動程序,指示完成了操作 許多需要一定時間來完成的驅動程序操作用完成數來完成支持異步的操作。這種函數有同一形式的名字NidisMXxxComplete。不僅可用於發送和接收函數,完成函數也可用於查詢、配置、重新設置硬件、狀態指示、指示收到數據和傳送收到數據
0x7: NDIS協議驅動的實現
1. 初始化 協議驅動程序必須提供一個入口函數DrverEntry 函數,相當與C 應用程序的main 函數,驅動程序也是從這里開始被調用執行的。DrverEntry 函數主要功能是 1) 對協議驅動中必須使用到的一系列protocol 函數進行注冊,並對驅動數據結構及資源進行初始化操作 2) 在 DriverEntry 必須調用NdisRegisterProtocol 函數注冊驅動的Protocol 函數入口點 3) 並且必須注冊ProtocolBindAdapter 和ProtocolUnBindAdapter 函數,以便在ProtocolBindAdapter 函數中繼續完成初始化工作 4) DriverEntry 函數中還需要注冊相關派遣例程等 Protocol 函數注冊后,驅動運行時會自動調用相關Protocol 函數完成相應功能。 2. 動態綁定 協議驅動程序通過提供ProtocolBindAdapter 函數和ProtocolUnbindAdapter 函數就可以支持對低層網卡的動態綁定(本質是一個高層接口規范,是底層物理網卡必須遵循和實現的一個接口規范)。如果驅動程序向NDIS 注冊了這些函數,那么將可以延遲打開和綁定低層網卡而不必在DriverEntry 函數中實現該功能,只要用ProtocolBindAdapter 函數就可代替執行該操作 如果協議驅動程序提供了這些函數,那么只要低層網卡可用,NDIS 將調用能夠將自己綁定到該適配器的任何協議驅動程序的ProtocolBindAdapter 函數。並且只要低層網卡被關閉,那么NDIS 將可以調用互逆的ProtocolUnbindAdapter 函數 3. 數據包的組織和管理 在 NDIS 中,數據的接收與發送都是以包為單位進行的。一個包由以下3 部分組成 1) 一個包描述符。其所含信息包括整個包所占用的物理頁面的數量、包的長度、指向第一個和最后一個緩沖區描述符的指針以及包池的句柄等等 2) 一組緩沖區描述符。每個緩沖區描述符r 用來描述一片存儲區域,其中包括起始虛擬地址、偏移量、該存儲區域的大小以及指向下一個緩沖區描述符的指針等信息 3) 由緩沖區描述符所描述的虛擬存儲區域,該區域可能橫跨幾個頁面。這些頁面最終被射到物理內存中 4. 接收數據 驅動初始化時會注冊兩個接收函數,ProtocolRecieve 和ProtocolRecievePacket 函數,當有數據包發送到網卡時,驅動會根據網卡類型調用所需函數,不支持多包接收的網卡或者接收單包且包描述符附有帶外數據的情況下會調用ProtocolRecieve 函數,而多包接收的網卡會調用ProtocolRecievePacket 函數
驅動程序負責維護一個接收緩沖區,該緩沖區以隊列的形式組織。當網卡通知NDIS已從網絡上接收到數據包時,可以先將這些數據緩存起來。當上層應用程序需要讀取數據時,該讀操作的數據源不是直接從網絡得到,而是經過有效管理的存放着數據的緩沖區
5. 發送數據 發送數據必須先組成需要發送的包格式,然后放到隊列中,等待發送。在發送前,要創建發送數據包池和緩沖池(在綁定時候創建),在需要發送數據時為包從包池中創建包描述符和緩沖描述符 當數據包組好后,首先對數據的合法性檢驗。如果數據太短,小於以太網包頭(14 個字節),則不合法;如果數據太長,比底層網卡所能支持的最大幀長度還長,則也不合法;然后判斷是否已經綁定網卡,如果已綁定則調用系統函數NdisAllocatePacket 分配和初始化一個包描述符,調用NdisAllocateBuffer 分配和初始化緩沖描述符,並給新分配得到的緩沖描述符賦值,指向系統空間中的用戶緩沖區,當資源分配完畢,會根據需要調用系統函數NdisSendPackets 或NdisSend 向下遞交一個發送請求
對 NdisSendPackets 的調用,系統會自動調用ProtocolSendComplete來完成發送操作,獲得發送的包的緩沖區信息,並釋放包緩沖區描述符
收包由響應網卡產生的中斷開始,發包由TDI傳入協議驅動的IRP開始
0x8: NDIS包描述符結構
NDIS包描述符在協議驅動和小端口驅動中都有用到,在WDK中定義了NDIS_PACKET結構
typedef struct _NDIS_PACKET { NDIS_PACKET_PRIVATE Private; union { struct { UCHAR MiniportReserved[2*sizeof(PVOID)]; UCHAR WrapperReserved[2*sizeof(PVOID)]; }; struct { UCHAR MiniportReservedEx[3*sizeof(PVOID)]; UCHAR WrapperReservedEx[sizeof(PVOID)]; }; struct { UCHAR MacReserved[4*sizeof(PVOID)]; }; }; ULONG_PTR Reserved[2]; UCHAR ProtocolReserved[1]; } NDIS_PACKET, *PNDIS_PACKET, **PPNDIS_PACKET;
需要明白的,NDIS的數據包是以一種指針隊列的形式存在的,也就是說,網卡收到數據庫產生中斷,通過Buffer緩存數據包,NDIS和網卡通過Buffer進行異步協同
Relevant Link:
https://msdn.microsoft.com/en-us/library/windows/hardware/ff557086(v=vs.85).aspx
3. NDIS微端口驅動編程實例
一個NDIS微端口驅動程序(也叫微端口NIC驅動程序)有兩個基本功能
1. 管理一個網絡接口卡(NIC),包括通過NIC發送和接收數據 2. 與高層驅動程序相接,例如中間層驅動程序和傳輸協議驅動程序
發送和接收操作表明了NDIS與高層驅動程序和微端口NIC的相互作用
1. 當一個傳輸驅動程序發送一個包時,它調用一個NDIS庫所提供的NdisXxx函數,NDIS於是通過調用由微端口提供的合適的MiniportXxx函數將包傳遞給微端口,然后微端口驅動程序通過調用恰當的NdisXxx函數將包傳遞給NIC來發送包 2. 當一個NIC接收到由NIC發給它的包時,它將產生一個由NDIS或NIC的微端口處理的中斷。NDIS通過調用恰當的MiniportXxx函數指示NIC的微端口。微端口通過調用恰當的NdisXxx函數把數據從NIC傳送到上層驅動程序,並且同時指示上層驅動程序接收包(NDIS在這里起到承上啟下作用,它再去調用對應的ProtocolXxx函數)
0x1: NIC微端口驅動程序類型
NDIS既支持無連接環境下的微端口驅動程序,也支持面向連接的微端口驅動程序,NDIS支持以下幾種類型的網絡接口卡(NIC)驅動程序
1. 無連接的微端口 例如Ethernt,FDDI和Token Ring,控制NIC。可將無連接微端口進一步分為以下幾種子類型 1) 串行化的驅動程序: 它依靠NDIS對MiniportXxx函數調用進行串行化,並管理它們的發送隊列 2) 非串行化驅動程序: 它自己對MiniportXxx函數操作進行串行化,並且在內部對進入的發送包進行排隊。它的意義在於有很高的效率,例如驅動程序的臨界區(在一個時間段內只有一個線程可訪問的代碼區)保持很小,但是,非串行化微端口必須滿足額外的並且是極苛刻的設計要求,同時還要求有額外的調試和測試時間 2. 面向連接的微端 面向連接的網絡介質,例如ATM和ISDN,控制NIC。面向連接的微端口經常對MiniportXxx函數操作進行串行化,並且在內部對進入的發送抱進行排隊
一個NDIS微端口驅動程序可以有一個非NDIS的底層,通過它的非NDIS底層,微端口使用某種類型的接口,例如通用串行總線構架(USB)或IEEEE1394(火線)來控制總線上的設備。微端口通過直接發送I/O請求包(IRPs)到總線或是發送到連接到總線的其他遠程設備上來與設備通信。在它的上層,微端口提供了標准的NDIS微端口接口,它使得微端口可以與上層NDIS驅動程序通信
NDIS也支持廣域網(WAN)范圍的控制WAN NIC的微端口
0x2: 網絡接口卡支持
Windows 2000支持以下幾種類型的網絡接口卡
1. Ethernet (802.3) 2. Token Ring (802.5) 3. FDDI 4. LocalTalk 5. Raw ARCNET (ARCNET 封裝在 Ethernet) 6. ARCNET 878.2 7. WAN(點對點和WAN卡) 8. 面向連接的WAN 9. 無線 10. ATM 11. IrDA
當NDIS調用一個NIC驅動程序的MiniportInitialize函數時,它傳遞了一組NDIS支持的介質。如果NIC驅動程序僅支持一種介質類型,它選擇它所支持的介質。或者,如果NIC驅動程序支持多於一種的介質類型,它選擇它所喜歡的那一種並且將它的選擇返回給NDIS
當一個高層NDIS協議驅動程序調用NdisOpenAdapter來綁定一個具體的NIC時,它提供了一系列它所能操作的介質類型,NDIS使用來自NIC的信息和來自協議驅動程序的信息來正確綁定它們。這個綁定提供了一條路徑,通過這條路徑,包可以在協議棧中往下傳遞並通過網絡傳遞出去,通過這條路徑,接收到的包可以傳到高層驅動程序
0x3: 微端口驅動程序代碼的重要特征
1. MiniportXxx函數 典型的微端口驅動程序僅使用很少的函數來通過NDIS與上層和硬件進行通信。NDIS為了完成自身的任務或協議驅動程序的服務,調用(MiniportXxx)函數列表與微端口通信;並不是所有的這些函數都是必須的,作為一個最基本的微端口驅動程序來說,它只需要滿足最低程度的NDIS的接口規范即可正常運行 2. NDIS NIC微端口和上層驅動程序使用NDIS庫(ndis.sys)通過調用NdisXxx函數在彼此間通信。許多微端口函數可以異步運行或同步運行 1) 對於異步函數,當操作完成以后,必須調用NdisXxx...Complete。例如,如果一個協議驅動程序調用NdisReset來復位一個NIC,微端口的MiniportReset函數通過返回NDIS_STATUS_PENDING來掛起復位操作。最終,MiniportReset函數必須調用NdisMResetComplete來指示復位請求的最終狀態 3. 與NDIS庫鏈接 NDIS庫封裝在ndis.sys中,這個內核模式庫提供了一系列函數接口,它側重於使功能最大化的宏。提供接口的庫是一個.sys文件,它的函數與動態鏈接庫相似。包括協議和NIC驅動程序,以及WAN NIC驅動程序,它們都與NDIS庫相鏈接 4. 微端口適配器環境 NDIS使用叫一個"邏輯適配器"的軟件對象來代表系統中每一個NIC。這個對象由NDIS管理,並且對微端口和協議驅動程序是透明的。NDIS將這個結構的句柄傳遞給NIC驅動程序的MiniportInitialize函數。以后微端口驅動程序在調用NdisXxx函數時,如果這個函數與此句柄表示的NIC有關,它應在函數中傳遞此句柄 當調用微端口NIC驅動程序來初始化一個它管理的NIC時,微端口驅動程序產生自己的內部數據結構來代表NIC。驅動程序使用這個結構,把它當作微端口適配器環境,來維護特定設備的狀態信息,這些信息是管理NIC所必須的。當驅動程序的MiniportInitialize函數調用NdisMSetAttributes或NdisMSetAttributesEx時,它將這個結構的句柄傳遞給NDIS。當NDIS調用與NIC相關的微端口的MiniportXxx函數時,它將指向NIC的句柄傳遞給適當驅動程序。微端口適配器環境為微端口所擁用並且為微端口所管理,它對於NDIS和協議驅動程序是透明的 5. VC環境 在建立一個呼叫之前,面向連接的客戶方請求面向連接的微端口建立一個虛擬連接(VC),通過它可以發送和/或接收包。同樣,在向面向連接的客戶方指示一個入站呼叫之前,呼叫管理器或集成的微端口呼叫管理器(MCM)請求微端口為入站呼叫建立一個VC。一個虛擬連接是兩個面向連接實體間的一個邏輯連接。面向連接的發送和接收通常在一個特定的VC上進行。 面向連接微端口在微端口在為每個VC分配的環境區中保存狀態信息。每一個VC的環境由微端口來管理,並且它對於NDIS和協議驅動程序是透明的。在它的MiniportCoCreateVC函數中,面向連接的微端口將VC環境區域的句柄傳遞給NDIS,NDIS將一個唯一指定的已存在的VC的NdisVcHandle回傳給微端口、回傳給適當的的面向連接的客戶方、回傳給呼叫管理器或集成微端口呼叫管理器(MCM) 在VC上發送和接收數據之前,VC必須被激話。呼叫管理器通過調用Ndis(M)CmDeactivateVc來初始化激活的VC並且向呼叫管理器傳遞呼叫參數,這包括用來激活VC的參數。作為響應,NDIS調用微端口的MiniportCoActivateVc函數來激活VC。 在一個呼叫結束或因其他原因不再需要VC時,呼叫管理器通過調用Ndis(M)CmDeactivateVc來去活VC,這也將引起NDIS調用微端口的MiniportCoDeactivateVc函數。對於面向連接的客戶方或呼收管理器,可以通過調用NdisCoDeleteVc來指示對VC的刪除,它將引起NDIS調用微端口的MiniportCoDeleteVc函數 6. 網絡OID 微端口維護有關它的性能和當前狀況的信息,以及有關它所控制的每個NIC的信息。每一個信息類型都由一個對象標識(OID)確認,OID由系統定義 NDIS和高層驅動程序可以使用OID來查詢信息以及在某些情況下設置信息 1) 對於無連接介質的高層驅動程序,它調用NdisRequest來查詢或設置一個無連接微端口中的信息。為了執行查詢操作,NDIS調用微端口的MiniportQueryInformation函數。為了執行設置操作,NDIS調用微端口的MiniportSetInformation函數 2) 對於面向連接介質的高層驅動程序,它調用NdisCoRequest來查詢或設置面向連接微端口中的信息。為了執行查詢和設置操作,NDIS調用微端口的MiniportCoRequest函數 NDIS將許多系統為微端口定義的OID映射為全局的唯一標識(GUIDs)。NDIS向內核模式的Windows管理檢測器(WMI)來注冊這些GUIDS。WMI支持用戶模式下的基於Web的企業管理(WBEM)應用。當一個WMI客戶方查詢或設置這些GUIDS中的一個值時,NDIS將這些請求轉換為一個恰當的OID查詢操作或一個OID設置操作,並且把信息和狀態返回給WMI。驅動程序編寫者可將自定義的GUID映射為客戶OID或微端口狀態。一個微端口必須在初始化時向NDIS注冊客戶的GUID-to-OID或GUID-to-status映射
0x4: NDIS微端口驅動程序入口函數
每個NIC微端口驅動程序必須提供一個DriverEntry的函數。DriverEntry由系統調用來裝載驅動程序。DriverEntry產生一個微端口NIC驅動程序和NDIS庫的連接,並且向NDIS注冊微端口的版本和入口指針
.. NDIS_HANDLE NdisWrapperHandle; .. NTSTATUS DriverEntry( //指向驅動程序對象的指針,它由I/O系統產生 IN PDRIVER_OBJECT DriverObject, /* 指向驅動程序對象的指針,它由I/O系統產生 注冊表包含系統重起時永久存在的數據,它同每次系統重起時重新產生的設置信息一樣。在驅動程序安裝時,描述驅動程序和NIC的數據存儲在注冊表,注冊表包含NIC驅動程序可讀取的,用來初始化自身和NIC的適配器的特性 */ IN PUNICODE_STRING RegistryPath ) { NDIS_MINIPORT_CHARACTERISTICS MChars; .. /* 1. 初始化包裹 調用NdisMInitializeWrapper函數來使得微端口NIC驅動程序和NDIS相聯系,NdisMInitializeWrapper分配一個結構來代表這個聯系,來存儲NDIS庫所需的微端口相關的信息,並且返回NdisWrapperHandle 它是代表這個微端口NIC驅動程序結構的句柄。當驅動程序注冊它的入口指針時,它必須保留和傳遞這個句柄到NdisMRegisterMiniport。NDIS將使用NdisWrapperHandle來辯別微端口,微端口必須保留這個句柄,但它不應試圖去訪問或解釋這個句柄 */ NdisMInitializeWrapper(&NdisWrapperHandle, DriverObject, RegistryPath, NULL); .. /* 2. 注冊微端口 在DriverEntry函數調用NdisMInitializeWrapper產生與NDIS庫的一個連接后,它必須初始化一個NDIS_MINIPORT_CHARACTERISTICS類型的結構。NDIS_MINIPORT_CHARACTERISTICS結構指定了與微端口兼容的NDIS版本以及要求的入口點和由微端口提供的可選上層函數(MiniportXxx)。然后DriverEntry通過一個指向NDIS_MINIPORT_CHARACTERISTICS結構的指針調用NdisMRegisterMiniport */ /* 2.1 指定NDIS版本號 NDIS_MINIPORT_CHARACTERISTICS結構中的MajorNdisVersion和MinorNdisVersion成員指定了與微端口兼容的NDIS版本。有效的NDIS版本號是5.0,4.0,或3.0。最新版本是5.0。NDIS繼續支持早期為4.0和3.0版編寫的驅動程序。因此NDIS版本號,包括包含在NDIS_MINIPORT_CHARACTERISTICS中的,都必須在微端口源代碼編釋時指定 */ MChars.MajorNdisVersion = PASSTHRU_MAJOR_NDIS_VERSION; MChars.MinorNdisVersion = PASSTHRU_MINOR_NDIS_VERSION; /* 2.2 注冊MiniportXxx函數 NDIS_MINIPORT_CHARACTERISTICS結構中每個地址成員必須被初始化——也就是說,它必須被設置成微端口提供的函數地址或NULL。微端口必須為所有MiniportXxx函數提供入口指針;否則NDIS將不允許裝載驅動程序。MiniportXxx函數在ndis.h頭文件中定義。 因為微端口提供MiniportXxx函數的地址,而不是名字,驅動程序開發者可以自由的以他們喜好命名。一個合理的微端口開發策略是給這函數命名以使調試容易。例如,微端口NIC驅動程序mp.sys命名入口指針為MPISR,MpSend等 一些MiniportXxx函數是可選擇的(之前說過,微端口驅動只需要滿足NDIS最基本的可滿足運行的接口規范即可)。例如,如果一個NIC不產生中斷,微端口必須輪詢它的NIC。這種微端口必須有一個MiniportTimer函數,但不需要MiniportISR函數、MiniportEnableInterrupt函數或MiniportDisableInterrupt函數 */ MChars.InitializeHandler = MPInitialize; MChars.QueryInformationHandler = MPQueryInformation; MChars.SetInformationHandler = MPSetInformation; MChars.ResetHandler = NULL; MChars.TransferDataHandler = MPTransferData; MChars.HaltHandler = MPHalt; #ifdef NDIS51_MINIPORT MChars.CancelSendPacketsHandler = MPCancelSendPackets; MChars.PnPEventNotifyHandler = MPDevicePnPEvent; MChars.AdapterShutdownHandler = MPAdapterShutdown; #endif // NDIS51_MINIPORT MChars.CheckForHangHandler = NULL; MChars.ReturnPacketHandler = MPReturnPacket; MChars.SendHandler = NULL; // MPSend; MChars.SendPacketsHandler = MPSendPackets; Status = NdisIMRegisterLayeredMiniport(NdisWrapperHandle, &MChars, sizeof(MChars), &DriverHandle); }
0x5: NDIS微端口初始化
當一個微端口DriverEntry函數返回時,NDIS立即為每個微端口管理的NIC,調用微端口的MiniportInitialize函數。如果一個新的NIC插入到系統,將調用微端口的MiniportInitialize函數來初始化新安裝的設備。因此, MiniportInitialize函數和其他調用函數,以及所有運行在IRQL PASSIVE_LEVEL的函數都可被指定為可分頁的。通過使用NDIS_PAGABLE_FUNCTTON宏來指定代碼為可分頁的。初始化代碼不能用NDIS_INIT_FUNCTION宏來指定,這是由於標記這種方式的代碼在初始化系統的啟動結束之際,已不再映射了。僅有DriverEntry函數和從DrireEntry調用的函數可以傳遞到NDIS_INTI_FUNCTLON宏
NDIS_STATUS MPInitialize( OUT PNDIS_STATUS OpenErrorStatus, //微端口必須選擇它所支持或兼容的介質。如果微端口沒有在MediumArray中發現它所支持的介質,將返回一個失敗狀態NDIS_STATUS_UNSUPPORTED_MEDIA OUT PUINT SelectedMediumIndex, IN PNDIS_MEDIUM MediumArray, IN UINT MediumArraySize, //MiniportAdapterHandle,一個可被NDIS引用的微端口句柄。微端口必須保留這個句柄,以使它在以后的調用中可傳遞給NDIS。例如,在NdisMRegisterAdapterShutdownHandler和NdisMInitializeTimer調用中 IN NDIS_HANDLE MiniportAdapterHandle, //MiniportAdapterHandle,一個可被NDIS引用的參照微端口句柄。微端口必須保留這個句柄,以使它在以后的調用中可傳遞給NDIS。——例如,在NdisMRegisterAdapterShutdownHandler和NdisMInitializeTimer調用中 IN NDIS_HANDLE WrapperConfigurationContext ) { .. /* 1. retrieving our adapter context and storing the Miniport handle in it */ pAdapt = NdisIMGetDeviceContext(MiniportAdapterHandle); pAdapt->MiniportIsHalted = FALSE; Medium = pAdapt->Medium; if (Medium == NdisMediumWan) { Medium = NdisMedium802_3; } for (i = 0; i < MediumArraySize; i++) { if (MediumArray[i] == Medium) { *SelectedMediumIndex = i; break; } } if (i == MediumArraySize) { Status = NDIS_STATUS_UNSUPPORTED_MEDIA; break; } .. /* 2. 注冊NIC 在微端口讀取它所需要的描述有關NIC特征的信息,並且按要求方式將此信息存儲在它的微端口適配器環境中之后,微端口調用NdisMSetAttributes或NdisMSetAttributeEx。在這個調用中,微端口作如下工作 2.1) 傳遞由NDIS提供的微端口適配器句柄到微端口的MiniportInitialize函數 2.2) 傳遞微端口適配器環境句柄,當NDIS調用任何MiniportXxx函數時,NDIS將這個適配器指定環境區域的句柄傳遞給微端口 2.3) 指定它的NIC是否是一個總線管理器DMA設備 2.4) 指定調用者的NIC的I/O總線接口類型 通過NdisMSetAttributesEx,微端口可以指定有關超時和Token Ring錯誤的NDIS缺省行為的變化 中間層驅動程序必須設置NDIS_ATTRIBUTE_INTERMEDLATE_DRIVER標志來調用NdisMSetAttributerEx 中間層驅動程序也必須設置NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND標志,來阻止微端口在系統轉向低耗能(睡眠)狀態時,停止驅動程序 一個非串行微端口必須設置NDIS_ATTRIBUTE_DESERIALIIE標志來調用NdisMSetAttrributerEx。一個面向連接的微端口,總是非串行的,所以並不需要這么做 */ //3. Create an ioctl interface (VOID)PtRegisterDevice(); .. }
0X6: MiniportXxx: 微端口接口函數邏輯實現
在初始化並注冊了微端口的描述句柄后(如果是在NDIS中間層協議中就僅僅是一個虛擬的微端口是配置,但是從內核邏輯層次上來說,它就是一個標准的微端口),需要對微端口描述句柄中賦值的函數句柄進行邏輯功能的實現
VOID MPSendPackets( IN NDIS_HANDLE MiniportAdapterContext, IN PPNDIS_PACKET PacketArray, IN UINT NumberOfPackets ) { PADAPT pAdapt = (PADAPT)MiniportAdapterContext; FILTER_STATUS fStatus; NDIS_STATUS Status; UINT i; PVOID MediaSpecificInfo = NULL; UINT MediaSpecificInfoSize = 0; for (i = 0; i < NumberOfPackets; i++) { PNDIS_PACKET Packet, MyPacket; Packet = PacketArray[i]; fStatus = AnalysisPacket(Packet, FALSE); if(fStatus == STATUS_DROP){ // 在這個函數中,任何一個被放棄的包,都必須調用NdisMSendComplete。 NdisMSendComplete(ADAPT_MINIPORT_HANDLE(pAdapt), Packet, NDIS_STATUS_FAILURE); continue; // 丟棄之 }else if(fStatus == STATUS_REDIRECT){ // 轉發...... // TODO. } .. .. NDIS_STATUS MPTransferData( OUT PNDIS_PACKET Packet, OUT PUINT BytesTransferred, IN NDIS_HANDLE MiniportAdapterContext, IN NDIS_HANDLE MiniportReceiveContext, IN UINT ByteOffset, IN UINT BytesToTransfer ) { PADAPT pAdapt = (PADAPT)MiniportAdapterContext; NDIS_STATUS Status; if (IsIMDeviceStateOn(pAdapt) == FALSE) { return NDIS_STATUS_FAILURE; } NdisTransferData(&Status, pAdapt->BindingHandle, MiniportReceiveContext, ByteOffset, BytesToTransfer, Packet, BytesTransferred); if(Status == NDIS_STATUS_SUCCESS){ // 已經完全傳遞好了,是一個完整的NDIS Packet FILTER_STATUS fStatus = AnalysisPacket(Packet, TRUE); if(fStatus == STATUS_DROP){ Status = NDIS_STATUS_FAILURE; // 丟棄之 }else if(fStatus == STATUS_REDIRECT){ // 轉發...... // TODO. } } return(Status); } .. NDIS_STATUS MPSend( IN NDIS_HANDLE MiniportAdapterContext, IN PNDIS_PACKET Packet, IN UINT Flags ) { PADAPT pAdapt = (PADAPT)MiniportAdapterContext; NDIS_STATUS Status; FILTER_STATUS fStatus; PNDIS_PACKET MyPacket; PVOID MediaSpecificInfo = NULL; ULONG MediaSpecificInfoSize = 0; if (pAdapt->MPDeviceState > NdisDeviceStateD0) { return NDIS_STATUS_FAILURE; } fStatus = AnalysisPacket(Packet, FALSE); if(fStatus == STATUS_DROP){ return NDIS_STATUS_FAILURE; // 丟棄之 }else if(fStatus == STATUS_REDIRECT){ // 轉發...... // 修改包中的目標地址,和正常發送一樣將包向下發送 // TODO. } .. ..
對於NDIS過濾驅動來說,我們可以在這里攔截到所有的本機將要外發的數據包
Relevant Link:
http://files.cnblogs.com/files/LittleHann/netdriver.pdf
4. NDIS中間層驅動編程實例
雖然向上層提供了MiniportXxx函數的一個子集,但是事實上中間層驅動程序並不管理物理的NIC。它只是向上層協議提供了一個或多個可以綁定的虛擬適配器。對於一個協議驅動程序,一個由中間層驅動程序提供的適配器就如同一個物理的NIC。當協議驅動程序發送包或向一個實際的適配器時發出請求時,中間層驅動程序將傳遞這些包和請求到下層的微端口。當下層微端口向上發出接收包的指示、響應協議請求的信息或指示狀態信息時,中間層驅動程序將這些包、響應、和狀態指示傳遞給綁定在虛擬適配器上的協議驅動程序
中間層驅動程序有如下幾種典型使用方法
1. 在不同的網絡介質間進行轉換 例如,處於Ethernet和Token Ring傳輸層及一個ATM微端口之間的中間層驅動程序的功能是將Ethenet和Token Ring的包轉換為ATM包,反之亦然 2. 過濾包 一個包調度器是中間層驅動程序用作過濾驅動程序的很好例子。包調度器讀出每個傳輸層傳下來的發送數據包和每一個由微端口指示的接收包的優先權。然后包調度器以它的優先權調度抱的接收和發送順序 3. 在多個NIC間平衡包的負載 負載平衡驅動程序向上層傳輸協議提供了一個虛擬的適配器,但實際上它卻將包分配了給多個NIC
為了使加載程序能夠准確地識別,必須將中間層驅動程序的初始入口點明確地指定為DriverEntry的形式。所有其他的驅動程序導出函數,像這里所描述的MiniportXxx和ProtocolXxx函數,由於其地址傳給了NDIS,因此在設計時可由開發者任意指定名稱,在中間層驅動程序中,DriverEntry至少應該完成以下工作
1. 調用NdisMInitializeWrapper並保存在NdisWrapperHandle中返回的句柄 2. 傳遞上一步保存的句柄,調用NdisIMRegisterLayeredMiniport注冊驅動程序的MiniportXxx函數 3. 如果驅動程序隨后要綁定到低層NDIS驅動程序上,則調用NdisRegisterProtocol注冊驅動程序的ProtocolXxx函數 4. 如果驅動程序導出了MiniportXxx和ProtocolXxx函數,那么調用NdisIMAssociateMiniport向NDIS通告有關驅動程序的微端口低邊界和協議高邊界信息
DriverEntry能夠為中間層驅動程序分配的所有共享資源初始化自旋鎖,例如驅動程序用於跟蹤運行中的連接和發送任務的構件和內存區
當DriverEntry不能為驅動程序分配用於網絡I/O操作的所有資源時,它就應該釋放先前已經分配的任何資源並返回一個適當的錯誤狀態。例如,如果DriverEntry已經調用了NdisMInitializeWrapper函數,那么當后續操作出錯時必須調用NdisTerminateWrapper復位系統狀態。
中間層驅動程序的DriverEntry函數能夠執行一些全局初始化操作。然而,如果驅動程序提供了實現對低層設備的打開和綁定功能的ProtocolBindAdapter函數,那么驅動程序就能夠讓ProtocolBindAdapter來分配綁定相關的系統資源,ProtocolBindAdapter根據要求為DeviceName設備進行資源分配和綁定操作。DriverEntry必須初始化該包裹程序並注冊微端口驅動程序。如果中間層驅動程序導出了一組ProtocolXxx函數的話,也要注冊協議驅動程序
如果中間層驅動程序僅向NDIS導出了一組MiniportXxx函數,只要向NDIS庫注冊這些函數即可
0x1: 中間層過濾驅動邏輯流程
1. 中間層驅動的初始化 [passthru] 1) 初始化小端口部分: NdisMInitializeWrapper -> NDIS_MINIPORT_CHARACTERISTICS –> NdisIMRegisterLayeredMiniport 2) 初始化協議驅動部分並關聯至小端口: NDIS_PROTOCOL_CHARACTERISTICS -> NdisRegisterProtocol -> NdisIMAssociateMiniport 3) passthru初始化總體結構 2. 封包結構分析與過濾 [TCP端口過濾] 要對TCP端口進行過濾,很明顯必須得先對TCP協議封包的結構進行分析。不同協議的封包格式都不盡相同,對封包格式有興趣的童鞋可以使用wireshark多抓幾個進行分析。TCP協議的封包總共有三個部分:第一是以太網部分,第二是IP部分(前兩部分是所有協議的封包都有的),第三就是TCP部分了。 3. 獲取封包 中間層驅動初始化完畢后,只需要在初始化時設置的相應回調函數進行處理即可。回調函數設置好了,封包的發送與接收都會調用我們的回調函數。過濾函數一般都在相應的收包發包函數內部的前端,如此一來可以提升處理效率。當然也省了不少麻煩(比如人家內存都分配好了,結果你要丟包)。不過也不死板,可以根據實際情況進行編寫。對於發送出去的數據包處理,只要在PassThru中的MiniportSend中加入必要的操作代碼,而對於接收的數據包時,則需要在ProtocolReceive和ProtocolReceviePackets中加入必要的操作代碼 4. 過濾規則的編寫 NDIS封包的過濾有很多種處理方式:延遲或重新排序、加密或解密、壓縮或解壓、包路由(NAT網絡地址轉換、LBFO負載平衡和失效替換)。如果修改了封包的內容的話,例如端口轉發,需要重新修正校驗和調整緩沖區和長度等相關信息。修改時需要注意字節順序的問題 5. 驅動程序加載方式:控制面板 -> 網絡連接 -> 本地連接 -> 屬性 -> 安裝 -> 服務 -> 添加 -> 從磁盤安裝 -> 選擇我們inf文件,忽略一切警告一路確定即可
NDIS中間層驅動源代碼
http://files.cnblogs.com/files/LittleHann/passthru.zip
0x2: 中間層驅動發送數據包
中間層驅動之所以能被作為過濾驅動使用,是因為它處於上下交通的要沖之地,上下往來的數據包它都能夠截獲並加以處理
當上層驅動將數據包發送到中間層驅動時,中間層驅動能夠在它注冊的MpSend函數中接收到這些包描述符,這時候,中間層驅動要么直接數據包描述符傳遞給底層驅動;要么將數據重新打包生成新的描述符后,再發送出去;或者直接拒絕這個包並返回一個錯誤值(防火牆的包過濾功能,就是在這里體現的)
中間層驅動要發送網絡數據包,最終都必須調用NdisSend/NdisSendPackets,要實現利用中間層驅動進行包發送,我們有如下幾種方法
1. "包描述符重利用"技術 把收到的包描述符不動地傳遞下去(重放) 2. "包重申請"技術 根據收到的包描述符,重建一個新的包描述符,將新建的描述符發送下去
0x3: 中間層驅動數據包過濾分析邏輯
FILTER_STATUS AnalysisPacket(PNDIS_PACKET Packet, BOOLEAN bRecOrSend) { FILTER_STATUS status = STATUS_PASS; // 默認全部通過 PNDIS_BUFFER NdisBuffer ; UINT TotalPacketLength = 0; UINT copysize = 0; UINT DataOffset = 0 ; UINT PhysicalBufferCount; UINT BufferCount ; PUCHAR pPacketContent = NULL; char* tcsPrintBuf = NULL; PUCHAR tembuffer = NULL ; UINT j; PTCPv4_HEADER pTCPHeader; USHORT iphdrlen; __try{ status = NdisAllocateMemoryWithTag( &pPacketContent, 2048, TAG); if( status != NDIS_STATUS_SUCCESS ){ status = NDIS_STATUS_FAILURE ; __leave; } NdisZeroMemory( pPacketContent, 2048 ) ; // 找到第一個Ndis_Buffer。然后通過通過NdisGetNextBuffer來獲得后續的NDIS_BUFFER。 // 如果只是找第一個節點,更快且方便的方法是調用NdisGetFirstBufferFromPacket。 NdisQueryPacket(Packet, // NDIS_PACKET &PhysicalBufferCount,// 內存中的物理塊數 &BufferCount, // 多少個NDIS_BUFFER包 &NdisBuffer, // 將返回第一個包 &TotalPacketLength // 總共的包數據長度 ); while(TRUE){ // 取得Ndis_Buffer中存儲緩沖區的虛擬地址。 // 這個函數的另一個版本是NdisQueryBuffer。 // 后者在系統資源低或者甚至耗盡的時候,會產生Bug Check,導致藍屏。 NdisQueryBufferSafe(NdisBuffer, &tembuffer,// 緩沖區地址 ©size, // 緩沖區大小 NormalPagePriority ); // 如果tembuffer為NULL,說明當前系統資源匱乏。 if(tembuffer != NULL){ NdisMoveMemory( pPacketContent + DataOffset , tembuffer, copysize) ; DataOffset += copysize; } // 獲得下一個NDIS_BUFFER。 // 如果得到的是一個NULL指針,說明已經到了鏈式緩沖區的末尾,我們的循環應該結束了。 NdisGetNextBuffer(NdisBuffer , &NdisBuffer ) ; if( NdisBuffer == NULL ) break ; } // 取得數據包內容后,下面將對其內容進行過濾。 // 我們在這個函數中的實現,僅僅簡單地打印一些可讀的Log信息。 if(pPacketContent[12] == 8 && pPacketContent[13] == 0 ) //is ip packet { PIP_HEADER pIPHeader = (PIP_HEADER)(pPacketContent + IP_OFFSET); DBGPRINT(("╔════════════IP協議結構打印══════════\n")); DBGPRINT(("║ 4位版本號: %2x\n", pIPHeader->Protocol)); DBGPRINT(("║ 8位服務類型(TOS): %2x\n", pIPHeader->TOS)); DBGPRINT(("║ 16位總長度(字節): %2x\n", pIPHeader->TotLen)); DBGPRINT(("║ 16位標識: %2x\n", pIPHeader->ID)); DBGPRINT(("║ 13位片偏移: %2x\n", pIPHeader->FlagOff)); DBGPRINT(("║ 8位生存時間(TTL): %2x\n", pIPHeader->TTL)); DBGPRINT(("║ 8位協議: %2x\n", pIPHeader->Protocol)); DBGPRINT(("║ 16位首部校驗和: %2x\n", pIPHeader->Checksum)); DBGPRINT(("╚═════════════════════════════════\n")); switch(pIPHeader->Protocol) { case PROT_ICMP: if(bRecOrSend){ DBGPRINT(("Receive ICMP packet [%d]\n",++ICMPRecv)); } else{ DBGPRINT(("Send ICMP packet [%d]\n",++ICMPSend)); } // // 取得ICMP頭,做出你的過濾判斷。 // if(ICMP_PASS) { return STATUS_DROP; } break; case PROT_UDP: if(bRecOrSend){ DBGPRINT(("Receive UDP packet [%d]\n",++UDPRecv)); } else{ DBGPRINT(("Send UDP packet [%d]\n",++UDPSend)); } // // 取得UDP頭,做出你的過濾判斷。 // break; case PROT_TCP: if(bRecOrSend){ DBGPRINT(("Receive TCP packet [%d]\n",++TCPRecv)); } else{ DBGPRINT(("Send TCP packet [%d]\n",++TCPSend)); } // // 取得TCP頭,做出你的過濾判斷。 // iphdrlen = (pIPHeader->VIHL & 0x0f) * sizeof(ULONG); pTCPHeader = (PTCPv4_HEADER)(pPacketContent+14 + iphdrlen); //tcphdrlen = ((pTCPHeader->dataoffset & 0xf0) >> 4) * sizeof(ULONG); //tcphdrlen = htons(pIPHeader->ipLength) - iphdrlen; DBGPRINT(("╔════════════TCP協議結構打印══════════\n")); DBGPRINT(("║ TCP 源端口: %2x\n",pTCPHeader->SourcePort)); DBGPRINT(("║ TCP 目的端口:%2x\n",pTCPHeader->DestinationPort)); DBGPRINT(("║ 32位序列號: %2x\n",pTCPHeader->SequenceNumber)); DBGPRINT(("║ 32位確認號: %2x\n",pTCPHeader->AckNumber)); DBGPRINT(("║ 4位首部長度: %2x\n",pTCPHeader->DataOffset)); DBGPRINT(("║ 保留(16位): %2x\n",pTCPHeader->Flags)); DBGPRINT(("║ 16位窗口大小:%2x\n",pTCPHeader->Window)); DBGPRINT(("║ 校驗和: %2x\n",pTCPHeader->Checksum)); DBGPRINT(("║ 16位緊急指針:%2x\n",pTCPHeader->Urgent)); DBGPRINT(("╚═════════════════════════════════\n")); /* if(pTCPHeader->SourcePort == TcpPort) { return STATUS_DROP; } if(pTCPHeader->DestinationPort == TcpPort) { return STATUS_DROP; } */ break; } }else if(pPacketContent[12] == 8 && pPacketContent[13] == 6 ){ if(bRecOrSend){ DBGPRINT(("Receive ARP packet [%d]\n",++ARPRecv)); //分析接收的ARP封包 // if(ARP_PROTECT){} } else{ DBGPRINT(("Send ARP packet [%d]\n",++ARPSend)); //分析發送的ARP封包 // if(ARP_PROTECT){} } }else{ if(bRecOrSend) DbgPrint("Receive unknown packet\n"); else DbgPrint("Send unknown packet\n"); } // 簡單打印出包數據內容 status = NdisAllocateMemoryWithTag( &tcsPrintBuf, 2048*3, TAG); //分配內存塊 if( status != NDIS_STATUS_SUCCESS ){ status = NDIS_STATUS_FAILURE ; __leave; } for(j=0;j<=DataOffset;j++) RtlStringCbPrintfA(tcsPrintBuf+j*3, 2048*3-j*3, "%2x ",pPacketContent[j]); DbgPrint(tcsPrintBuf); DbgPrint("\n"); }__finally{ if(pPacketContent)NdisFreeMemory(pPacketContent, 0, 0); if(tcsPrintBuf)NdisFreeMemory(tcsPrintBuf, 0, 0); } return STATUS_PASS; }
0x4: 用戶態和中間層驅動通信
一種常見的需求是,希望從用戶層或其他的內核模塊中,能夠直接和我們編寫的NDIS中間層驅動進行通信,我們可以安裝自己的分發函數,並且在私有的分發函數中繼續調用舊的分發函數來繼續兼容原始的內核邏輯,通過Hook設備創建函數,在內核態中創建我們私有的命名設備對象,從而在用戶態使用write、read等簡單接口進行ring3-ring0通信
// 這里實現Hook // systemAddDevice = DriverObject->DriverExtension->AddDevice; DriverObject->DriverExtension->AddDevice = myAddDevice; // Hook分發函數 // systemCreate = DriverObject->MajorFunction[IRP_MJ_CREATE]; DriverObject->MajorFunction[IRP_MJ_CREATE] = myCreate; systemWrite = DriverObject->MajorFunction[IRP_MJ_WRITE]; DriverObject->MajorFunction[IRP_MJ_WRITE] = myWrite; systemRead = DriverObject->MajorFunction[IRP_MJ_READ]; DriverObject->MajorFunction[IRP_MJ_READ] = myRead; systemDeviceControl = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = myDeviceControl;
Relevant Link:
寒江獨釣-Windows內核安全編程(完整版).pdf
5. NDIS協議層驅動編程實例
協議驅動程序和NDIS進行通訊來發送和接收網絡數據包,並綁定和使用低層微端口 NIC驅動程序或中間層NDIS驅動程序
1. NDIS協議驅動程序可能在上邊界支持TDI,或者可以通過驅動程序的傳送棧,其中包括棧頂支持TDI的棧,向高層核心模式驅動程序導出私有接口。例如,NDIS協議驅動程序可能是多模塊傳輸實現的標准協議的最低層模塊,如最高層模塊支持TDI的TCP/IP協議 2. 低層NDIS驅動程序通信來發送和接收數據包的協議驅動程序總是使用NDIS提供的函數來進行通信。例如,下邊界面向無連接的協議驅動程序(和面向無連接介質的低層驅動程序進行通信,如以太網、令牌環網)必須調用NdisSend或者NdisSendPackets向低層NDIS驅動程序發送數據包,也必須調用NdisRequest來產生或傳送查詢,以及用網絡相關的低層面向無連接驅動程序支持的OID_XXX的設置信息請求 3. NDIS也提供一組隱藏低層操作系統細節的NdisXxx函數。例如,協議驅動程序調用NdisInitializeEvent為同步目的創建事件,調用NdisInitializeListHead創建鏈表。使用那些函數的NDIS版本的協議驅動程序在支持WIN32接口的微軟操作系統中可移植性更好。協議驅動程序也可調用OS專用的核心模式支持的例程,像用KeInitializeEvent創建事件、用KeWaitForSingleObject同步兩個執行線程
0x1: 協議DriverEntry及其初始化
對於驅動程序初始化要求的入口點,為了使引導程序能夠識別,必須被明確地命名為DriverEntry形式。所有其他的被描述為ProtocolXxx的導出函數,由於其地址被傳給了NDIS,則可由開發者指定任何確定的名字
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { //零初始化一個NDIS_PROTOCOL_CHARACTERISTICS類型的結構 NDIS_PROTOCOL_CHARACTERISTICS PChars; .. //填充協議描述句柄的成員字段 NdisZeroMemory(&PChars, sizeof(NDIS_PROTOCOL_CHARACTERISTICS)); PChars.MajorNdisVersion = PASSTHRU_PROT_MAJOR_NDIS_VERSION; PChars.MinorNdisVersion = PASSTHRU_PROT_MINOR_NDIS_VERSION; PChars.Name = Name; //OpenAdapterCompleteHandler: 這是一個必須提供的函數。如果協議驅動程序對NdisOpenAdapter的調用返回NDIS_STATUS_PENDING,則接着調用ProtocolOpenAdapterComplete來完成綁定操作 PChars.OpenAdapterCompleteHandler = PtOpenAdapterComplete; //CloseAdapterCompleteHandler: 這是一個必須提供的函數。如果協議驅動程序對NdisCloseAdapter的調用返回NDIS_STATUS_PENDING,則接着調用ProtocolCloseAdapterComplete來完成解除綁定操作 PChars.CloseAdapterCompleteHandler = PtCloseAdapterComplete; PChars.SendCompleteHandler = PtSendComplete; PChars.TransferDataCompleteHandler = PtTransferDataComplete; PChars.ResetCompleteHandler = PtResetComplete; PChars.RequestCompleteHandler = PtRequestComplete; //ReceiveHandler: 這是一個必須提供的函數。ProtocolReceive函數以前視緩沖區的指針為參數被調用執行。如果該緩沖區包含的不是完整的接收到的網絡數據包,ProtocolReceive以協議分配的數據包描述符作為參數,調用NdisTransferData指定協議分配緩沖區接收數據包的其余部分 PChars.ReceiveHandler = PtReceive; //ReceiveCompleteHandler: 這是一個必須提供的函數。ProtocolReceiveComplete用來指出:以前指示給ProtocolReceive 的接收數據包現在可以延期處理 PChars.ReceiveCompleteHandler = PtReceiveComplete; PChars.StatusHandler = PtStatus; PChars.StatusCompleteHandler = PtStatusComplete; //BindAdapterHandler: 這是一個必須提供的函數。NDIS調用該函數請求協議驅動程序綁定到低層網卡或虛擬網卡上,網卡名作為該處理程序的參數傳遞 PChars.BindAdapterHandler = PtBindAdapter; //UnbindAdapterHandler: 這是一個必須提供的函數。NDIS調用ProtocolUnbindAdapter釋放對低層網卡或虛擬網卡的綁定,網卡名作為參數傳遞。當綁定成功解除時,ProtocolUnbindAdapter函數調用NdisCloseAdapter並釋放資源 PChars.UnbindAdapterHandler = PtUnbindAdapter; PChars.UnloadHandler = PtUnloadProtocol; PChars.ReceivePacketHandler = PtReceivePacket; PChars.PnPEventHandler= PtPNPHandler; /* 1. 注冊NDIS協議驅動程序 該調用的返回句柄NdisProtocolHandler對協議驅動程序是透明的,協議驅動程序必須保存該句柄並在將來對NDIS的調用中作為輸入參數傳遞,例如,打開低層適配器 */ NdisRegisterProtocol(&Status, &ProtHandle, &PChars, sizeof(NDIS_PROTOCOL_CHARACTERISTICS)); } ..
0x2: ProtocolXxx協議驅動接口邏輯功能實現
對於NDIS過濾驅動來說,最重要的要實現的就是數據包接收函數
INT PtReceivePacket( IN NDIS_HANDLE ProtocolBindingContext, IN PNDIS_PACKET Packet ) { PADAPT pAdapt =(PADAPT)ProtocolBindingContext; NDIS_STATUS Status; PNDIS_PACKET MyPacket; BOOLEAN Remaining; FILTER_STATUS fStatus; if ((!pAdapt->MiniportHandle) || (pAdapt->MPDeviceState > NdisDeviceStateD0)) { return 0; } fStatus = AnalysisPacket(Packet, TRUE); if(fStatus == STATUS_DROP){ return 0;// 丟棄之 }else if(fStatus == STATUS_REDIRECT){ // 轉發...... // TODO. } .. .. VOID PtTransferDataComplete( IN NDIS_HANDLE ProtocolBindingContext, IN PNDIS_PACKET Packet, IN NDIS_STATUS Status, IN UINT BytesTransferred ) { PADAPT pAdapt =(PADAPT)ProtocolBindingContext; // 到達這里,說明MPTransferData返回了Pending FILTER_STATUS fStatus = AnalysisPacket(Packet, TRUE); if(fStatus == STATUS_DROP){ Status = NDIS_STATUS_FAILURE;// 丟棄之 }else if(fStatus == STATUS_REDIRECT){ // 轉發...... // TODO. } if(pAdapt->MiniportHandle) { NdisMTransferDataComplete(pAdapt->MiniportHandle, Packet, Status, BytesTransferred); } } .. NDIS_STATUS PtReceive( IN NDIS_HANDLE ProtocolBindingContext, IN NDIS_HANDLE MacReceiveContext, IN PVOID HeaderBuffer, IN UINT HeaderBufferSize, IN PVOID LookAheadBuffer, IN UINT LookAheadBufferSize, IN UINT PacketSize ) { PADAPT pAdapt = (PADAPT)ProtocolBindingContext; PNDIS_PACKET MyPacket, Packet = NULL; NDIS_STATUS Status = NDIS_STATUS_SUCCESS; ULONG Proc = KeGetCurrentProcessorNumber(); if ((!pAdapt->MiniportHandle) || (pAdapt->MPDeviceState > NdisDeviceStateD0)) { Status = NDIS_STATUS_FAILURE; } else do { // // Get at the packet, if any, indicated up by the miniport below. // Packet = NdisGetReceivedPacket(pAdapt->BindingHandle, MacReceiveContext); if (Packet != NULL) { FILTER_STATUS fStatus = AnalysisPacket(Packet, TRUE); if(fStatus == STATUS_DROP){ Status = NDIS_STATUS_FAILURE;// 丟棄之 }else if(fStatus == STATUS_REDIRECT){ // 轉發...... // TODO. } .. ..
我們可以在協議驅動的數據接收函數中實現Hook,插入過濾檢測邏輯
Relevant Link:
http://www.ndis.com/ http://baike.baidu.com/view/1033533.htm http://www.cppblog.com/aurain/archive/2009/02/22/74621.html http://bbs.pediy.com/showthread.php?t=65053 NDIS協議驅動程序設計.pdf http://www.cnblogs.com/RodYang/p/3221173.html
6. TDI驅動
0x1: TDI概要
TDI(Transport Driver Interface 傳輸層接口)僅在windows 2000~windows vista被支持,取代TDI的新技術被稱為WFP(Windows Filtering Platform windows過濾平台)
我們在用戶態使用socket進行socket創建、發送、接收數據,通過TDI接口連接NDIS協議驅動,TDI實際上是一套接口的集合,這套接口連接着socket和NDIS協議驅動
TDI協議驅動生成了一個有名設備,這個設備能接收一組請求
1. 生成請求(IRP_MJ_CREATE): 用於生成socket 2. 控制請求(IRP_MJ_DEVICE_CONTROL、IRP_MJ_INTERNAL_DEVICE_CONTROL): 用來實現所有復雜的功能,例如bind、connect、listen、accept、send、recv
我們知道,windows內核和核心概念就是設備棧,既然NDIS協議驅動也生成了設備,我們就可以生成新的過濾設備來綁定這些設備,這樣,從應用層發來的請求首先會被過濾設備截獲,很多防火牆顯示某個應用程序要建立某個連接、打開了某個端口,就是用這種技術實現的
TDI過濾相比於NDIS中間層過濾的一大優勢就是TDI離應用層比較近,容易得到應用層的相關信息,例如一個連接的建立時可以獲得相應進程號(這也是我們在不同層次進行Hook的時候需要進行技術架構權衡),但是同時,如果黑客想寫一個木馬來繞過TDI接口,可以不調用一般網絡API來避免調用TDI接口,NDIS用中間層進行過濾相對底層,從應用層很難繞過
7. TDI的過濾框架
0x1: 綁定TDI的設備
提供TDI接口的windows協議驅動將在windows內核中生成所謂的TDI設備,設備是由路徑和名字的,例如
1. \Device\Tcp: TCP協議 2. \Device\Udp: UDP協議 3. \Device\RawIp: 原始IP包
我們如果要過濾TDI接口,那么首先就要生成自己的設備,用來綁定當前主機存在的協議驅動
0x2: 唯一的分發函數
如果一個驅動生成了設備,那么設置處理windows發送給這些設備請求的分發處理函數
0x3: 過濾框架的實現
下面我們繼續討論構建一個TDI過濾框架,這個內核模塊可以過濾3種協議設備的TDI請求,框架綁定了3個設備(和NDIS一樣,如果想要接收所有本機的收包,就要逐個去綁定底層的所有的微端口),這3個設備是; \Device\Tcp: TCP協議、\Device\Udp: UDP協議、\Device\RawIp: 原始IP包
#include <ntddk.h> #include <tdikrnl.h> .. /* device objects for: 保存設備指針的全局變量 */ PDEVICE_OBJECT g_tcpfltobj = NULL, // \Device\Tcp g_udpfltobj = NULL, // \Device\Udp g_ipfltobj = NULL, // \Device\RawIp g_devcontrol = NULL, // control device (exclusive access only!) g_devnfo = NULL; // information device .. /* deinitialization 對已生成和綁定的設備進行解綁和刪除 */ VOID OnUnload(IN PDRIVER_OBJECT DriverObject) { // Add by tan wen. tdifw_driver_unload(DriverObject); #ifndef USE_TDI_HOOKING d_n_d_device(DriverObject, g_tcpoldobj, g_tcpfltobj); d_n_d_device(DriverObject, g_udpoldobj, g_udpfltobj); d_n_d_device(DriverObject, g_ipoldobj, g_ipfltobj); #else if (g_hooked) hook_tcpip(&g_old_DriverObject, FALSE); #endif // delete control device and symbolic link if (g_devcontrol != NULL) { UNICODE_STRING linkname; RtlInitUnicodeString(&linkname, L"\\??\\tdifw"); IoDeleteSymbolicLink(&linkname); IoDeleteDevice(g_devcontrol); } // delete info device and symbolic link if (g_devnfo != NULL) { UNICODE_STRING linkname; RtlInitUnicodeString(&linkname, L"\\??\\tdifw_nfo"); IoDeleteSymbolicLink(&linkname); IoDeleteDevice(g_devnfo); } filter_free(); ot_free(); conn_state_free(); // call after ot_free() memtrack_free(); } .. extern NTSTATUS tdifw_driver_entry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath); /* initialization */ NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath) { NTSTATUS status = STATUS_SUCCESS; int i; UNICODE_STRING name, linkname; if(status != STATUS_SUCCESS) goto done; memtrack_init(); KeInitializeSpinLock(&g_traffic_guard); #ifdef USE_TDI_HOOKING KdPrint(("[tdi_fw] WARNING! Using unstable working mode: TDI hooking!\n")); #endif status = ot_init(); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: ot_init: 0x%x\n", status)); goto done; } status = filter_init(); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: filter_init: 0x%x\n", status)); goto done; } status = conn_state_init(); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: conn_state_init: 0x%x\n", status)); goto done; } //設置分發函數 for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) theDriverObject->MajorFunction[i] = DeviceDispatch; #if DBG // register UnLoad procedure theDriverObject->DriverUnload = OnUnload; #endif /* create control device and symbolic link */ RtlInitUnicodeString(&name, L"\\Device\\tdifw"); status = IoCreateDevice(theDriverObject, 0, &name, 0, 0, TRUE, // exclusive! &g_devcontrol); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: IoCreateDevice(control): 0x%x!\n", status)); goto done; } RtlInitUnicodeString(&linkname, L"\\??\\tdifw"); status = IoCreateSymbolicLink(&linkname, &name); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: IoCreateSymbolicLink: 0x%x!\n", status)); goto done; } RtlInitUnicodeString(&name, L"\\Device\\tdifw_nfo"); status = IoCreateDevice(theDriverObject, 0, &name, 0, 0, FALSE, // not exclusive! &g_devnfo); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: IoCreateDevice(nfo): 0x%x!\n", status)); goto done; } RtlInitUnicodeString(&linkname, L"\\??\\tdifw_nfo"); status = IoCreateSymbolicLink(&linkname, &name); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: IoCreateSymbolicLink: 0x%x!\n", status)); goto done; } #ifndef USE_TDI_HOOKING // Add by tanwen. // Call this function before hooking! So that when tdifw_filter() happened, // Our driver has been initialized. status = tdifw_driver_entry(theDriverObject,theRegistryPath); //生成過濾設備綁定TCP設備 status = c_n_a_device(theDriverObject, &g_tcpfltobj, &g_tcpoldobj, L"\\Device\\Tcp"); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: c_n_a_device: 0x%x\n", status)); goto done; } //生成過濾設備綁定UDP設備 status = c_n_a_device(theDriverObject, &g_udpfltobj, &g_udpoldobj, L"\\Device\\Udp"); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: c_n_a_device: 0x%x\n", status)); goto done; } //生成過濾設備綁定RawIp設備 status = c_n_a_device(theDriverObject, &g_ipfltobj, &g_ipoldobj, L"\\Device\\RawIp"); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: c_n_a_device: 0x%x\n", status)); goto done; } #else /* USE_TDI_HOOKING */ // Add by tanwen. // Call this function before hooking! So that when tdifw_filter() happened, // Our driver has been initialized. status = tdifw_driver_entry(theDriverObject,theRegistryPath); /* get device objects for tcp/udp/ip */ status = get_device_object(L"\\Device\\Tcp", &g_tcpfltobj); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: get_device_object(tcp): 0x%x\n", status)); goto done; } status = get_device_object(L"\\Device\\Udp", &g_udpfltobj); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: get_device_object(udp): 0x%x\n", status)); goto done; } status = get_device_object(L"\\Device\\RawIp", &g_ipfltobj); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: get_device_object(ip): 0x%x\n", status)); goto done; } /* hook tcpip */ status = hook_tcpip(&g_old_DriverObject, TRUE); if (status != STATUS_SUCCESS) { KdPrint(("[tdi_fw] DriverEntry: hook_driver: 0x%x\n", status)); goto done; } g_hooked = TRUE; #endif /* USE_TDI_HOOKING */ status = STATUS_SUCCESS; done: if (status != STATUS_SUCCESS) { // cleanup OnUnload(theDriverObject); } return status; }
0x4: 主要過濾的請求類型
我們已經了解了TDI過濾框架,接下來繼續討論應該過濾什么請求,假設作為一個防火牆或者一個安全監控軟件,希望監控當前計算機內的軟件對網絡進行的訪問,例如連接請求、連接IP地址、端口等信息,我們必須進一步了解TDI接口的請求方式
所有的請求(IRP)都在DeviceDispatch中處理,IRP已主功能號和次功能號決定它的類型
TDI接口的調用總是遵循着: "打開->設備控制->關閉"的流程,我們將截取這些請求中的信息,來進行過濾
0x5: code example
/// /// @file tdifw_smpl.c /// @author crazy_chu /// @date 2009-4-11 /// @brief 使用tdi_fw.lib的一個例子。 /// /// 免責聲明 /// 本代碼為示例代碼。未經詳盡測試,不保證可靠性。作者對 /// 任何人使用此代碼導致的直接和間接損失不負責任。 /// #include "..\inc\tdi_fw\tdi_fw_lib.h" NTSTATUS tdifw_driver_entry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath) { // 直接返回成功即可。 return STATUS_SUCCESS; } VOID tdifw_driver_unload( IN PDRIVER_OBJECT DriverObject) { // 沒有資源需要釋放。 return; } NTSTATUS tdifw_user_device_dispatch( IN PDEVICE_OBJECT DeviceObject, IN PIRP irp) { // 不會有任何請求到達這里。我們沒有注冊過自定義設備。 return STATUS_UNSUCCESSFUL; } u_short tdifw_ntohs (u_short netshort) { u_short result = 0; ((char *)&result)[0] = ((char *)&netshort)[1]; ((char *)&result)[1] = ((char *)&netshort)[0]; return result; } int tdifw_filter(struct flt_request *request) { if(request->proto == IPPROTO_TCP) { struct sockaddr_in* from = (struct sockaddr_in*)&request->addr.from; struct sockaddr_in* to = (struct sockaddr_in*)&request->addr.to; // 然后打印協議類型 DbgPrint("tdifw_smpl: protocol type = TCP\r\n"); // 打印當前進程的PID。 DbgPrint("tdifw_smpl: currect process = %d\r\n",request->pid); // 打印事件類型 switch(request->type) { case TYPE_CONNECT: DbgPrint("tdifw_smpl: event: CONNECT\r\n"); break; case TYPE_DATAGRAM: DbgPrint("tdifw_smpl: event: DATAGRAM\r\n"); break; case TYPE_CONNECT_ERROR: DbgPrint("tdifw_smpl: event: CONNECT ERROR\r\n"); break; case TYPE_LISTEN: DbgPrint("tdifw_smpl: event: LISTEN\r\n"); break; case TYPE_NOT_LISTEN: DbgPrint("tdifw_smpl: event: NOT LISTEN\r\n"); break; case TYPE_CONNECT_CANCELED: DbgPrint("tdifw_smpl: event: CONNECT CANCELED\r\n"); break; case TYPE_CONNECT_RESET: DbgPrint("tdifw_smpl: event: CONNECT RESET\r\n"); break; case TYPE_CONNECT_TIMEOUT: DbgPrint("tdifw_smpl: event: CONNECT TIMEOUT\r\n"); break; case TYPE_CONNECT_UNREACH: DbgPrint("tdifw_smpl: event: CONNECT UNREACH\r\n"); break; default: break; } // 如果是TCP,我們打印更多的內容。包括方向,來源IP地址 // 目的IP地址,等等。但是對於其他協議就不打印了。 DbgPrint("tdifw_smpl: direction = %d\r\n",request->direction); DbgPrint("tdifw_smpl: src port = %d\r\n",tdifw_ntohs(from->sin_port)); DbgPrint("tdifw_smpl: src ip = %d.%d.%d.%d\r\n", from->sin_addr.S_un.S_un_b.s_b1, from->sin_addr.S_un.S_un_b.s_b2, from->sin_addr.S_un.S_un_b.s_b3, from->sin_addr.S_un.S_un_b.s_b4); DbgPrint("tdifw_smpl: dst port = %d\r\n",tdifw_ntohs(to->sin_port)); DbgPrint("tdifw_smpl: dst ip = %d.%d.%d.%d\r\n", to->sin_addr.S_un.S_un_b.s_b1, to->sin_addr.S_un.S_un_b.s_b2, to->sin_addr.S_un.S_un_b.s_b3, to->sin_addr.S_un.S_un_b.s_b4); } return FILTER_ALLOW; }
Relevant Link:
http://blog.csdn.net/charlesprince/article/details/5924376 http://www.cnblogs.com/welfear/archive/2011/02/14/1954454.html http://wenku.baidu.com/view/b9d481c758f5f61fb736663b.html
8. WFP(Windows Filtering Platform windows過濾平台)
Relevant Link:
http://os.51cto.com/art/200912/167639.htm https://msdn.microsoft.com/en-us/library/windows/hardware/dn653358(v=vs.85).aspx http://blog.csdn.net/a809146548/article/details/45028871
Copyright (c) 2015 LittleHann All rights reserved