驅動篇之核心程式設計基礎,詳細介紹一些編寫驅動的一些注意事項和一些細節。
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由于系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程将會長期更新。 如有好的建議,歡迎回報。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,并聲明我的個人資訊和本人部落格o'o位址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,友善學習本教程。
看此教程之前,問個問題,你明确學驅動的目的了嗎?你的開發環境準備好了嗎?上一節的内容學會了嗎? 沒有的話就不要繼續了,請重新學習前面驅動篇的教程内容繼續。
🔒 華麗的分割線 🔒
在應用層程式設計我們可以使用<code>WINDOWS</code>提供的各種<code>API</code>函數,隻要導入頭檔案<code>windows.h</code>就可以了。但是在核心程式設計的時候,微軟為核心程式提供了專用的<code>API</code>,隻要在程式中包含相應的頭檔案就可以使用了,如:<code>#include <ntddk.h></code>,前提你必須安裝了<code>WDK</code>。
遇到不會的函數或者不知道如何使用函數怎麼辦?在應用層程式設計的時候,我們通過<code>MSDN</code>來了解函數的詳細資訊,在核心程式設計的時候,要使用<code>WDK</code>自己的幫助文檔。
然而<code>WDK</code>說明文檔中隻包含了核心子產品導出的函數,對于未導出的函數,則不能直接使用。如果要使用未導出的函數,隻要自己定義一個函數指針,并且為函數指針提供正确的函數位址就可以使用了。有兩種辦法都可以擷取為導出的函數位址:特征碼搜尋和解析核心<code>PDB</code>檔案。對于第一種方法,每個函數不可能是一模一樣的,它們的寫死具有不同的特征,通過這個特定的獨一無二的寫死可以搜到我想要的函數。對于最後一種方法,我們思考一下<code>WinDbg</code>為什麼那麼強大。為什麼<code>WinDbg</code>可以輕松分析一些結構體,或者函數名稱?本質原因它有符号檔案并且能夠解析它,也就是<code>PDB</code>檔案。也就是為什麼我們之前要為它配備符号檔案路徑。
在核心程式設計的時候,強烈建議大家遵守<code>WDK</code>的編碼習慣,建議不要這樣寫:<code>unsigned long length;</code>,建議這樣寫:<code>ULONG length</code>。
如下是<code>WDK</code>習慣與我們正常的習慣:
WDK 習慣
SDK 習慣
ULONG
unsigned long
PULONG
unsigned long*
UCHAR
unsigned char
PUCHAR
unsigned char*
UINT
unsigned int
PUNIT
unsigned int*
VOID
void
PVOID
void*
大部分核心函數的傳回值都是<code>NTSTATUS</code>類型,如:
這個值能說明函數執行的結果,比如:
當你調用的核心函數,如果傳回的結果不是<code>STATUS_SUCCESS</code>,就說明函數執行中遇到了問題,具體是什麼問題,可以在<code>ntstatus.h</code>檔案中檢視。
在核心中,一個小小的錯誤就可能導緻藍屏,比如:讀寫一個無效的記憶體位址。為了讓自己的核心程式更加健壯,強烈建議大家在編寫核心程式時,使用異常處理,降低藍屏的可能性。不過錯誤大了該藍屏的還是藍屏。
<code>Windows</code>提供了結構化異常處理機制,一般的編譯器都是支援的,如下:
出現異常時,可根據<code>filter_value</code>的值來決定程式該如果執行,當<code>filter_value</code>的值為:
1️⃣ <code>EXCEPTION_EXECUTE_HANDLER(1)</code>:代碼進入<code>except</code>塊
2️⃣ <code>EXCEPTION_CONTINUE_SEARCH(0)</code>:不處理異常,由上一層調用函數處理
3️⃣ <code>EXCEPTION_CONTINUE_EXECUTION(-1)</code>:回去繼續執行錯誤處的代碼
對記憶體的使用,主要就是:申請、設定、拷貝以及釋放。我們在編寫3環的應用程式和核心對應的函數舉例如下,具體使用請檢視<code>MSDN</code>和<code>WDK</code>的幫助文檔:
普通程式
核心中
malloc
ExAllocatePoolWithTag
memset
RtlFillMemory
memcpy
RtlMoveMemory
free
ExFreePool
當然<code>malloc</code>對應的核心函數有很多,但是有很多已經被廢棄掉了,下面是說明:
The ExAllocatePool routine is obsolete, and is exported only for existing binaries. Use ExAllocatePoolWithTag instead.
當我們進行記憶體申請時,比如遇到<code>ExAllocatePoolWithTag</code>函數時,會有<code>POOL_TYPE PoolType</code>這個參數。那麼什麼是<code>POOL_TYPE</code>,我們查一下<code>WDK</code>:
其實我們用的成員也就前兩項目<code>NonPagedPool</code>和<code>PagedPool</code>,分别申請非分頁記憶體和分頁記憶體。那麼什麼是非分頁記憶體?什麼是分頁記憶體?我們在前面介紹過申請的實體頁并不是永久屬于你的,這個申請的頁就是分頁記憶體,也就是可以随時被作業系統撤走轉到虛拟記憶體交換檔案。而非分頁記憶體就是告訴作業系統,不要把我的申請的實體頁撤走,這就是我獨享的實體頁。作業系統就不會把它給撤走轉到檔案中了。
在編寫3環程式我們經常用:<code>CHAR(char)</code>/<code>WCHAR(wchar_t)</code>來分别表示宅字元串和寬字元串,用0表示結尾。但是在核心中,我們常用:<code>ANSI_STRING</code>/<code>UNICODE_STRING</code>來分别表示宅字元串和寬字元串。它們的結構如下:
<code>ANSI_STRING</code>字元串:
<code>UNICODE_STRING</code>字元串:
為什麼核心要用這樣的字元串呢?主要是為了安全考慮。我們初學<code>C語言</code>的時候經常列印出<code>燙燙燙</code>之類的字元串,那是因為它列印沒用0結尾的字元串的結果。如果核心出現了這個問題,很容易導緻藍屏。故使用改結構體保證安全性。當然,處理這樣的字元串核心就有專門處理的函數,接下來我将繼續介紹。
字元串常用的功能無非就是:建立、複制、比較以及轉換等等。它們的函數如下,具體使用請檢視<code>WDK</code>的幫助文檔:
ANSI_STRING
UNICODE_STRING
RtlInitAnsiString
RtlInitUnicodeString
RtlCopyString
RtlCopyUnicodeString
RtlCompareString
RtlCompareUnicodeString
RtlAnsiStringToUnicodeString
RtlUnicodeStringToAnsiString
上一篇教程我們用了一段代碼,用來測試驅動是否能夠加載并執行,下面我們就來解析它,上次使用的代碼如下:
<code>DriverEntry</code>是驅動程式的入口,如果驅動加載成功後,就像<code>Dll</code>加載成功調用<code>DllMain</code>函數一樣,調用該函數。
是指向<code>DRIVER_OBJECT</code>結構體的指針。一個驅動檔案被加載後,它的完整資訊将會傳回給我們。我們來看看<code>DRIVER_OBJECT</code>這個結構體存了什麼,下面是頭檔案裡面的定義:
既然是講解基礎,我們就挑最重要的幾個來講解。不過為了友善學習驅動,我們對上面的代碼進行小小的修改:
然後編譯,讓虛拟機加載這個驅動。如下圖所示,然後我們得到了它的首位址:

然後我們再<code>dt</code>一下:
驅動對象加載後的起始位址。
驅動對象加載後的記憶體大小。
它是一個存儲目前所有已加載的驅動程式資訊相關的<code>LDR_DATA_TABLE_ENTRY</code>結構體的雙向循環連結清單。通過這個東西來實作把它們全部串起來,通過這個我們也可以進行周遊。我們通過<code>WinDbg</code>來看看。我們先<code>dt</code>一下我們自己編寫的驅動的<code>DriverSection</code>:
然後我們繼續<code>dt</code>下一個成員:
可以看出,我們可以通過這個連結清單實作周遊驅動程式的資訊。
訓示驅動對象的名字,是一個<code>_UNICODE_STRING</code>的結構體。
驅動對象的解除安裝位址,如果存在則會調用它。它的定義:
剩下的未介紹的成員,自己感興趣的自行繼續探索。
<code>IRQL</code>全稱<code>Interrupt Request Level</code>,即中斷執行的優先級。它是<code>Windows</code>自己定義的一套優先級方案,與<code>CPU</code>無關,數值越大權限越高。中斷包括了硬中斷和軟中斷,硬中斷是由硬體産生,而軟中斷則是完全虛拟出來的。處理器在一個<code>IRQL</code>上執行線程代碼,每個處理器的<code>IRQL</code>決定了它如何進行中斷,以及允許接收哪些中斷。在同一處理器上,線程隻能被更進階别<code>IRQL</code>的線程能中斷。每個處理器都有自己的中斷<code>IRQL</code>。常見的<code>IRQL</code>級别有四個:<code>Passive</code>、<code>APC</code>、<code>Dispatch</code>、<code>DIRQL</code>。<code>PASSIVE_LEVEL</code>是最低級别,沒有被屏蔽的中斷,線程執行使用者模式,可以通路分頁記憶體。<code>APC_LEVEL</code>隻有<code>APC</code>級别的中斷被屏蔽,可以通路分頁記憶體。當有<code>APC</code>發生時,處理器提升到<code>APC</code>級别,就屏蔽掉其它<code>APC</code>。<code>DISPATCH_LEVEL</code>可以屏蔽<code>DPC</code>(延遲過程) 和更低的中斷,不能通路分頁記憶體。因為隻能處理分頁記憶體,是以在這個級别,能夠通路的<code>API</code>大大減少。對于我們核心安全來講,了解這些就夠了,如下是<code>IRQL</code>的示意圖:
在進行核心程式編寫的時候,尤其注意<code>IRQL</code>這個東西。有很多的藍屏是以而起。
本節的答案将會在下一節進行講解,務必把本節練習做完後看下一個講解内容。不要偷懶,實驗是學習本教程的捷徑。
俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習不多,請保質保量的完成。
1️⃣ 編寫驅動,申請一塊記憶體,并在記憶體中存儲<code>GDT</code>表的所有資料。然後在<code>DebugView</code>中顯示出來,最後釋放記憶體。
2️⃣ 編寫驅動,實作如下功能:
<1> 初始化一個字元串;
<2> 拷貝一個字元串;
<3> 比較兩個字元串是否相等;
<4> <code>ANSI_STRING</code>與<code>UNICODE_STRING</code>字元串互相轉換;
3️⃣ 思考題:為什麼<code>DISPATCH_LEVEL</code>不能通路分頁記憶體。
驅動篇——核心空間與核心子產品
本作品采用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協定 進行許可
本文來自部落格園,作者:寂靜的羽夏 ,一個熱愛計算機技術的菜鳥
轉載請注明原文連結:https://www.cnblogs.com/wingsummer/p/15491543.html