寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。
看此教程之前,問個問題,你明確學驅動的目的了嗎?你的開發環境準備好了嗎?上一節的內容學會了嗎? 沒有的話就不要繼續了,請重新學習前面驅動篇的教程內容繼續。
? 華麗的分割線 ?
練習及參考
本次答案均為參考,可以與我的答案不一致,但必須成功通過。
1️⃣ 編寫驅動,申請一塊記憶體,並在記憶體中儲存GDT
表的所有資料。然後在DebugView
中顯示出來,最後釋放記憶體。
? 點選檢視程式碼 ?
#include <ntddk.h>
NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = UnloadDriver;
/*讀取 GDT 表*/
char gdt[6];
_asm
{
sgdt gdt;
}
short limit = *(short*)gdt;
UINT32* base = *(UINT32*)&gdt[2];
UINT32* mem = (UINT32*)ExAllocatePoolWithTag(NonPagedPool, limit, NULL);
if (!mem)
{
DbgPrint("申請記憶體失敗!!\n");
}
else
{
RtlMoveMemory(mem, base, limit);
DbgPrint("\n=====列印申請的記憶體存的資料=====\n");
for (int i = 0; i < limit / 4; i+=2)
{
DbgPrint("%0.8x`%0.8x\n", mem[i+1], mem[i]);
}
ExFreePool(mem);
}
return STATUS_SUCCESS;
}
2️⃣ 編寫驅動,實現如下功能:
<1> 初始化一個字串;
<2> 拷貝一個字串;
<3> 比較兩個字串是否相等;
<4> ANSI_STRING
與UNICODE_STRING
字串相互轉換;
? 點選檢視程式碼 ?
#include <ntddk.h>
NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = UnloadDriver;
UNICODE_STRING unicode_str;
ANSI_STRING ansi_str;
RtlInitAnsiString(&ansi_str, "WingSummer CNBLOG");
RtlInitUnicodeString(&unicode_str, L"CNBLOG ONLY");
DbgPrint("ANSI : %Z\n", &ansi_str);
DbgPrint("Unicode : %wZ\n", &unicode_str);
ANSI_STRING astr;
RtlInitAnsiString(&astr, "Wingsummer CNBLOG");
BOOLEAN b0 = RtlCompareString(&ansi_str, &astr, TRUE);
BOOLEAN b1 = RtlCompareString(&ansi_str, &astr, FALSE);
DbgPrint("RtlCompareString : %d %d", b0, b1);
UNICODE_STRING ustr;
RtlInitUnicodeString(&ustr, L"Hello CNBLOG");
b0 = RtlCompareUnicodeString(&unicode_str, &ustr, FALSE);
DbgPrint("RtlCompareUnicodeString : %d", b0);
ANSI_STRING astr0;
UNICODE_STRING ustr0 = {0}; //主要是為了讓 IntelliSense 閉嘴
RtlAnsiStringToUnicodeString(&ustr0, &ansi_str, TRUE);
RtlUnicodeStringToAnsiString(&astr0, &ustr, TRUE);
DbgPrint("%wZ\n%Z", &unicode_str, &astr);
RtlFreeAnsiString(&astr0);
RtlFreeUnicodeString(&ustr0);
return STATUS_SUCCESS;
}
3️⃣ 思考題:為什麼DISPATCH_LEVEL
不能訪問分頁記憶體。
? 點選檢視答案 ?
說到這個問題,我們回過頭來看看上篇文章的一個圖:
從圖上可以看出,假設我們的驅動執行在IRQL
為DISPATCH_LEVEL
,並使用分頁記憶體,且使用的分頁記憶體的物理頁被作業系統回收放到檔案了。然後驅動程式需要訪問這個記憶體,必定會觸發缺頁異常,正常的話會進入異常處理程式。
然而不幸的是,這個異常處理程式處在APC_LEVEL
這層IRQL
,結果你現在的IRQL
是DISPATCH_LEVEL
,很明顯異常處理程式無法打斷它,結果無法執行。既然異常處理不了,那就進入熟悉的藍屏環節。
綜上所述,這就是為什麼DISPATCH_LEVEL
不能訪問分頁記憶體。
核心空間與核心模組
在之前的教程裡,我們介紹了普通的應用程式的4GB
記憶體只有低2GB
可能被自身使用,而高2GB
共用。示意圖如下所示:
硬體種類繁多,不可能做一個相容所有硬體的核心,所以,微軟提供規定的介面格式,讓硬體驅動人員安裝規定的格式編寫驅動程式
。在核心中,這些驅動程式每一個都是一個模組,稱為核心模組
,都可以載入到核心中,都遵守PE
結構。但本質上講,任意一個sys
檔案與核心檔案沒有區別。每個驅動都是一個模組。就和在3環的程式載入dll
一樣,載入一個貼上一個。如下圖所示:
DRIVER_OBJECT
DRIVER_OBJECT
這個東西,在上一篇具有詳細的講解,這次我們再把它給搬過來:
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
我們如何通過除錯資訊輸出呢,我們如下示例:
DbgPrint("DRIVER_OBJECT 物件地址:%x \r\n",driver);
DbgPrint("驅動名稱:%ws \r\n",driver->DriverName.Buffer);
DbgPrint("模組基址:%x \r\n",driver->DriverStart);
DbgPrint("模組大小:%x \r\n",driver->DriverSize);
遍歷核心模組與隱藏
上篇教程我們講過DriverSection
,它是一個儲存目前所有已載入的驅動程式資訊相關的LDR_DATA_TABLE_ENTRY
結構體的雙向迴圈連結串列。並且我們用WinDbg
手動遍歷了一下。那麼我們用程式碼遍歷也是可以的。這裡就留個作業了,具體程式碼留在下一篇進行講解。
如果我們把這個專案從DriverSection
的InLoadOrderLinks
摘掉後,是不是可以實現隱藏呢?會不會對我載入後的驅動有沒有影響?既然驅動已經載入到記憶體當中,就算你去除了它的存在資訊,對它的功能也沒有實際影響。可以實現隱藏,不過只是對付一些API
,但它還是被揪出來的,如何進一步隱藏將會在後面的教程介紹。
本節練習
本節的答案將會在下一節進行講解,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。
俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習不多,請保質保量的完成。
1️⃣ 遍歷核心模組,輸出模組名稱,基址以及大小。
2️⃣ 編寫一個函式,找到一個未匯出的函式,並呼叫。(例子:找到PspTerminateProcess
,通過呼叫這個函式結束記事本程式)
3️⃣ 通過斷鏈實現隱藏驅動模組。
下一篇
驅動篇——常規的0環與3環通訊