驅動開發:核心實現SSDT掛鉤與摘鉤

2023-06-05 12:01:27

在前面的文章《驅動開發:核心解析PE結構匯出表》中我們封裝了兩個函數KernelMapFile()函數可用來讀取核心檔案,GetAddressFromFunction()函數可用來在匯出表中尋找指定函數的匯出地址,本章將以此為基礎實現對特定SSDT函數的Hook掛鉤操作,與《驅動開發:核心層InlineHook掛鉤函數》所使用的掛鉤技術基本一致,不同點是前者使用了CR3的方式改寫記憶體,而今天所講的是通過MDL對映實現,此外前者掛鉤中所取到的地址是通過GetProcessAddress()取到的動態地址,而今天所使用的方式是通過讀取匯出表尋找。

掛鉤的目的就是要為特定函數增加功能,掛鉤的實現方式無非就是替換原函數地址,我們以核心函數ZwQueryDirectoryFile()為例,ZwQueryDirectoryFile例程返回給定檔案控制程式碼指定的目錄中檔案的各種資訊,其微軟定義如下;

NTSYSAPI NTSTATUS ZwQueryDirectoryFile(
  [in]           HANDLE                 FileHandle,
  [in, optional] HANDLE                 Event,
  [in, optional] PIO_APC_ROUTINE        ApcRoutine,
  [in, optional] PVOID                  ApcContext,
  [out]          PIO_STATUS_BLOCK       IoStatusBlock,
  [out]          PVOID                  FileInformation,
  [in]           ULONG                  Length,
  [in]           FILE_INFORMATION_CLASS FileInformationClass,
  [in]           BOOLEAN                ReturnSingleEntry,
  [in, optional] PUNICODE_STRING        FileName,
  [in]           BOOLEAN                RestartScan
);

如果需要Hook一個函數則你需要去微軟官方得到該函數的具體宣告部分包括其返回值,而Hook的目的只是為函數增加或處理新功能,則在執行完自定義函數後一定要跳回到原始函數上,此時定義一個typedef_ZwQueryDirectoryFile函數指標在呼叫結束後即可很容易的跳轉回原函數上,保證流程被正確執行,如果需要Hook其他函數其編寫模板也是如下所示;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

// 儲存原函數地址
PVOID gOldFunctionAddress = NULL;

// Hook後被替換的新函數
NTSTATUS MyZwQueryDirectoryFile(
	IN HANDLE               FileHandle,
	IN HANDLE               Event OPTIONAL,
	IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
	IN PVOID                ApcContext OPTIONAL,
	OUT PIO_STATUS_BLOCK    IoStatusBlock,
	OUT PVOID               FileInformation,
	IN ULONG                Length,
	IN FILE_INFORMATION_CLASS FileInformationClass,
	IN BOOLEAN              ReturnSingleEntry,
	IN PUNICODE_STRING      FileMask OPTIONAL,
	IN BOOLEAN              RestartScan
	)
{
	NTSTATUS status = STATUS_SUCCESS;

	// 定義函數指標
	typedef NTSTATUS(*typedef_ZwQueryDirectoryFile)(
		IN HANDLE               FileHandle,
		IN HANDLE               Event OPTIONAL,
		IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
		IN PVOID                ApcContext OPTIONAL,
		OUT PIO_STATUS_BLOCK    IoStatusBlock,
		OUT PVOID               FileInformation,
		IN ULONG                Length,
		IN FILE_INFORMATION_CLASS FileInformationClass,
		IN BOOLEAN              ReturnSingleEntry,
		IN PUNICODE_STRING      FileMask OPTIONAL,
		IN BOOLEAN              RestartScan
		);

	DbgPrint("MyZwQueryDirectoryFile 自定義功能 \n");

	// 執行原函數
	status = ((typedef_ZwQueryDirectoryFile)gOldFunctionAddress)(FileHandle,
		Event,
		ApcRoutine,
		ApcContext,
		IoStatusBlock,
		FileInformation,
		Length,
		FileInformationClass,
		ReturnSingleEntry,
		FileMask,
		RestartScan);

	return status;
}

接著就是如何掛鉤並讓其中轉到我們自己的程式碼流程中的問題,由於掛鉤與恢復程式碼是一樣的此處就以掛鉤為例,首先呼叫MmCreateMdl()建立MDL,接著呼叫MmBuildMdlForNonPagedPool()接收一個 MDL,該MDL指定非分頁虛擬記憶體緩衝區,並對其進行更新以描述基礎物理頁。呼叫MmMapLockedPages()將此段記憶體提交為鎖定狀態,最後就是呼叫RtlCopyMemory()將新函數地址寫出到記憶體中實現替換,最後釋放MDL控制程式碼即可,這段程式碼如下所示,看過驅動讀寫篇的你一定很容易就能理解。

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

// 掛鉤SSDT函數
BOOLEAN SSDTFunctionHook(ULONG64 FunctionAddress)
{
	PMDL pMdl = NULL;
	PVOID pNewAddress = NULL;
	ULONG ulNewFuncAddr = 0;

	gOldFunctionAddress = FunctionAddress;

	// 使用MDL修改SSDT
	pMdl = MmCreateMdl(NULL, &FunctionAddress, sizeof(ULONG));
	if (NULL == pMdl)
	{
		return FALSE;
	}

	MmBuildMdlForNonPagedPool(pMdl);

	// 鎖定記憶體
	pNewAddress = MmMapLockedPages(pMdl, KernelMode);
	if (NULL == pNewAddress)
	{
		IoFreeMdl(pMdl);
		return FALSE;
	}

	// 寫入新函數地址
	ulNewFuncAddr = (ULONG)MyZwQueryDirectoryFile;
	RtlCopyMemory(pNewAddress, &ulNewFuncAddr, sizeof(ULONG));

	// 釋放
	MmUnmapLockedPages(pNewAddress, pMdl);
	IoFreeMdl(pMdl);

	return TRUE;
}

Hook核心程式碼如下所示,為了節約篇幅,如果您找不到程式中的核心功能,請看前面的幾篇文章,這裡就不在贅述了。

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

// 儲存原函數地址
PVOID gOldFunctionAddress = NULL;

// Hook後被替換的新函數
NTSTATUS MyZwQueryDirectoryFile(
	IN HANDLE               FileHandle,
	IN HANDLE               Event OPTIONAL,
	IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
	IN PVOID                ApcContext OPTIONAL,
	OUT PIO_STATUS_BLOCK    IoStatusBlock,
	OUT PVOID               FileInformation,
	IN ULONG                Length,
	IN FILE_INFORMATION_CLASS FileInformationClass,
	IN BOOLEAN              ReturnSingleEntry,
	IN PUNICODE_STRING      FileMask OPTIONAL,
	IN BOOLEAN              RestartScan
	)
{
	NTSTATUS status = STATUS_SUCCESS;

	// 定義函數指標
	typedef NTSTATUS(*typedef_ZwQueryDirectoryFile)(
		IN HANDLE               FileHandle,
		IN HANDLE               Event OPTIONAL,
		IN PIO_APC_ROUTINE      ApcRoutine OPTIONAL,
		IN PVOID                ApcContext OPTIONAL,
		OUT PIO_STATUS_BLOCK    IoStatusBlock,
		OUT PVOID               FileInformation,
		IN ULONG                Length,
		IN FILE_INFORMATION_CLASS FileInformationClass,
		IN BOOLEAN              ReturnSingleEntry,
		IN PUNICODE_STRING      FileMask OPTIONAL,
		IN BOOLEAN              RestartScan
		);

	DbgPrint("MyZwQueryDirectoryFile 自定義功能 \n");

	// 執行原函數
	status = ((typedef_ZwQueryDirectoryFile)gOldFunctionAddress)(FileHandle,
		Event,
		ApcRoutine,
		ApcContext,
		IoStatusBlock,
		FileInformation,
		Length,
		FileInformationClass,
		ReturnSingleEntry,
		FileMask,
		RestartScan);

	return status;
}

// 掛鉤SSDT函數
BOOLEAN SSDTFunctionHook(ULONG64 FunctionAddress)
{
	PMDL pMdl = NULL;
	PVOID pNewAddress = NULL;
	ULONG ulNewFuncAddr = 0;

	gOldFunctionAddress = FunctionAddress;

	// 使用MDL修改SSDT
	pMdl = MmCreateMdl(NULL, &FunctionAddress, sizeof(ULONG));
	if (NULL == pMdl)
	{
		return FALSE;
	}

	MmBuildMdlForNonPagedPool(pMdl);

	// 鎖定記憶體
	pNewAddress = MmMapLockedPages(pMdl, KernelMode);
	if (NULL == pNewAddress)
	{
		IoFreeMdl(pMdl);
		return FALSE;
	}

	// 寫入新函數地址
	ulNewFuncAddr = (ULONG)MyZwQueryDirectoryFile;
	RtlCopyMemory(pNewAddress, &ulNewFuncAddr, sizeof(ULONG));

	// 釋放
	MmUnmapLockedPages(pNewAddress, pMdl);
	IoFreeMdl(pMdl);

	return TRUE;
}

// 恢復SSDT函數
BOOLEAN SSDTFunctionUnHook(ULONG64 FunctionAddress)
{
	PMDL pMdl = NULL;
	PVOID pNewAddress = NULL;
	ULONG ulOldFuncAddr = 0;

	gOldFunctionAddress = FunctionAddress;

	// 使用MDL修改SSDT
	pMdl = MmCreateMdl(NULL, &FunctionAddress, sizeof(ULONG));
	if (NULL == pMdl)
	{
		return FALSE;
	}

	MmBuildMdlForNonPagedPool(pMdl);

	// 鎖定記憶體
	pNewAddress = MmMapLockedPages(pMdl, KernelMode);
	if (NULL == pNewAddress)
	{
		IoFreeMdl(pMdl);
		return FALSE;
	}

	// 寫入新函數地址
	ulOldFuncAddr = (ULONG)gOldFunctionAddress;
	RtlCopyMemory(pNewAddress, &ulOldFuncAddr, sizeof(ULONG));

	// 釋放
	MmUnmapLockedPages(pNewAddress, pMdl);
	IoFreeMdl(pMdl);

	return TRUE;
}

// 關閉驅動
VOID UnDriver(PDRIVER_OBJECT driver)
{
	SSDTFunctionUnHook(gOldFunctionAddress);
	DbgPrint("驅動解除安裝 \n");
}

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

	NTSTATUS status = STATUS_SUCCESS;

	HANDLE hFile = NULL;
	HANDLE hSection = NULL;
	PVOID pBaseAddress = NULL;
	UNICODE_STRING FileName = { 0 };
	ULONG64 FunctionAddress = 0;

	// 初始化字串
	RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");

	// 記憶體對映檔案
	status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);
	if (NT_SUCCESS(status))
	{
		DbgPrint("讀取記憶體地址 = %p \n", pBaseAddress);
	}

	// 獲取指定模組匯出函數地址
	FunctionAddress = GetAddressFromFunction(FileName, "ZwQueryDirectoryFile");
	DbgPrint("ZwQueryVirtualMemory記憶體地址 = %p \n", FunctionAddress);

	// 開始Hook掛鉤
	if (FunctionAddress != 0)
	{
		BOOLEAN ref = SSDTFunctionHook(FunctionAddress);
		if (ref == TRUE)
		{
			DbgPrint("[+] Hook已掛鉤 \n");
		}
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

編譯並執行這段驅動程式,則你會看到掛鉤成功的提示資訊;