驅動篇——核心空間與核心模組

寂靜的羽夏發表於2021-11-06

寫在前面

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

你如果是從中間插過來看的,請仔細閱讀 羽夏看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_STRINGUNICODE_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不能訪問分頁記憶體。

? 點選檢視答案 ?


  說到這個問題,我們回過頭來看看上篇文章的一個圖:

驅動篇——核心空間與核心模組

  從圖上可以看出,假設我們的驅動執行在IRQLDISPATCH_LEVEL,並使用分頁記憶體,且使用的分頁記憶體的物理頁被作業系統回收放到檔案了。然後驅動程式需要訪問這個記憶體,必定會觸發缺頁異常,正常的話會進入異常處理程式。

  然而不幸的是,這個異常處理程式處在APC_LEVEL這層IRQL,結果你現在的IRQLDISPATCH_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手動遍歷了一下。那麼我們用程式碼遍歷也是可以的。這裡就留個作業了,具體程式碼留在下一篇進行講解。
  如果我們把這個專案從DriverSectionInLoadOrderLinks摘掉後,是不是可以實現隱藏呢?會不會對我載入後的驅動有沒有影響?既然驅動已經載入到記憶體當中,就算你去除了它的存在資訊,對它的功能也沒有實際影響。可以實現隱藏,不過只是對付一些API,但它還是被揪出來的,如何進一步隱藏將會在後面的教程介紹。

本節練習

本節的答案將會在下一節進行講解,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。

  俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習不多,請保質保量的完成。

1️⃣ 遍歷核心模組,輸出模組名稱,基址以及大小。
2️⃣ 編寫一個函式,找到一個未匯出的函式,並呼叫。(例子:找到PspTerminateProcess,通過呼叫這個函式結束記事本程式)
3️⃣ 通過斷鏈實現隱藏驅動模組。

下一篇

  驅動篇——常規的0環與3環通訊

相關文章