Windows內核開發-2-開始內核開發-2-內核開發入門

Windows內核開發-2-開始內核開發-2-

第一個驅動程序:

直接採用vs2019中的Empty WDM Driver 模塊創建:

 

 

初始的項目文件夾中有一個Driver Files裏面會有一個.inf的文件,沒用直接刪除就好,然後在源文件裏面創建一個.cpp的源文件。

DriverEntry和Unload Routines

DriverEntry:

每個驅動都有一個入口點,叫做DriverEntry,就好比平常寫的C/C++代碼裏面的main函數。DriverEntry是由一個叫做IRQL_PASSIVE_LEVEL(0)的系統進程調用出來的。DriverEntry函數原型:

NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING
RegistryPath);

代碼原型里的_In _是源代碼注釋語言(SAL)中的一部分,用來描述函數如何使用其參數,SAL對於編譯器來說可以直接忽略,但是對程序員很有幫助。

相關鏈接:Understanding SAL | Microsoft Docs

這裡的最小的DriverEntry示例可以只返回一個狀態,比如:

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
return STATUS_SUCCESS;
}

該DriverEntry函數包含在<ntddk.h>頭文件中,但是添加了頭文件後仍然是編譯失敗的,因為編譯器會把警告當場錯誤來報錯,但是不建議刪除該功能,因為有時候警告就是會導致錯誤誕生:

 

 

可以對應修改這些警告,比如這裡將形參刪除,但是這樣僅對於C++好用,因為C++有函數重載,所以這裡用不上。這裡有一個很經典的解決辦法,就是採用一個宏函數:

UNREFERENCED_PARAMETER();

這樣就可以暫時解決掉前面的報錯說形參沒有使用了:

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);

return STATUS_SUCCESS;
}

但是這樣仍然不行:

 

 

可以很明顯得看出,編譯器沒問題,但是Linker鏈接器出了問題,DriverEntry是一個C函數,必須用C的linker來link,但是這裡我們採用的是C++的默認,所以必須給該函數設置為C的默認LInker才行

extern "C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);

return STATUS_SUCCESS;
}

這樣最簡單的驅動程序的源代碼就寫好了,就相當於C語言中的:

#include<stdio.h>

int main()
{

return 0;
}

Unload Routines:

這是一個驅動的卸載函數,就相當於C++中類的析構函數一樣,當驅動被卸載的時候就會自動調用該函數。在DriverEntry函數中創建的東西需要由Unload Routines來釋放,這就非常像C++類中的構造函數和析構函數的關係了。如果沒有該函數來釋放驅動加載時所開闢的內容就會導致泄露,直到下次電腦重啟時內核產生的泄露才會清楚。

該函數的函數指針,必須在DriverEntry給DriverEntry的參數DriverObject中的DriverUnload字段賦值才行。Unload函數和DriverEntry函數一樣都需要接受一個_In _ PDRIVER_OBJECT DriverObject 參數,但是Unload函數不需要返回值,直接用void 定義就好。

比如:

#include <ntddk.h>

void SampleUnload(_In_ PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
}

extern "C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
DriverObject->DriverUnload = SampleUnload;
UNREFERENCED_PARAMETER(RegistryPath);

return STATUS_SUCCESS;
}

就是一個非常簡單但是可以用的驅動了。

部署驅動程序Deploying the Driver

前面已經寫好了一個驅動程序,但是我們還需要把它跑起來,驅動程序不像平時寫的普通程序一樣,採用IDE就可以正常使用了,需要將其加載到系統裏面,通常為了避免風險,採用虛擬機來部署驅動程序。

安裝驅動程序就像是在User用戶態安裝服務.exe一樣,需要調用CreateService API或者採用現有的工具,這裡採用比較常用的Sc.exe來進行部署內核驅動。

註冊驅動

採用管理員權限的命令行:

sc create sample type=kernel binPath=C:\DriverTest\MyDriver3.sys

其中 sample是創建的名字,然後type表示創建的權限,binPath後面的是驅動的路徑。如果沒問天會彈出一個成功的標識符。

並且可以在註冊表裏面查到:

使用Win+R的彈出框裏面輸入regedit.exe,查看路徑\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\sample,這就是我們剛剛寫好且用sc創造的驅動:

 

 

啟動驅動

前面是註冊了該驅動,還得使用它。這裡也和User用戶態的服務Service類似,需要採用StartService API來使用或者採用一些軟件,這裡也可以用Sc.exe來繼續使用。

sc start sample

這裡的sample就是前面註冊的驅動的名字。

但是這個通常會失效,因為對於64bit位的windows系統,加載驅動必須得要有驅動的簽名才行,這裡為了學習方便,避開簽名這個東西,可以直接把系統置為測試版本。

bcdedit /set testsigning on

如果你要生成除了Windows10以外的版本,可以在項目屬性裏面配置你的驅動要部署在的系統環境裏面:

 

 

最後再使用前面sc來加載驅動時會看到一個關於驅動的輸出:

 

 

有了這個輸出就表明我們的驅動已經成功加載了,可以使用Process Explorer工具來確認是否加載成功(下載地址://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer

 

 

這裡的驅動名稱是你自己的sys驅動名稱。

卸載驅動

不用了將驅動程序卸下,同樣的可以採用 ControlService API或者Sc.exe來處理。Sc指令:

sc stop sample

就OK了。

簡單跟蹤

為了確保函數有確切被調用,這裡提供一種基礎的跟蹤辦法來確保函數被使用,驅動採用KdPrint這個宏來輸出類似於printf風格的文本,該宏的內容可以被內核的調試器,或者其它工具查看到。

KdPrint這個宏只在debug模式下採用,它的底層調用的其實是DbgPrint 內核Kernel API。

下面更新一下DriverEntry和Unload函數:

void SampleUnload(_In_ PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
KdPrint(("Sample driver Unload called! \n"));
}

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
DriverObject->DriverUnload = SampleUnload;
UNREFERENCED_PARAMETER(RegistryPath);
KdPrint(("Sample driver initialized successfully\n !"));

return STATUS_SUCCESS;
}

需要注意的是該宏函數在調用時採用了兩個括號,因為它是一個宏函數,但是又顯然它是可以接受任意變量的,由於宏函數不能接受可變的變量參數,所以編譯器實際上調用的是DbgPrint函數。這裡理解不了沒關係,先這樣用着就行。

重新生成驅動並加載來查看這些Print信息,這裡需要採用一個內核的調試器才行,但是為了方便,先採用一個系統的內部工具:DebugView來查看。在使用DebugView之前,需要先給它在註冊表裏面配置內容不然用不上。

在\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\里新建一個Key(項)Key名為DebugPrintFilter,並且添加一個DWORD類型的值名為DEFAULT,這個DEFAULT要和默認的一個值區別開來,後面那個默認的值是註冊表中的每一個項都有的,那個值暫時先不用管,然後給該名為DEFAULT類型為DWORD的變量賦值為8,如下圖所示:

 

 

 

然後下載DebugView(DebugView – Windows Sysinternals | Microsoft Docs),並用管理員身份打開它,然後再在Capture選項中去掉Capture Win32 和Capture Global Win32,選中Capture Kernel:

 

 

這樣,再使用Sc.exe來重新加載驅動就可以看到KdPrint打印的內容了。

 

 

總結Summary

這裡明白了如何寫一個驅動,以及如何再電腦上部署和查看驅動的消息,算是驅動入門了。