7.3 Windows驅動開發:內核監視LoadImage映像回調

在筆者上一篇文章《內核註冊並監控對象回調》介紹瞭如何運用ObRegisterCallbacks註冊進程與線程回調,並通過該回調實現了攔截指定進行運行的效果,本章LyShark將帶大家繼續探索一個新的回調註冊函數,PsSetLoadImageNotifyRoutine常用於註冊LoadImage映像監視,當有模塊被系統加載時則可以第一時間獲取到加載模塊信息,需要注意的是該回調函數內無法進行攔截,如需要攔截則需寫入返回指令這部分內容將在下一章進行講解,本章將主要實現對模塊的監視功能。

PsSetLoadImageNotifyRoutine和PsRemoveLoadImageNotifyRoutine是Windows操作系統提供的兩個內核API函數,用於註冊和取消註冊LoadImage映像的回調函數。

LoadImage映像回調函數是一種內核回調函數,它可以用於監視和攔截系統中的模塊加載事件,例如進程啓動時加載的DLL、驅動程序等。當有新的模塊被加載時,操作系統會調用註冊的LoadImage映像回調函數,並將加載模塊的相關信息傳遞給回調函數。

PsSetLoadImageNotifyRoutine函數用於註冊LoadImage映像的回調函數,而PsRemoveLoadImageNotifyRoutine函數則用於取消註冊已經註冊的回調函數。開發者可以在LoadImage映像回調函數中執行自定義的邏輯,例如記錄日誌、過濾敏感數據、或者阻止某些操作。

需要注意的是,LoadImage映像回調函數的註冊和取消註冊必須在內核模式下進行,並且需要開發者有一定的內核開發經驗。同時,LoadImage映像回調函數也需要遵守一些約束條件,例如不能阻塞或掛起進程或線程的創建或訪問,不能調用一些內核API函數等。

內核監視LoadImage映像回調在安全軟件、系統監控和調試工具等領域有着廣泛的應用。開發者可以利用這個機制來監視和攔截系統中的模塊加載事件,以保護系統安全。

監視模塊加載與卸載需要費別使用兩個函數,這兩個函數的參數傳遞都是自己的回調地址。

  • PsSetLoadImageNotifyRoutine 設置回調
  • PsRemoveLoadImageNotifyRoutine 移除回調

此處MyLySharkLoadImageNotifyRoutine回調地址必須有三個參數傳遞組成,其中FullImageName代表完整路徑,ModuleStyle代表模塊類型,一般來說ModuleStyle=0表示加載SYS驅動,如果ModuleStyle=1則表示加載的是DLL,最後一個參數ImageInfo則是映像的詳細參數結構體。

VOID MyLySharkLoadImageNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ModuleStyle, PIMAGE_INFO ImageInfo)

那麼如何實現監視映像加載呢,來看如下完整代碼片段,首先PsSetLoadImageNotifyRoutine註冊回調,當有模塊被加載則自動執行MyLySharkLoadImageNotifyRoutine回調函數,其內部首先判斷ModuleStyle得出是什麼類型的模塊,然後再通過GetDriverEntryByImageBase拿到當前進程詳細參數並打印輸出。

#include <ntddk.h>
#include <ntimage.h>

// 未導出函數聲明
PUCHAR PsGetProcessImageFileName(PEPROCESS pEProcess);

// 獲取到鏡像裝載基地址
PVOID GetDriverEntryByImageBase(PVOID ImageBase)
{
    PIMAGE_DOS_HEADER pDOSHeader;
    PIMAGE_NT_HEADERS64 pNTHeader;
    PVOID pEntryPoint;
    pDOSHeader = (PIMAGE_DOS_HEADER)ImageBase;
    pNTHeader = (PIMAGE_NT_HEADERS64)((ULONG64)ImageBase + pDOSHeader->e_lfanew);
    pEntryPoint = (PVOID)((ULONG64)ImageBase + pNTHeader->OptionalHeader.AddressOfEntryPoint);
    return pEntryPoint;
}

// 獲取當前進程名
UCHAR* GetCurrentProcessName()
{
    PEPROCESS pEProcess = PsGetCurrentProcess();
    if (NULL != pEProcess)
    {
        UCHAR *lpszProcessName = PsGetProcessImageFileName(pEProcess);
        if (NULL != lpszProcessName)
        {
            return lpszProcessName;
        }
    }
    return NULL;
}

// 設置自己的回調函數
VOID MyLySharkLoadImageNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ModuleStyle, PIMAGE_INFO ImageInfo)
{
    PVOID pDrvEntry;

    // MmIsAddress 驗證地址可用性
    if (FullImageName != NULL && MmIsAddressValid(FullImageName))
    {
        // ModuleStyle爲零表示加載sys
        if (ModuleStyle == 0)
        {
            // 得到裝載主進程名
            UCHAR *load_name = GetCurrentProcessName();
            pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
            DbgPrint("[LyShark SYS加載] 模塊名稱:%wZ --> 裝載基址:%p --> 鏡像長度: %d --> 裝載主進程: %s \n", FullImageName, pDrvEntry, ImageInfo->ImageSize, load_name);
        }
        // ModuleStyle非零表示加載DLL
        else
        {
            // 得到裝載主進程名
            UCHAR *load_name = GetCurrentProcessName();
            pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
            DbgPrint("[LyShark DLL加載] 模塊名稱:%wZ --> 裝載基址:%p --> 鏡像長度: %d --> 裝載主進程: %s \n", FullImageName, pDrvEntry, ImageInfo->ImageSize, load_name);
        }
    }
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLySharkLoadImageNotifyRoutine);
    DbgPrint("[lyshark] 驅動卸載完成...");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("hello lyshark \n");

    PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLySharkLoadImageNotifyRoutine);
    DbgPrint("[lyshark] 驅動加載完成...");
    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

運行這個驅動程序,則會輸出被加載的驅動詳細參數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章