驅動開發:核心監控程序與執行緒回撥

2022-10-23 12:05:35

在前面的文章中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;
}

執行後則可監控到系統總所有執行緒的建立與退出,效果如下所示: