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

在前面的文章《驅動開發:內核解析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;
}

編譯並運行這段驅動程序,則你會看到掛鉤成功的提示信息;

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