1、背景介紹安全
1.1WindowsNT操做系統的組成
1.1.1用戶模式(UserMode)與內核模式(KernelMode)
從Intel80386開始,出於安全性和穩定性的考慮,該系列的CPU能夠運行於ring0~ring3從高到低四
個不一樣的權限級,對數據也提供相應的四個保護級別。運行於較低級別的代碼不能隨意調用高級別的代
碼和訪問較高級別的數據,並且也只有ring0層的代碼能夠直接進行對物理硬件的訪問。因爲WindowsNT
是一個支持多平臺的操做系統,爲了與其餘平臺兼容,它只利用了CPU的兩個運行級別。一個被稱爲內核
模式,對應80x86的ring0層,操做系統的核心部分,包括設備驅動程序都運行在該模式;另外一個被稱爲
用戶模式,對應80x86的ring3層,操做系統的用戶接口部分以及全部的用戶應用程序都運行在該級別。網絡
1.1.2WindowsNT操做系統的結構
圖1簡要地描述了WindowsNT的系統組成。多線程
圖一
從圖中能夠看到,在物理硬件(Hardware)與系統核心(Kernel)之間有一個硬件抽象層(Hardwa
reAbstractionLayer),它屏蔽了不一樣平臺硬件的差別,向操做系統的上層提供了一套統一的接口。從
圖中咱們還能夠看到,設備驅動程序(DeviceDriver)是被I/O管理器(I/OManager)包圍起來的,即驅動
程序與操做系統上層的通訊所有都要經過I/O管理器。這給驅動程序的編寫帶來了很大的便利,由於不少
諸如接收用戶的請求、與用戶程序交換數據、內存映射、掛接中斷、同步等等麻煩的工做都由I/O管理器
代勞了。函數
1.1.3WindowsNT設備驅動程序的分類工具
根據是否直接操做硬件,能夠把驅動程序分紅兩大類:內核模式的驅動程序和專用驅動程序。性能
內核模式的驅動程序根據硬件的通訊協議,直接對硬件進行端口訪問、中斷響應、DMA傳輸。它包括
:串、並行口,鍵盤,文件系統,SCSI,網絡等驅動程序;專用驅動程序包括視頻,打印,多媒體,虛
擬DOS等驅動程序,他們在實現上與前者有很大區別。我在實習期間所作的工做以及本文如下的討論都局
限於內核模式的驅動程序。測試
p>1.2WindowsNT下內核模式設備驅動程序的結構和運行ui
通常來講,設備驅動程序的任務主要有二:第一,接受來自用戶程序的讀寫請求,把用戶的數據傳
送給設備,或把從設備接收到的數據傳送給用戶;第二,輪詢設備或處理來自設備的中斷請求,完成數
據傳輸。spa
1.2.1驅動程序與用戶程序的通訊操作系統
I/O管理器把每個設備對上層都抽象成了文件,因此在Win32用戶程序中只要經過如下幾條簡單的
文件操做API函數就能夠實現與驅動程序中的某個設備通訊(請注意,一個驅動程序能夠驅動多個設備)
:
函數名功能
CreateFile打開一個設備,準備進行數據傳輸。返回一個與設備相關的句柄。
CloseHandle關閉一個由CreateFile打開的設備。
ReadFile從設備讀取數據。
WriteFile向設備寫數據。
"DeviceIoControl"對設備進行一些自定義的操做,好比更改設置等。
表一
1.2.2DriverEntry過程
這是每個設備驅動程序的入口,每次該程序啓動時被系統自動調用。大部分的設備初始化的工做
都在這個過程當中完成。包括設置響應各類用戶請求的過程的入口,使I/O管理器能知道當用戶的打開、關
閉、讀寫等請求到來時各應調用那些過程來處理。驅動程序中只有本過程的名字"DriverEntry"是固定的
,如下列出的全部過程都要由本過程向系統註冊。
若是該驅動程序不響應任何請求的話,只要一個DriverEntry過程就能夠構成一個能運行的驅動程序
。
1.2.3Unload和ShutDown過程
Unload過程負責在驅動程序被中止前作一些必要的處理。好比釋放資源,記錄最終狀態等。ShutDo
wn過程在系統即將關閉時被調用,與前者的區別在於不用釋聽任何資源。
1.2.4DispatchOpen和DispatchClose過程
這兩個過程在用戶調用CreateFile和CloseHandle時被調用,爲即將到來的讀寫操做作備,或作一些
讀寫完成後的必要處理。
1.2.5DispatchRead,DispatchWrite與StartIo過程
這前兩個過程在用戶調用ReadFile和WriteFile時被調用。它們先作一些檢驗用戶請求合法性的工做
,而後啓動一個被稱爲StartIo的過程開始實際的與硬件間的數據傳輸。I/O管理器還經過IRP爲它們提供
了一個指向用戶緩衝區的指針,用於與用戶程序交換數? 詳情請見1.3.2
1.2.6接受自定義的其餘請求
這兩個過程在用戶調用DeviceIoControl時被調用。它經過IRP得到用戶的請求號,以及一個指向用
戶緩衝區的指針,能夠與用戶程序進行通訊。
1.2.7中斷處理過程(ISR)
這些過程在中斷髮生時被系統調用。
1.2.8推遲過程(DeferredProcedure)
這些過程用來在較低的運行級別完成較高運行級別過程(如中斷處理過程)的一些任務。詳情請見
1.3.3
p>1.3實現細節
1.3.1內核代碼運行級別
WindowsNT爲它的內核模式的代碼分配了不一樣的級別。在同一個CPU上,級別低的過程能夠被任何級
別更大的過程當中斷。級別由低到高排列以下:
級別名稱運行於該級別的過程
PASSIVE_LEVELDriverEntry,Unload,ShutDown,DispatchXxx。
APC_LEVEL在某些特殊狀況下,大存儲量設備的驅動程序運行於該級別。
DISPATCH_LEVELStartIo,AdapterControl,ControllerControl,IoTimer,Dpc。
DIRQLs各類中斷處理程序。 表二
1.3.2幾個對象
i)I/O請求包(IRP)
I/O管理器每收到一個來自用戶的請求就建立一個該結構,並將其做爲參數傳給驅動程序的Dispatc
hXxx、StartIo過程。該結構中存放有請求的類型,用戶緩衝區的首地 址,用戶請求數據的長度等信息
。驅動程序處理完這個請求後,也在該結構中添入處理結果的有關信息,調用IoCompleteRequest將其返
回給I/O管理器,用戶程序的請求隨即 返回。
ii)DPC
當驅動程序中要用到Dpc過程時,須要建立該對象。具體做用請見1.3.3。
iii)驅動程序對象(DriverObject)
該對象在驅動程序被啓動時由I/O管理器建立,保存有該程序處理各類請求的過程入口、該程序所驅
動的所有設備對象的鏈表等。
iv)設備對象(DeviceObject)
每發現一個能夠驅動的設備,驅動程序調用IoCreateDevice建立一個該對象。該對象有一個指針Device
Extension指向一塊由驅動程序定義的結構,其中保存有關此設備的如端口號,中斷向量等所有信息。
v)中斷對象(Interrupt)
該對象在驅動程序調用IoConnectInterrupt時建立,存有中斷及處理的過程的信息。當一箇中斷髮
生時,I/O管理器用它尋找對應的處理過程。
1.3.3推遲過程調用(DeferredProcedureCall)
因爲中斷處理過程運行於較高的DIRQL級,它們能屏蔽許多級別小於或等於它們的過程的執行,若是
它們佔用CPU時間過長,很容易使系統性能降低。所以中斷處理過程應將一 些不是很緊急的任務放在被
稱爲Dpc的過程當中,在完成數據傳輸等緊急任務? 一個DPC對象放在系統DPC隊列的末尾,而後退出,儘可能
早地讓出CPU。系統將在完成全部DIRQL級 的任務後處理DPC隊列,在DISPATCH_LEVEL執行每個DPC對象
指定的Dpc過程,完成中處理斷過程未盡的任務。
1.3.4查找硬件信息
i)系統自動搜索到的設備
在系統啓動時,組件NTDETECT會自動地搜索計算機上已有的硬件,包括串、並行口,鍵盤,鼠標,
以及大多數PCI和EISA設備。並將它們的信息,包括總線類型,總線號,用到的端口號及數量、中斷向量
號、DMA通道號、佔用內存等按必定格式添入註冊表的\HKEY_LOCAL_MACHINE\Hardware\description\Sy
stem\鍵之下。在驅動程序中能夠用IoQueryDeviceDescription以及一個回調函數ConfigCallback來查找
符合要求的設備,並獲取它的配置信息。
ii)系統不能自動搜索到的設備
一些ISA的設備沒法被系統自動檢測到,只有在安裝驅動程序時在註冊表中人工添入它們的配置信息
。驅動程序啓動時能夠用RtlQueryRegistryValues等函數查詢註冊表得到 這些信息。
1.3.5有關內存
80386以上的32位CPU能夠管理多達4GB的物理內存。它將這些內存分爲許多大小爲64KB的段和4KB的
頁來管理,並經過段描述符和頁表將物理地址映射成系統地址供程序訪問 。因爲WindowsNT使用虛擬內
存技術,可能某些系統地址對應的物理地址處於硬盤上,每當程序讀寫這些地址時會產生一個缺頁異常
,使CPU將這些內存調入物理存儲器中。這 部份內存被稱爲分頁內存(Paged)。與之對應的是非分頁內
存(Nonpaged),這部份內存保證是物理駐留的。驅動程序中運行級別大於等於DISPATCH_LEVEL的過程不
能訪問分 頁內存,不然引發系統崩潰。
1.3.6緩衝的I/O與直接I/O
在驅動程序建立了一個設備後,能夠經過設置DeviceObject的Flags域的值來將設備設置成緩衝的I
/O或直接的I/O。
若是該值被設爲DO_BUFFERED_IO,每當I/O管理器收到一個讀寫請求,就在內存的非分 頁區分配一
塊與用戶區大小相同的區域,並將首指針存放於Irp對象的AssociatedIrp.S ystemBuffer中,驅動程序
就經過這個緩衝區與用戶交換數據。每當一個讀請求被完成時 I/O管理器自動將該緩衝區中的內容複製
到用戶區,並釋放該區域。
若是用戶區大於一頁(在80x86上爲4096字節),通常將該值設爲DO_DIRECT_IO。這時每當I/O管理
器收到一個讀寫請求,先鎖定用戶區的物理內存,而後爲其建立一個內 存描述表(MDL),並將該表的首
指針存放於Irp對象的MdlAddress中,驅動程序能夠經過調用MmGetSystemAddressForMdl得到用戶區在系
統空間中的地址。每當一個讀請求被完 成時I/O管理器自動將該區域解鎖。
1.3.7定時
爲了防止當設備出現某種故障時致使讀寫請求超時,或須要定時輪詢某些設備的狀態 ,驅動程序需
要設置一些定時器。驅動程序中有兩種方法能夠設置定時器。一種是調用IoInitializeTimer將一個定時
器過程IoTimer與一個設備對象聯繫起來。在調用IoStar tTimer後,系統將每一秒鐘調用一次IoTimer,
直至驅動程序調用IoStopTimer。若是須要設置更小間隔的定時器,須要用到被稱爲CustomTimerDpc的一
種推遲過程調用機制。 它能夠設置系統每隔必定時間將一個設置好的DPC對象放到DPC隊列的末尾,執行
一個指定的定時器Dpc過程。這個時間間隔能夠精確到100ns。
1.3.8同步
若是驅動程序有可能在某時刻有多個部分在同時運行,好比有中斷處理過程,或存在多個設備等,
對公共數據或代碼的訪問就須要同步。方法有:
i)自旋鎖(SpinLock)
驅動程序能夠在初始化時調用KeInitializeSpinLock建立該對象。在任何代碼段訪問被保護的數據
以前,先調用KeAcquireSpinLock試圖得到該對象的全部權,若是成功,該段代碼被系統提高至DISPATCH
_LEVEL,進行數據訪問。訪問完畢後須調用KeRelease SpinLock釋放全部權,運行級別也被恢復。此方
法只適用於同步運行級別小於等於DISP ATCH_LEVEL的代碼,主要用於多CPU的情形。此外,還有一種中
斷自旋鎖用於與中斷處理過程同步,能夠將較低級別的代碼提高到須要與之同步的中斷DIRQL。
ii)控制器(Controller)
該對象主要用於同步一個驅動程序中的多個設備,保證它們能順序地訪問特定的代碼或數據。該對
象在驅動程序初始化調用IoCreateController被建立。設備在StartIo過程當中調用IoAllocateControlle
r請求得到Controller對象的獨佔權。使用完後調用IoFreeController釋放。驅動程序中止時調用IoDel
eteController從內存刪除該對象。該對象有一個指針ControllerExtension指向一塊由驅動程序定義的
結構,其中保存有此驅程序的公共數據。
iii)適配器(Adapter)
該對象用於同步多個設備(不必定在一個驅動程序中)對DMA通道的使用。該對象在系統啓動偵測硬
件時自動被建立。驅動程序在初始化時調用HalGetAdapter得到該對象的指針。設備在StartIo過程當中調
用IoAllocateAdapterChannel請求得到DMA通道的獨佔權,而後開始傳輸數據。使用完後調用IoFreeCon
trollerChannel釋放DMA通道。
iv)DPC
因爲DPC隊列中的對象老是被系統順序地處理,因此也能夠將須要同步的代碼作成Dpc過程,須要調
用時將相應的DPC對象放到隊列的末尾便可。
v)其餘
同用戶模式的應用程序相似,驅動程序也可使用多線程,也提供了一套用來同步的對象,如Even
t,Mutex,Semaphore,Timer,Thread。其中Event對象能夠被命名,不一樣的驅動程序能夠利用同名的Event
對象同步對公共數據的訪問。
1.3.9分層
I/O管理器一個有用的功能是容許把一個驅動程序堆在另外一個驅動程序之上。這樣在分編寫如網絡驅
動等有協議棧程序時,能夠爲各層編寫相對獨立的代碼。當驅動程序須要在不一樣的平臺上移植時,只需
從新編寫最下層的硬件驅動程序便可。高層驅動程序的另外一個功能是能夠對用戶請求進行予處理,好比
把較大的請求分割成較小的請求分屢次傳給給下層的程序。
1.3.10設備名及其符號鏈接
WindowsNT系統維護着一個對象名字空間,把全部在系統內註冊過的對象的名字分類存在一個樹狀空
間裏,用Win32SDK提供的WinObj工具能夠瀏覽這個空間。若是但願設備能被用戶的CreateFile函數打開
,就須要在調用IoCreateDevice建立該設備對象時賦予 它一個名字,位於\Device\下,並調用IoCreat
eSymbolicLink在\DosDevices\下建立一個符號鏈接。這樣,用戶程序就能用CreateFile("\\\\.\\符號
鏈接名",……)打開該設備,並得到其句柄。
1.4驅動程序的編譯連接,調試、安裝和啓動
WindowsNT下編寫驅動程序的環境被稱爲爲DDK(DeviceDriverKit)ForMicrosoftWindowsNT,這是一
個命令行下的工做環境。可是在安裝DDK以前須要安裝Win32 SDK(SoftwareDevelopmentKit)以及Micr
osoftVisualC++。
編譯連接器爲Build.exe,他從配置文件Sources中讀出待編譯的程序的配置,包括源文件、目標文
件等,從環境變量Include中獲得引用文件的地址,而後調用Visual C++的編譯連接器Nmake.exe進行實
際的編譯連接工做。日誌文件build.log,build.wrn,build.err中分別記錄了編譯連接中執行的命令行
,遇到的錯誤,遇到的警告。編譯完成後的文件後綴爲.sys.
安裝過程分兩步:第一,將編譯成的.sys文件拷貝到WindowsNT的System32\Drivers\下;第二,在
註冊表的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\下建立與.sys文件同名的鍵,而後
在之下建立名爲Start,Type,ErrorControl的三個REG_DWORD類型的數值鍵。其中Start的鍵值控制該驅
動程序在系統啓動的哪一個階段被 啓動。小於3的數設定該驅動程序在系統啓動的某個階段被自動啓動;
3表示須要管理員手動啓動;4表示該程序被禁用。設置完畢後須要從新啓動系統。
手動啓動和中止一個驅動程序須要使用控制面板(ControlPanel)中的設備(Device)圖標。
因爲驅動程序的結構比較複雜,並且調試內核模式的代碼須要兩臺安裝有WindowsNT的計算機,比較麻煩,因此在編寫一個較複雜的驅動程序的過程當中應分步來進行測試。 在完成任何一部分工做後都應進行測試,以便及早地發現錯誤。根據本人的經驗,驅動程序中的大多數錯誤都是因爲不正確地訪問內存形成的。好比使用未被初始化的指針,釋放已經被釋放的內存,在DISPATCH_LEVEL或以上的運行級別引用分頁的內存。