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工具來確認是否加載成功(下載地址:https://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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM