驅動開發:核心監視LoadImage映像回撥

2022-10-25 09:00:53

在筆者上一篇文章《驅動開發:核心註冊並監控物件回撥》介紹瞭如何運用ObRegisterCallbacks註冊程序與執行緒回撥,並通過該回撥實現了攔截指定進行執行的效果,本章LyShark將帶大家繼續探索一個新的回撥註冊函數,PsSetLoadImageNotifyRoutine常用於註冊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拿到當前程序詳細引數並列印輸出。

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#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.com] 驅動解除安裝完成...");
}

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

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

執行這個驅動程式,則會輸出被載入的驅動詳細引數。