驅動開發:內核監控進程與線程回調

在前面的文章中LyShark一直在重複的實現對系統底層模塊的枚舉,今天我們將展開一個新的話題,內核監控,我們以監控進程線程創建為例,在Win10系統中監控進程與線程可以使用微軟提供給我們的兩個新函數來實現,此類函數的原理是創建一個回調事件,當有進程或線程被創建或者註銷時,系統會通過回調機制將該進程相關信息優先返回給我們自己的函數待處理結束後再轉向系統層。

進程回調默認會設置CreateProcess通知,而線程回調則會設置CreateThread通知,我們來看ARK工具中的枚舉效果。

  • 通常情況下:
    • PsSetCreateProcessNotifyRoutineEx 用於監控進程
    • PsSetCreateThreadNotifyRoutine 用於監控線程

監控進程的啟動與退出可以使用 PsSetCreateProcessNotifyRoutineEx來創建回調,當新進程創建時會優先執行回調,我們看下微軟是如何定義的結構。

// 參數1: 新進程回調函數
// 參數2: 是否註銷
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
  [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
  [in] BOOLEAN                           Remove
);

如上,該函數只有兩個參數,第一個參數是回調函數,第二個參數是是否註銷,通常在驅動退出時可以傳入TRUE對該回調進行註銷,通常情況下如果驅動關閉,則必須要註銷回調,而對於MyLySharkCreateProcessNotifyEx自定義回調來說,則需要指定三個必須要有的參數傳遞。

// 參數1: 新進程的EProcess
// 參數2: 新進程PID
// 參數3: 新進程詳細信息 (僅在創建進程時有效)

VOID MyLySharkCreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)

根據如上函數定義,就可以實現監控功能了,例如我們監控如果進程名是lyshark.exe則直接CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL禁止該進程打開。

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>

// 兩個未公開函數導出
NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);

// 通過PID獲得進程名
PCHAR GetProcessNameByProcessId(HANDLE ProcessId)
{
	NTSTATUS st = STATUS_UNSUCCESSFUL;
	PEPROCESS ProcessObj = NULL;
	PCHAR string = NULL;
	st = PsLookupProcessByProcessId(ProcessId, &ProcessObj);
	if (NT_SUCCESS(st))
	{
		string = PsGetProcessImageFileName(ProcessObj);
		ObfDereferenceObject(ProcessObj);
	}
	return string;
}

// 繞過簽名檢查
BOOLEAN BypassCheckSign(PDRIVER_OBJECT pDriverObject)
{
#ifdef _WIN64
	typedef struct _KLDR_DATA_TABLE_ENTRY
	{
		LIST_ENTRY listEntry;
		ULONG64 __Undefined1;
		ULONG64 __Undefined2;
		ULONG64 __Undefined3;
		ULONG64 NonPagedDebugInfo;
		ULONG64 DllBase;
		ULONG64 EntryPoint;
		ULONG SizeOfImage;
		UNICODE_STRING path;
		UNICODE_STRING name;
		ULONG   Flags;
		USHORT  LoadCount;
		USHORT  __Undefined5;
		ULONG64 __Undefined6;
		ULONG   CheckSum;
		ULONG   __padding1;
		ULONG   TimeDateStamp;
		ULONG   __padding2;
	} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#else
	typedef struct _KLDR_DATA_TABLE_ENTRY
	{
		LIST_ENTRY listEntry;
		ULONG unknown1;
		ULONG unknown2;
		ULONG unknown3;
		ULONG unknown4;
		ULONG unknown5;
		ULONG unknown6;
		ULONG unknown7;
		UNICODE_STRING path;
		UNICODE_STRING name;
		ULONG   Flags;
	} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#endif

	PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
	pLdrData->Flags = pLdrData->Flags | 0x20;

	return TRUE;
}

// 進程回調函數
VOID My_LyShark_Com_CreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
	char ProcName[16] = { 0 };
	if (CreateInfo != NULL)
	{
		strcpy_s(ProcName, 16, PsGetProcessImageFileName(Process));
		DbgPrint("[LyShark] 父進程ID: %ld | 父進程名: %s | 進程名: %s | 進程路徑:%wZ \n", CreateInfo->ParentProcessId, GetProcessNameByProcessId(CreateInfo->ParentProcessId), PsGetProcessImageFileName(Process), CreateInfo->ImageFileName);

		// 判斷是否為指定進程
		if (0 == _stricmp(ProcName, "lyshark.exe"))
		{
			// 禁止打開
			CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;
		}
	}
	else
	{
		strcpy_s(ProcName, 16, PsGetProcessImageFileName(Process));
		DbgPrint("[LyShark] 進程[ %s ] 退出了, 程序被關閉", ProcName);
	}
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DWORD32 ref = 0;

	// 註銷進程回調
	ref = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)My_LyShark_Com_CreateProcessNotifyEx, TRUE);
	DbgPrint("[lyshark.com] 註銷進程回調: %d \n", ref);
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	NTSTATUS status;

	// 繞過簽名檢查
	// LINKER_FLAGS=/INTEGRITYCHECK
	BypassCheckSign(Driver);

	DbgPrint("hello lyshark.com \n");

	// 創建進程回調
	// 參數1: 新進程的EProcess
	// 參數2: 新進程PID
	// 參數3: 新進程詳細信息 (僅在創建進程時有效)
	status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)My_LyShark_Com_CreateProcessNotifyEx, FALSE);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("[lyshark.com] 創建進程回調錯誤");
	}
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

編譯並運行這個驅動程序,我們可以在ARK工具中看到這個驅動所加載的CreateProcess的回調事件。

當驅動加載後,如果你嘗試打開lyshark.exe那麼會提示連接的設備沒有發揮作用,我們則成功攔截了這次打開,當然如果在打開進程之前掃描其特徵並根據特徵拒絕進程打開,那麼就可以實現一個簡單的防惡意程序,進程監控在防惡意程序中也是用的最多的。

說完了PsSetCreateProcessNotifyRoutineEx回調的使用方式,LyShark將繼續帶大家看看線程監控如何實現,監控線程創建與監控進程差不多,檢測線程需要調用PsSetCreateThreadNotifyRoutine 創建回調函數,之後就可監控系統所有線程的創建,具體實現代碼如下。

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>

// 兩個未公開函數導出
NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);
NTKERNELAPI NTSTATUS PsLookupThreadByThreadId(HANDLE ThreadId, PETHREAD *Thread);

// 繞過簽名檢查
BOOLEAN BypassCheckSign(PDRIVER_OBJECT pDriverObject)
{
#ifdef _WIN64
	typedef struct _KLDR_DATA_TABLE_ENTRY
	{
		LIST_ENTRY listEntry;
		ULONG64 __Undefined1;
		ULONG64 __Undefined2;
		ULONG64 __Undefined3;
		ULONG64 NonPagedDebugInfo;
		ULONG64 DllBase;
		ULONG64 EntryPoint;
		ULONG SizeOfImage;
		UNICODE_STRING path;
		UNICODE_STRING name;
		ULONG   Flags;
		USHORT  LoadCount;
		USHORT  __Undefined5;
		ULONG64 __Undefined6;
		ULONG   CheckSum;
		ULONG   __padding1;
		ULONG   TimeDateStamp;
		ULONG   __padding2;
	} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#else
	typedef struct _KLDR_DATA_TABLE_ENTRY
	{
		LIST_ENTRY listEntry;
		ULONG unknown1;
		ULONG unknown2;
		ULONG unknown3;
		ULONG unknown4;
		ULONG unknown5;
		ULONG unknown6;
		ULONG unknown7;
		UNICODE_STRING path;
		UNICODE_STRING name;
		ULONG   Flags;
	} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#endif

	PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
	pLdrData->Flags = pLdrData->Flags | 0x20;

	return TRUE;
}

// 線程回調函數
VOID MyCreateThreadNotify(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN CreateInfo)
{
	PEPROCESS eprocess = NULL;
	PETHREAD ethread = NULL;
	UCHAR *pWin32Address = NULL;

	// 通過此函數拿到程序的EPROCESS結構
	PsLookupProcessByProcessId(ProcessId, &eprocess);
	PsLookupThreadByThreadId(ThreadId, &ethread);

	if (CreateInfo)
	{
		DbgPrint("[lyshark.com] 線程TID: %1d | 所屬進程名: %s | 進程PID: %1d \n", ThreadId, PsGetProcessImageFileName(eprocess), PsGetProcessId(eprocess));
		/*
		if (0 == _stricmp(PsGetProcessImageFileName(eprocess), "lyshark.exe"))
		{
		DbgPrint("線程TID: %1d | 所屬進程名: %s | 進程PID: %1d \n", ThreadId, PsGetProcessImageFileName(eprocess), PsGetProcessId(eprocess));

		// dt _kthread
		// 尋找裏面的 Win32StartAddress 並寫入ret
		pWin32Address = *(UCHAR**)((UCHAR*)ethread + 0x1c8);
		if (MmIsAddressValid(pWin32Address))
		{
		*pWin32Address = 0xC3;
		}
		}
		*/
	}
	else
	{
		DbgPrint("[LyShark] %s 線程已退出...", ThreadId);
	}

	if (eprocess)
		ObDereferenceObject(eprocess);
	if (ethread)
		ObDereferenceObject(ethread);
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	NTSTATUS status;

	// 註銷進程回調
	status = PsRemoveCreateThreadNotifyRoutine(MyCreateThreadNotify);
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	NTSTATUS status;

	DbgPrint("hello lyshark.com \n");

	// 繞過簽名檢查
	// LINKER_FLAGS=/INTEGRITYCHECK
	BypassCheckSign(Driver);

	// 創建線程回調
	// 參數1: 新線程ProcessID
	// 參數2: 新線程ThreadID
	// 參數3: 線程創建/退出標誌
	status = PsSetCreateThreadNotifyRoutine(MyCreateThreadNotify);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("創建線程回調錯誤");
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

運行後則可監控到系統總所有線程的創建與退出,效果如下所示: