Windows驅動開發
一、前言
依據《Windows內核安全與驅動開發》及MSDN等網絡質料進行學習開發。
二、初步環境
1、下載安裝WDK7.1.0(WinDDK\7600.16385.1)
地址:https://msdn.microsoft.com/en-us/windows/hardware/hh852365
2、下載InstDrv軟件(用於安裝、啟動、停止、卸載驅動)
界面如下:
注:srvinstw.exe 也可以安裝、卸載sys文件,但需要手動開啟、關閉,即在cmd命令窗口下執行net start 驅動名、net stop 驅動名來啟動、停止服務。
3、下載DbgWiew.exe 軟件(查看內核模塊的輸出信息)
地址:https://technet.microsoft.com/en-us/sysinternals/bb896647.aspx
注:詳見DebugView的使用文檔。
4、下載64Signer-V1.2(Win7 64位 私有測試數字簽名軟件)
地址:http://www.yiiyee.cn/Blog/64signer-download/
5、其他軟件:虛擬光驅DAEMON Tools Lite、EasyBCD(系統引導文件修復工具)、PartitionManager(將C盤設置為活動分區)等
三、初步示例
1、編寫一個簡單的驅動樣例(加載、卸載及信息輸出等)
注:詳見TestDriver.c
內核模塊的入口是DriverEnter函數,在加載時被系統進程System調用。若想能卸載驅動,必須設置驅動對象的DriverUnload函數指針。
Windows內核編程不是應用程序編程,它有自己的一套東西,所有的Win32函數都不能使用,部分C Runtime函數也不能使用,需要調用內核API函數,因此在使用函數前,現在幫助里查詢該函數。
幫助文檔在“開始”-->“所有程序”-->“Windows Driver Kits”下。如圖:
2、使用WDK的build工具,編譯時需要makefile.def和SOURCES文件。
1)makfile.def文件固定,拷貝一份即可,不需要任何改動。
2)SOURCES文件內容關聯編譯那些文件及編譯出的.sys文件的名字等。
WDK的build工具在“開始”-->“所有程序”-->“Windows Driver Kits”-->“WDK 7600.16385.1”下,選擇要根據本地主機的版本,如:本機是64位的Win7旗艦版,則build工具要選x64 Checked build Environment(對應debug)或x64 Free build Environment(對應Release)。
3、InstDrv軟件直接安裝生成的.sys文件,安裝、卸載成功,啟動失敗,並提示“Windows需要已數字簽名的驅動程序”,DebugView軟件中無自定義信息輸出。圖如下:
解決方法:
1、從http://www.yiiyee.cn/Blog/64signer-download/下載Win7 64位私有測試數字簽名軟件(64Signer-V1.2),以管理員身份運行,瀏覽驅動文件,進行測試數字簽名。圖如下:
2、重新安裝簽名后的.sys驅動文件,並啟動,依然報並提示“Windows需要已數字簽名的驅動程序”。 圖如下:
3、以管理員身份運行cmd.exe,,並執行bcdedit /set testsigning true 命令,若提示“操作成功完成”,則表示命令執行成功;否則,若失敗,請將bcdedit命令所在boot文件夾的所在盤(一般C盤)設置為活動狀態;若依然失敗,請修復boot文件夾的所在盤下的Boot(系統引導文件所在),或干脆恢復|重裝系統。
4、將Windows 7 設置為測試版本成功后,須重啟計算機,重啟后,以管理員身份運行InstDrv.exe軟件,對已數字簽名的.sys驅動文件進行安裝、啟動、關閉、卸載均成功。圖如下:
注:若驅動中包含中斷或錯誤,則計算機藍屏,須重啟恢復。恢復后的計算機系統內新安裝的軟件會遭到卸載或損壞,須重新安裝。
以上是一個簡單的驅動程序開發步驟。若要進行較復雜的開發,一般需要配置VS開發環境、安裝虛擬機、WinDgb.exe等軟件進行調試等。
四、完善環境
1、下載並安裝VS2010(64位)
地址:https://www.visualstudio.com/downloads/download-visual-studio-vs
2、下載WinDbg.exe軟件
地址:http://www.microsoft.com/whdc/devtools/debugging/default.mspx
3、虛擬機(WMware10.0供雙機調試)
地址:http://www.microsoft.com/zh-CN/download/confirmation.aspx?id=8002
五、配置VS2010
1、依據本機版本配置管理器。win7旗艦64位系統配置如下:
點擊VS內工具欄中的解決方案配置的下按鈕(即debug所處的下拉框按鈕),點擊配置管理器,點擊活動方案配置,點擊新建,輸入DriverDebug64、默認空,在項目上下文中的平台項新建X64並在活動解決方案平台:選X64。
注:32位系統選Win32
2、在VS中VisualC++下新建“空項目”,將TestDrive.c文件添加到工程中(或直接新建.c文件),.c文件添加后,在屬性中就顯示出C/C++配置項。
3、點擊VS中“視圖”菜單->“其他窗口”->“屬性管理器”,右擊屬性管理器中的DriverDebug64,選擇屬性,在彈出的窗體中進行必要的設置。(或直接點擊項目的屬性)
注: 1、WDK7.1.0默認安裝在C:\WinDDK\7600.16385.1文件夾下
2、此處的設置都是必須的, 對復雜些的驅動開發也許還要額外另加設置
具體如下:
1)常規
目標文件擴展名:.sys
2)VC++目錄
可執行文件目錄(編譯器路徑):
C:\WinDDK\7600.16385.1\bin\amd64
包含目錄:
C:\WinDDK\7600.16385.1\inc\api
C:\WinDDK\7600.16385.1\inc\crt
C:\WinDDK\7600.16385.1\inc\ddk
庫目錄:
C:\WinDDK\7600.16385.1\lib\win7\amd64
3)C/C++
預處理器->預處理器定義:
_AMD64_=1,AMD64=1,STD_CALL=1,WINVER=0x0501,_DEBUG =1
高級->調用約定:
__stdcall (/Gz)
代碼生成-->運行庫:
選擇多線程調試/MTd 或者 多線程調試DLL/MDd
(主要解決_DEBUG未預定義的問題)
常規-->調試信息格式:用於“編輯並繼續”的程序數據庫 (/ZI)
優化:已禁用 (/Od)
4)連接器
輸入->附加依賴項: ntoskrnl.lib;Hal.lib;wdm.lib;wdmsec.lib;wmilib.lib;ndis.lib;MSVCRT.LIB;LIBCMT.LIB;%(AdditionalDependencies)
輸入->忽略所有默認庫:是 (/NODEFAULTLIB)
清單文件->啟用用戶賬戶控制(UAC):否 (/MANIFESTUAC:NO)
系統->子系統:控制台 (/SUBSYSTEM:CONSOLE)
系統->驅動程序:驅動程序 (/Driver)
系統->堆棧保留大小:4194304(可修改)
系統->堆棧提交大小:4096 (可修改)
高級->入口點:DriverEntry
高級->基址:0x10000
高級->隨機基址:(清空)
高級->數據執行保護(dep):(清空)
調試->生成調試信息:是 (/DEBUG)
注:參考C:\WinDDK\7600.16385.1文件夾的ia64、X86等路徑,進行修改可以配置為ia64、32位系統。編譯若通過,則配置成功,並生產.sys等文件。
六、配置WinDbg
1、設置系統字符表路徑:WinDbg->File->Symbol File Path界面中輸入
SRV*C:\WINDOWS\Symbols*http://msdl.microsoft.com/download/symbols; 並選擇Reload,WinDbg會自動下載字符表,關鍵是勾選reload。
2)設置自己生成的.sys對應的字符(Symbol)路徑:
C:\Users\shenc\Desktop\TestDriver\objchk_win7_amd64\amd64
3)設置自己生成.sys的原代碼路徑:
C:\Users\shenc\Desktop\TestDriver
注:
1)在調試源碼時,若發生竄碼(調試驅動與對應的源碼不一致),須將WinDbg軟件File菜單下存儲的舊源碼路徑刪除。
2)調試時WinDbg默認不輸出Dbgprintf信息,須執行ednt!Kd_DEFAULT_Mask 0x8 命令。
3)驅動運行時,按下Ctrol+Break組合鍵,進入中斷狀態,點擊工具按鈕(快捷鍵:F8、F10等)可進行詳細調試。
4)中斷調試時,可查看局部變量等信息
七、虛擬機
1、安裝虛擬機
若主板默認沒有開啟虛擬化技術,則一般方法是開機或重啟時按F12鍵進入BIOS菜單,將虛擬化(Virtualization)設置為Enable。
2、配置虛擬機
1)開始-->WMware Work Stations-->雙擊“我的電腦”下的一個虛擬機(Windows 7 x64)-->編輯虛擬機設置-->移除打印機-->添加竄行端口
2)選中剛添加的竄行端口,在右側的對話框中,設置如下:
勾選 啟動時連接
選擇 使用命名的管道(N)
其下的兩個下拉框分別選擇:該端是服務器、另一端是應用程序
八、安裝並配置虛擬機上的Win7 64位系統
1、將Win7設置為可調試狀態
1)以管理員身份打開cmd命令窗口:Win + R 打開運行輸入框,輸入cmd;或鼠標 點擊系統開始圖標,在輸入框中輸入cmd,右擊上方搜索顯示出的cmd.exe,以管理員身份運 行。
2)依次輸入:
bcdedit /?
Bcdedit /enum OSLOADER
Bcdedit /copy {current} /d “Windows 7 Copy”
Bcdedit /debug on
Bcdedit /bootdebug on
Bcdedit /dbgsettings
Bcdedit /timeout 7
2、將Win7設置為測試版
管理員身份運行cmd命令,bcdedit /set testsigning true
3、測試證書數字簽名
以管理員身份運行64Signer V1.2.exe,點擊瀏覽找到並雙機.sys文件,點擊簽名。
4、安裝.sys文件
以管理員身份運行運行InstDrv.exe,選擇.sys文件,進行安裝、啟動等操作
5、查看內核輸出信息
以管理員身份運行Dbgview.exe,點擊Capture菜單,勾選上Captrue kernel項。
注:測試含有斷點的內核,否則卡機,無法進行任何操作,因此不在本地主機測試,而采用雙機調試(即新建個虛擬機,本地機與虛擬機通過管道進行通信)。
九、雙機調試
1、源代碼中添加中斷語句
#if _DEBUG //DBG
__debugbreak(); //64位
#endif
注: 1)匯編_asm int 3 中斷,在64位Win7上報錯。
2)如果未能進入斷點進行調試,請檢查sys文件是否在客戶機(虛擬Win7系統)安裝成功,系統字符集(Symbols)、本人字符Symbols是否下載或設置正確,管道端口是否正確等。
2、虛擬機共享主機文件夾
方法:
1)啟動VMware,選擇虛擬機,點擊編輯虛擬機設置;
2) 點擊選項,點擊共享文件夾,右側點擊總是啟用,然后點擊添加按鈕;
3) 輸入win7主機下需要共享的文件夾路徑,制定共享名,點擊下一步;
4) 點擊確定,點擊虛擬機內的計算機,在網絡下就可以訪問共享的文件夾嘍。
/*
3、Windows 7系統中的驅動簽名強制要求,關閉強制驅動簽名的命令為:
bcdedit /set loadoptions DISABLE_INTEGRITY_CHECKS
使用管理員的身份打開CMD命令行,然后輸入上面的命令,完成之后重新啟動計算機, 就可以在64位win7系統上使用未有數字簽名的驅動程序。(測試時不簽名啟動失敗!)
*/
十、NDIS中間層驅動開發
1、前言
TDI(傳輸層驅動接口)是傳輸層的過濾技術,在Windows Vista之前,常用來開發網絡數據過濾,Windows Vista之后的操作系統中不再支持,取而代之的是WFP(Widows過濾平台)技術,一套系統API和服務,其簡單穩定,但微軟沒有介紹XP等Windows Vista之前的系統如何支持WFP。NDIS Filter(網絡驅動接口規范)過濾框架,即支持XP等Windows Vista之前的系統,又支持Win7等Windows Vista之前的系統;並且WDK7.1.0安裝環境下的passthru工程,就是NDIS Filter開發的示例,在其基礎上可以方便開發。passthru工程路徑如下圖:
2、Windows 網絡架構
1)最上層是網絡應用層。Socket、WinInet編寫的程序。
2)第二層是網絡API層,也是系統最頂層,為應用程序提供編程接口,且接口協議無關性。接口的定義必須在用戶層,內部邏輯實現常在內核層。如:Windows套接字、WinInet庫 API等
3)第三層是網絡API的內核實現(即第二層的內核層)。內核實現層總是以內核模式設備驅動程序的形式體現,並且他有一個統一的責任:將上層網絡請求格式化為TDI格式,並將這個格式化后的IRP發送到下層NDIS協議驅動。
4)再下一層是NDIS協議驅動,又叫TDI傳輸器。TCP/IP等都是NDIS協議驅動。
5)最下層是NDIS小端口驅動程序,直接驅動物理網卡。
圖如下:
網絡驅動模型的每一層都定義了對外的公開的公共接口,與其相連的上下層驅動模塊,不需要關心其內部實現就可以很好的支持擴展性、完成工作。
3、NDIS中間層
為了方便對網絡操作進行過濾,依據網絡驅動模型內NDIS協議驅動、NDIS小端口驅動的公共接口進行擴展出來的一層。對上面的NDIS協議驅動,扮演NDIS小端口特征的角色;對下面的NDIS小端口驅動,扮演NDIS協議特征的角色。
圖如下:
注:NDIS中間層的數量理論上不限數量
協議驅動綁定了所有小端口驅動,於是能截獲所有接受到的包;而中間層驅動不僅綁定了所有小端口驅動,而且還被“所有驅動協議”綁定,因此理論上能截獲所有發送和接受到的包。
4、NDIS中間層驅動開發示例Passthru工程
1)NDIS驅動的入口函數DriverEntry:做了初始化包裝句柄、注冊NDIS小端口特征集、注冊NDIS協議特征集、關聯NDIS兩個接口等必做工作,也可以在其中創建設備對象、初始化分發函數表。
初始化包裝句柄:NdisMInitializeWrapper函數,獲得NDIS包裝句柄;
注冊小端口特征:先填寫小端口特征,再使用NdisIMRegisterLayeredMiniport函數進行注冊,輸入參數是小端口特征、NDIS包裝句柄等,獲得關聯小端口的NDIS_HANDLE類型句柄(DriverHandle)。
注冊小端口卸載關聯程序:NdisMRegisterUnloadHandler函數,參數是NDIS包裝句柄、卸載出來程序函數句柄。
注銷小端口:NdisIMDeregisterLayeredMiniport函數,參數是注冊小端口特征時獲得的DriverHandle句柄。
注冊協議特征:NdisRegisterProtocol函數,輸入參數是協議特征、NDIS包裝句柄等,獲得關聯協議的NDIS_HANDLE類型句柄(ProtHandle)。
關聯兩個接口:NdisIMAssociateMiniport函數,輸入參數是DriverHandle、ProtHandle兩句柄,這樣小端口層和協議層,在一個不透明體中進行了關聯。
注:中間層驅動本身就是集小端口驅動、協議驅動於一體的一個混合體驅動。
2)動態綁定NIC設備
綁定過程是由PNP管理器發起,當PNP管理器發現系統中有可用的NIC設備時,它最終會找到所有注冊過的中間層驅動。依次調用它們的AddDevice函數。
注:驅動的AddDevice函數都是被NDIS庫中函數托管的。
綁定的過程中會調用PtBindAdapter函數,在其函數內實現了協議驅動對小端口的綁定。PtBindAdapter函數調用NdisAllocatepacketPoolEx函數分配用於發送和接受數據包的緩沖池;調用NDISOpenAdapter函數綁定下層的NIC,本質是在NDIS的內核對象中,建立起中間層驅動和下層被綁定驅動之間的注冊函數的調用關系;調用NdisIMInitializeDeviceInstanceEx函數,在這個函數內部,調用中間層驅動程序的MpInitialize函數來初始化驅動的虛擬NIC。
3)小端口的初始化(MpInitialize)
通過傳入的DriverHandle句柄參數,驅動可以很方便地找到兩個特征結構中的函數接口。
調用NdisMSetAttributesEx函數設置適配器上下文,其第三個參數必須設置屬性值:
NDIS_ATTRIBUTE_IGNORE_PACKET_TIMEOUT :不對未決包進行超時處理。
NDIS_ATTRIBUTE_IGNORE_REQUEST_TIMEOUT:不對驅動程序維持的隊 列中的查詢和設置命令進行超時處理
NDIS_ATTRIBUTE_INTERMEDIATE_DRIVER:告訴NDIS這是一個中間層驅動
調用PtRegisterDevice函數,生成一個控制設備對象,並設置其派遣函數。
4)中間層發送數據包
中間層驅動要發送網絡數據包,最終都必須調用NDISSend/NDISSendPacket/NDISCoSendPackets這個系列的函數。以NDISSend為例,NdisSend在內部通過協議驅動的綁定句柄,找到所綁定的中間層驅動,並找到中間層驅動的MpSend/MpSendPackets函數調用。
因此可以在MpSend/MpSendPackets中對發送的數據包直接進行處理。
(包描述符進行重利用或重申請)
MpSend返回值是NDIS_STATUS_PENDING,則表示發送數據包的異步完成。那么久不能再對包描述符做任何操作了,因為已經對它失去了控制權,響應的操作應該保留到完成函數PtSendComplete中進行。
5)中間層驅動接受數據包
底層面向無連接的小端口驅動可通過下面兩種方式指示數據包接收。
方式一:小端口驅動調用過濾無關的NdisMindicateReceivePacket函數,向上層驅動傳遞數據包描述符指針。當上層驅動處理完畢后,將向NIC驅動程序返回那些包描述符及其所指向的資源。此方式下,如果中間層驅動提供了PtReceivePacket處理函數,則PtReceivePacket函數被調用;否則PtReceive函數被調用。
方式二:小端口驅動調用過濾相關的NdisMXxxindicateReceivePacket函數,傳遞包頭及數據緩沖區指針和緩沖區大小。此方式下,PtReceive函數被調用。
因此可以在PtReceive/PtReceivePacket中對接受的數據包直接進行處理。
上層驅動收到網絡包接收通知后,會在合適的時候調用NdisTransferData函數來要求底層驅動將完整的包數據發送給它。我們會在MpTransferData函數中得到上層的這個請求;但因為我們沒有完整的報數據,所以應該在這個函數中繼續把請求往底層傳遞。底層驅動如果立刻返回包數據。那么我們在MpTransferData中即能立刻截獲到;否則在MpTransferData的異步完成函數PtTransferDataComplete中才能截獲到完整的包內容。
因此可以在MpTransferData、PtTransferDataComplete中對數據包進行處理。
6)中間層驅動程序查詢和設置
查詢和設置,是小端口特征回調中兩個重要的接口:一個用來處理OID查詢請求,一個用來處理OID設置請求。Passthru中分別對應的是MPQueryInformation和MPSetInformation.
綜上,可以在MpSend/MpSendPackets,PtReceive/PtReceivePacket,MpTransferData、PtTransferDataComplete中對數據包進行處理。
5、NDIS包描述符
調用NdisQueryPacket函數,可以找到第一個Ndis_Buffer,然后通過NdisGetNextBuffer函數,來獲得后續的NDIS_BUFFER。
調用NdisQueryBufferSafe函數,可以取得Ndis_Buffer中存儲緩沖區的虛擬地址。
調用NdisMoveMemory函數,可以將各NDIS_BUFFER內容拷貝到指定的存儲區。
十一、安裝驅動
1、將netsf.inf、netsf_m.inf、Passthru.sys放在同一目錄下。
2、安裝
(1) 打開“網絡和共享中心”。
(2) 右擊“本地連接”或“無線網絡”,選擇“屬性”。
(3) 在彈出的“本地連接 屬性”對話框中選中“常規”屬性頁,點擊“安裝”按鈕。
(4) 在彈出的“選擇網絡組件類型”對話框中選中“服務”,然后點擊“添加”按鈕。
(5) 在彈出的“選擇網絡服務”對話框中點擊“從磁盤安裝”按鈕。
(6) 在彈出的“從磁盤安裝”對話框中點擊“瀏覽...”按鈕。“netsf.inf”文件,點擊“打開”按鈕,確定。
(7) 在彈出的“選擇網絡服務”對話框中選中“Passthru”,點擊“確定”按鈕。
(8) 在安裝過程中對彈出的數字簽名對話框都要點擊“確認”按鈕。
(9) 安裝完成后,“Passthru”就出現在了組件列表中。
十二、 其他
first.c

/// /// @file first.c /// @author crazy_chu /// @date2008-11-1 /// #include <ntddk.h> // 提供一個Unload函數只是為了 VOID DriverUnload(PDRIVER_OBJECT driver) { // 但是實際上我們什么都不做,只打印一句話: DbgPrint("first: Our driver is unloading…\r\n"); } // DriverEntry,入口函數。相當於main。 NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path) { #if DBG // _asm int 3 #endif // 這是我們的內核模塊的入口,可以在這里寫入我們想寫的東西。 // 我在這里打印一句話。因為”Hello,world” 常常被高手恥笑,所以 // 我們打印一點別的。 DbgPrint("first: Hello, my salary!"); // 設置一個卸載函數便於這個函數能退出。 driver->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 TARGETPATH=obj