天天看點

驅動篇——核心程式設計基礎

驅動篇之核心程式設計基礎,詳細介紹一些編寫驅動的一些注意事項和一些細節。

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由于系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程将會長期更新。 如有好的建議,歡迎回報。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,并聲明我的個人資訊和本人部落格o'o位址即可,但必須事先通知我。

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,友善學習本教程。

  看此教程之前,問個問題,你明确學驅動的目的了嗎?你的開發環境準備好了嗎?上一節的内容學會了嗎? 沒有的話就不要繼續了,請重新學習前面驅動篇的教程内容繼續。

🔒 華麗的分割線 🔒

  在應用層程式設計我們可以使用<code>WINDOWS</code>提供的各種<code>API</code>函數,隻要導入頭檔案<code>windows.h</code>就可以了。但是在核心程式設計的時候,微軟為核心程式提供了專用的<code>API</code>,隻要在程式中包含相應的頭檔案就可以使用了,如:<code>#include &lt;ntddk.h&gt;</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️⃣ 編寫驅動,實作如下功能:

&lt;1&gt; 初始化一個字元串;

&lt;2&gt; 拷貝一個字元串;

&lt;3&gt; 比較兩個字元串是否相等;

&lt;4&gt; <code>ANSI_STRING</code>與<code>UNICODE_STRING</code>字元串互相轉換;

3️⃣ 思考題:為什麼<code>DISPATCH_LEVEL</code>不能通路分頁記憶體。

  驅動篇——核心空間與核心子產品

驅動篇——核心程式設計基礎
驅動篇——核心程式設計基礎

本作品采用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協定 進行許可

本文來自部落格園,作者:寂靜的羽夏 ,一個熱愛計算機技術的菜鳥

轉載請注明原文連結:https://www.cnblogs.com/wingsummer/p/15491543.html

驅動篇——核心程式設計基礎