X64驅動:內核操作進線程/模塊


注意:下面的所有案例必須使用.C結尾的文件,且必須在鏈接選項中加入 /INTEGRITYCHECK 選項,否則編譯根本無法通過(整合修正,Win10可編譯,須在測試模式下進行),內核代碼相對固定,如果對內核編程不太熟的話,建議不要隨意修改代碼,任何一處錯誤的調用都會導致系統藍屏,大佬繞過!

下方所有代碼,均在 Windows 10 LTSC 企業版中測試,經過修改后代碼均無任何問題,放心不會藍屏!

內核枚舉進線程/模塊

內核枚舉進程: 進程就是活動起來的程序,每一個進程在內核里,都有一個名為 EPROCESS 的結構記錄它的詳細信息,其中就包括進程名,PID,PPID,進程路徑等,通常在應用層枚舉進程只列出所有進程的編號即可,不過在內核層需要把它的 EPROCESS 地址給列舉出來。

內核枚舉進程使用PspCidTable 這個未公開的函數,它能最大的好處是能得到進程的EPROCESS地址,由於是未公開的函數,所以我們需要變相的調用這個函數,通過PsLookupProcessByProcessId函數查到進程的EPROCESS,如果PsLookupProcessByProcessId返回失敗,則證明此進程不存在,如果返回成功則把EPROCESS、PID、PPID、進程名等通過DbgPrint打印到屏幕上。

#include <ntifs.h>

NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process); //未公開的進行導出即可
NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);//未公開進行導出

// 根據進程ID返回進程EPROCESS結構體,失敗返回NULL
PEPROCESS LookupProcess(HANDLE Pid)
{
	PEPROCESS eprocess = NULL;
	NTSTATUS Status = STATUS_UNSUCCESSFUL;
	Status = PsLookupProcessByProcessId(Pid, &eprocess);
	if (NT_SUCCESS(Status))
		return eprocess;
	return NULL;
}

VOID EnumProcess()
{
	PEPROCESS eproc = NULL;
	for (int temp = 0; temp < 100000; temp += 4)
	{
		eproc = LookupProcess((HANDLE)temp);
		if (eproc != NULL)
		{
			DbgPrint("進程名: %s --> 進程PID = %d --> 父進程PPID = %d\r\n",PsGetProcessImageFileName(eproc),PsGetProcessId(eproc),
				PsGetProcessInheritedFromUniqueProcessId(eproc));
			ObDereferenceObject(eproc);
		}
	}
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint(("Uninstall Driver Is OK \n"));
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	EnumProcess();
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}


內核終止進程: 結束進程的標准方法就是使用ZwOpenProcess打開進程獲得句柄,然后使用ZwTerminateProcess結束,最后使用ZwClose關閉句柄,代碼如下:

#include <ntifs.h>

NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process);

// 根據進程ID返回進程EPROCESS結構體,失敗返回NULL
PEPROCESS GetProcessNameByProcessId(HANDLE pid)
{
	PEPROCESS ProcessObj = NULL;
	NTSTATUS Status = STATUS_UNSUCCESSFUL;
	Status = PsLookupProcessByProcessId(pid, &ProcessObj);
	if (NT_SUCCESS(Status))
		return ProcessObj;
	return NULL;
}

// 根據ProcessName獲取到進程的PID號
HANDLE GetPidByProcessName(char *ProcessName)
{
	PEPROCESS pCurrentEprocess = NULL;
	HANDLE pid = 0;
	for (int i = 0; i < 1000000000; i += 4)
	{
		pCurrentEprocess = GetProcessNameByProcessId((HANDLE)i);
		if (pCurrentEprocess != NULL)
		{
			pid = PsGetProcessId(pCurrentEprocess);
			if (strstr(PsGetProcessImageFileName(pCurrentEprocess), ProcessName) != NULL)
			{
				ObDereferenceObject(pCurrentEprocess);
				return pid;
			}
			ObDereferenceObject(pCurrentEprocess);
		}
	}
	return (HANDLE)-1;
}

int KillProcess(char *ProcessName)
{
	PEPROCESS pCurrentEprocess = NULL;
	HANDLE pid = 0;
	HANDLE Handle = NULL;
	OBJECT_ATTRIBUTES obj;
	CLIENT_ID cid = { 0 };
	NTSTATUS Status = STATUS_UNSUCCESSFUL;

	for (int i = 0; i < 10000000; i += 4)
	{
		pCurrentEprocess = GetProcessNameByProcessId((HANDLE)i);
		if (pCurrentEprocess != NULL)
		{
			pid = PsGetProcessId(pCurrentEprocess);
			if (strstr(PsGetProcessImageFileName(pCurrentEprocess), ProcessName) != NULL)
			{
				ObDereferenceObject(pCurrentEprocess);
				DbgPrint("已經找到對應的PID,開始執行結束代碼...");
				InitializeObjectAttributes(&obj, NULL, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
				cid.UniqueProcess = (HANDLE)pid;
				cid.UniqueThread = 0;
				Status = ZwOpenProcess(&Handle, GENERIC_ALL, &obj, &cid);
				if (NT_SUCCESS(Status))
				{
					ZwTerminateProcess(Handle, 0);
					ZwClose(Handle);
				}
				ZwClose(Handle);
				return 0;
			}
			ObDereferenceObject(pCurrentEprocess);
		}
	}
	return -1;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint(("Uninstall Driver Is OK \n"));
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	int Retn = 0;
	Retn = KillProcess("calc.exe");
	DbgPrint("結束狀態: %d \n", Retn);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

內核枚舉線程: 內核線程的枚舉與進程相似,線程中也存在一個ETHREAD結構,但在枚舉線程之前需要先來枚舉到指定進程的eprocess結構,然后在根據eprocess結構對指定線程進行枚舉。

#include <ntddk.h>
#include <windef.h>

//聲明API
NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE Id, PEPROCESS *Process);
NTKERNELAPI NTSTATUS PsLookupThreadByThreadId(HANDLE Id, PETHREAD *Thread);
NTKERNELAPI PEPROCESS IoThreadToProcess(PETHREAD Thread);

//根據進程ID返回進程EPROCESS,失敗返回NULL
PEPROCESS LookupProcess(HANDLE Pid)
{
	PEPROCESS eprocess = NULL;
	if (NT_SUCCESS(PsLookupProcessByProcessId(Pid, &eprocess)))
		return eprocess;
	else
		return NULL;
}

//根據線程ID返回線程ETHREAD,失敗返回NULL
PETHREAD LookupThread(HANDLE Tid)
{
	PETHREAD ethread;
	if (NT_SUCCESS(PsLookupThreadByThreadId(Tid, &ethread)))
		return ethread;
	else
		return NULL;
}

//枚舉指定進程中的線程
VOID EnumThread(PEPROCESS Process)
{
	ULONG i = 0, c = 0;
	PETHREAD ethrd = NULL;
	PEPROCESS eproc = NULL;
	for (i = 4; i<262144; i = i + 4) // 一般來說沒有超過100000的PID和TID
	{
		ethrd = LookupThread((HANDLE)i);
		if (ethrd != NULL)
		{
			//獲得線程所屬進程
			eproc = IoThreadToProcess(ethrd);
			if (eproc == Process)
			{
				//打印出ETHREAD和TID
				DbgPrint("線程: ETHREAD=%p TID=%ld\n",ethrd,(ULONG)PsGetThreadId(ethrd));
			}
			ObDereferenceObject(ethrd);
		}
	}
}

// 通過枚舉的方式定位到指定的進程,這里傳遞一個進程名稱
VOID MyEnumThread(char *ProcessName)
{
	ULONG i = 0;
	PEPROCESS eproc = NULL;
	for (i = 4; i<100000000; i = i + 4)
	{
		eproc = LookupProcess((HANDLE)i);
		if (eproc != NULL)
		{
			ObDereferenceObject(eproc);
			if (strstr(PsGetProcessImageFileName(eproc), ProcessName) != NULL)
			{
				EnumThread(eproc);  // 相等則說明是我們想要的進程,直接枚舉其中的線程
			}
		}
	}
}

VOID DriverUnload(IN PDRIVER_OBJECT DriverObject){}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
	MyEnumThread("calc.exe");
	DriverObject->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}


內核枚舉進程模塊: 枚舉進程中的所有模塊信息,DLL模塊記錄在 PEB 的 LDR 鏈表里,LDR 是一個雙向鏈表,枚舉鏈表即可,相應的卸載可使用MmUnmapViewOfSection函數,分別傳入進程的EPROCESS,DLL模塊基址即可。

#include <ntddk.h>
#include <windef.h>

//聲明結構體
typedef struct _KAPC_STATE
{
	LIST_ENTRY ApcListHead[2];
	PKPROCESS Process;
	UCHAR KernelApcInProgress;
	UCHAR KernelApcPending;
	UCHAR UserApcPending;
} KAPC_STATE, *PKAPC_STATE;

typedef struct _LDR_DATA_TABLE_ENTRY
{
	LIST_ENTRY64	InLoadOrderLinks;
	LIST_ENTRY64	InMemoryOrderLinks;
	LIST_ENTRY64	InInitializationOrderLinks;
	PVOID			DllBase;
	PVOID			EntryPoint;
	ULONG			SizeOfImage;
	UNICODE_STRING	FullDllName;
	UNICODE_STRING 	BaseDllName;
	ULONG			Flags;
	USHORT			LoadCount;
	USHORT			TlsIndex;
	PVOID			SectionPointer;
	ULONG			CheckSum;
	PVOID			LoadedImports;
	PVOID			EntryPointActivationContext;
	PVOID			PatchInformation;
	LIST_ENTRY64	ForwarderLinks;
	LIST_ENTRY64	ServiceTagLinks;
	LIST_ENTRY64	StaticLinks;
	PVOID			ContextInformation;
	ULONG64			OriginalBase;
	LARGE_INTEGER	LoadTime;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;


ULONG64 LdrInPebOffset = 0x018;		//peb.ldr
ULONG64 ModListInPebOffset = 0x010;	//peb.ldr.InLoadOrderModuleList

//聲明API
NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process);
NTKERNELAPI PPEB PsGetProcessPeb(PEPROCESS Process);
NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);

//根據進程ID返回進程EPROCESS,失敗返回NULL
PEPROCESS LookupProcess(HANDLE Pid)
{
	PEPROCESS eprocess = NULL;
	if (NT_SUCCESS(PsLookupProcessByProcessId(Pid, &eprocess)))
		return eprocess;
	else
		return NULL;
}

//枚舉指定進程的模塊
VOID EnumModule(PEPROCESS Process)
{
	SIZE_T Peb = 0;
	SIZE_T Ldr = 0;
	PLIST_ENTRY ModListHead = 0;
	PLIST_ENTRY Module = 0;
	ANSI_STRING AnsiString;
	KAPC_STATE ks;
	//EPROCESS地址無效則退出
	if (!MmIsAddressValid(Process))
		return;
	//獲取PEB地址
	Peb = (SIZE_T)PsGetProcessPeb(Process);
	//PEB地址無效則退出
	if (!Peb)
		return;
	//依附進程
	KeStackAttachProcess(Process, &ks);
	__try
	{
		//獲得LDR地址
		Ldr = Peb + (SIZE_T)LdrInPebOffset;
		//測試是否可讀,不可讀則拋出異常退出
		ProbeForRead((CONST PVOID)Ldr, 8, 8);
		//獲得鏈表頭
		ModListHead = (PLIST_ENTRY)(*(PULONG64)Ldr + ModListInPebOffset);
		//再次測試可讀性
		ProbeForRead((CONST PVOID)ModListHead, 8, 8);
		//獲得第一個模塊的信息
		Module = ModListHead->Flink;
		while (ModListHead != Module)
		{
			//打印信息:基址、大小、DLL路徑
			DbgPrint("模塊基址=%p 大小=%ld 路徑=%wZ\n",(PVOID)(((PLDR_DATA_TABLE_ENTRY)Module)->DllBase),
				(ULONG)(((PLDR_DATA_TABLE_ENTRY)Module)->SizeOfImage),&(((PLDR_DATA_TABLE_ENTRY)Module)->FullDllName));
			Module = Module->Flink;
			//測試下一個模塊信息的可讀性
			ProbeForRead((CONST PVOID)Module, 80, 8);
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER){;}
	//取消依附進程
	KeUnstackDetachProcess(&ks);
}

// 通過枚舉的方式定位到指定的進程,這里傳遞一個進程名稱
VOID MyEnumModule(char *ProcessName)
{
	ULONG i = 0;
	PEPROCESS eproc = NULL;
	for (i = 4; i<100000000; i = i + 4)
	{
		eproc = LookupProcess((HANDLE)i);
		if (eproc != NULL)
		{
			ObDereferenceObject(eproc);
			if (strstr(PsGetProcessImageFileName(eproc), ProcessName) != NULL)
			{
				EnumModule(eproc);  // 相等則說明是我們想要的進程,直接枚舉其中的線程
			}
		}
	}
}

VOID DriverUnload(IN PDRIVER_OBJECT DriverObject){}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
	MyEnumModule("calc.exe");
	DriverObject->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}


內核枚舉加載SYS文件: 內核中的SYS文件也是通過雙向鏈表的方式相連接的,我們可以通過遍歷LDR_DATA_TABLE_ENTRY結構(遍歷自身DriverSection成員),就能夠得到全部的模塊信息。

#include <ntddk.h>
#include <wdm.h>

typedef struct _LDR_DATA_TABLE_ENTRY {
	LIST_ENTRY InLoadOrderLinks;
	LIST_ENTRY InMemoryOrderLinks;
	LIST_ENTRY InInitializationOrderLinks;
	PVOID DllBase;
	PVOID EntryPoint;
	ULONG SizeOfImages;
	UNICODE_STRING FullDllName;
	UNICODE_STRING BaseDllName;
	ULONG Flags;
	USHORT LoadCount;
	USHORT TlsIndex;
	union {
		LIST_ENTRY HashLinks;
		struct {
			PVOID SectionPointer;
			ULONG CheckSum;
		};
	};
	union {
		struct {
			ULONG TimeDateStamp;
		};
		struct {
			PVOID LoadedImports;
		};
	};
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

VOID DriverUnload(IN PDRIVER_OBJECT DriverObject){}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	ULONG count = 0;
	NTSTATUS Status;
	DriverObject->DriverUnload = DriverUnload;
	
	PLDR_DATA_TABLE_ENTRY pLdr = NULL;
	PLIST_ENTRY pListEntry = NULL;
	PLDR_DATA_TABLE_ENTRY pModule = NULL;
	PLIST_ENTRY pCurrentListEntry = NULL;

	pLdr = (PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection;
	pListEntry = pLdr->InLoadOrderLinks.Flink;
	pCurrentListEntry = pListEntry->Flink;

	while (pCurrentListEntry != pListEntry)
	{
		pModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
		if (pModule->BaseDllName.Buffer != 0)
		{
			DbgPrint("基址:%p ---> 偏移:%p ---> 結束地址:%p---> 模塊名:%wZ \r\n", pModule->DllBase, pModule->SizeOfImages - (LONGLONG)pModule->DllBase, 
				(LONGLONG)pModule->DllBase + pModule->SizeOfImages,pModule->BaseDllName);
		}
		pCurrentListEntry = pCurrentListEntry->Flink;
	}
	DriverObject->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}


### 監控進程與線程創建

監控進程的啟動與退出可以使用 PsSetCreateProcessNotifyRoutineEx 來創建回調,當新進程產生時,回調函數會被率先執行,然后執行我們自己的MyCreateProcessNotifyEx函數,並在內部進行打印輸出。

#include <ntddk.h>

NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);

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;
}

VOID MyCreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
	char ProcName[16] = { 0 };
	if (CreateInfo != NULL)
	{
		strcpy(ProcName, PsGetProcessImageFileName(Process));
		DbgPrint("父進程ID: %ld  --->父進程名: %s --->進程名: %s---->進程路徑:%wZ", CreateInfo->ParentProcessId,
			GetProcessNameByProcessId(CreateInfo->ParentProcessId),
			PsGetProcessImageFileName(Process),CreateInfo->ImageFileName);
	}
	else
	{
		strcpy(ProcName, PsGetProcessImageFileName(Process));
		DbgPrint("進程[ %s ] 離開了,程序被關閉了",ProcName);
	}
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, TRUE);
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	NTSTATUS status;
	status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, FALSE);
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

在上方代碼基礎上進行一定的改進,思路:通過PsGetProcessImageFileName即將PID轉換為進程名,然后通過_stricmp對比,如果發現是calc.exe進程則拒絕執行,禁止特定服務的運行,實現代碼如下:

#include <ntddk.h>

NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);

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;
}

VOID MyCreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
	char ProcName[16] = { 0 };
	if (CreateInfo != NULL)
	{
		strcpy(ProcName, PsGetProcessImageFileName(Process));
		if (!_stricmp(ProcName, "calc.exe"))
		{
			CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;
		}
	}
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, TRUE);
	DbgPrint(("驅動卸載成功"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	NTSTATUS status;
	status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, FALSE);
	Driver->DriverUnload = UnDriver;
	DbgPrint("驅動加載成功!");
	return STATUS_SUCCESS;
}

將上方代碼編譯,當我們加載驅動程序以后,再次打開C:\Windows\System32\calc.exe 計算器進程則提示無法打開,我們的驅動已經成功的攔截了本次的請求。

而檢測線程操作與檢測進程差不多,檢測線程需要調用PsSetCreateThreadNotifyRoutine 創建回調函數,然后就可以檢測線程的創建了,具體代碼如下:

#include <ntddk.h>

NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);

VOID MyCreateThreadNotify(HANDLE  ProcessId, HANDLE  ThreadId, BOOLEAN  Create)
{
	PEPROCESS eprocess = NULL;
	PsLookupProcessByProcessId(ProcessId, &eprocess);                // 通過此函數拿到程序的EPROCESS結構
	if (Create)
		DbgPrint("線程TID: %1d --> 所屬進程名: %s --> 進程PID: %1d \n", ThreadId, PsGetProcessImageFileName(eprocess), PsGetProcessId(eprocess));
	else
		DbgPrint("%s 線程已退出...", ThreadId);
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
	PsRemoveCreateThreadNotifyRoutine(MyCreateThreadNotify);
	DbgPrint(("驅動卸載成功"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	NTSTATUS status;
	status = PsSetCreateThreadNotifyRoutine(MyCreateThreadNotify);
	DbgPrint("PsSetCreateThreadNotifyRoutine: %x", status);
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}


### 監控進程與線程對象操作

監控進程對象和線程對象操作,可以使用ObRegisterCallbacks這個內核回調函數,通過回調我們可以實現保護calc.exe進程不被關閉,具體操作從OperationInformation->Object獲得進程或線程的對象,然后再回調中判斷是否是計算器,如果是就直接去掉TERMINATE_PROCESSTERMINATE_THREAD權限即可,附上進程監控回調的寫法:

#include <ntddk.h>
#include <ntstrsafe.h>

PVOID Globle_Object_Handle;

OB_PREOP_CALLBACK_STATUS MyObjectCallBack(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation)
{
	DbgPrint("執行了我們的回調函數...");
	return STATUS_SUCCESS;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
	ObUnRegisterCallbacks(Globle_Object_Handle);
	DbgPrint("回調卸載完成...");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	OB_OPERATION_REGISTRATION Base;                          // 回調函數結構體(你所填的結構都在這里)
	OB_CALLBACK_REGISTRATION CallbackReg;

	CallbackReg.RegistrationContext = NULL;                  // 注冊上下文(你回調函數返回參數)
	CallbackReg.Version = OB_FLT_REGISTRATION_VERSION;       // 注冊回調版本
	CallbackReg.OperationRegistration = &Base;
	CallbackReg.OperationRegistrationCount = 1;               // 操作計數(下鈎數量)
	RtlUnicodeStringInit(&CallbackReg.Altitude, L"600000");   // 長度
	Base.ObjectType = PsProcessType;                          // 進程操作類型.此處為進程操作
	Base.Operations = OB_OPERATION_HANDLE_CREATE;             // 操作句柄創建
	Base.PreOperation = MyObjectCallBack;                     // 你自己的回調函數
	Base.PostOperation = NULL;

	if (ObRegisterCallbacks(&CallbackReg, &Globle_Object_Handle)) // 注冊回調
		DbgPrint("回調注冊成功...");
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

上方代碼運行后,我們可以打開Xuetr掃描一下內核Object鈎子,可以看到已經成功掛鈎了。

檢測計算器進程的關閉狀態,代碼如下:

#include <ntddk.h>
#include <wdm.h>
#include <ntstrsafe.h>
#define PROCESS_TERMINATE 1

PVOID Globle_Object_Handle;
NTKERNELAPI UCHAR * PsGetProcessImageFileName(__in PEPROCESS Process);

char* GetProcessImageNameByProcessID(ULONG ulProcessID)
{
	NTSTATUS  Status;
	PEPROCESS  EProcess = NULL;
	Status = PsLookupProcessByProcessId((HANDLE)ulProcessID, &EProcess);
	if (!NT_SUCCESS(Status))
		return FALSE;
	ObDereferenceObject(EProcess);
	return (char*)PsGetProcessImageFileName(EProcess);
}
OB_PREOP_CALLBACK_STATUS MyObjectCallBack(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION Operation)
{
	char ProcName[256] = { 0 };
	HANDLE pid = PsGetProcessId((PEPROCESS)Operation->Object);           // 取出當前調用函數的PID
	strcpy(ProcName, GetProcessImageNameByProcessID((ULONG)pid));        // 通過PID取出進程名,然后直接拷貝內存
	//DbgPrint("當前進程的名字是:%s", ProcName);

	if (strstr(ProcName, "win32calc.exe"))
	{
		if (Operation->Operation == OB_OPERATION_HANDLE_CREATE)
		{
			if ((Operation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_TERMINATE) == PROCESS_TERMINATE)
			{
				DbgPrint("你想結束進程?");
				// 如果是計算器,則去掉它的結束權限,在Win10上無效
				Operation->Parameters->CreateHandleInformation.DesiredAccess = ~THREAD_TERMINATE;
				return STATUS_UNSUCCESSFUL;
			}
		}
	}
	return STATUS_SUCCESS;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
	ObUnRegisterCallbacks(Globle_Object_Handle);
	DbgPrint("回調卸載完成...");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	NTSTATUS obst = 0;
	OB_CALLBACK_REGISTRATION obReg;
	OB_OPERATION_REGISTRATION opReg;

	memset(&obReg, 0, sizeof(obReg));
	obReg.Version = ObGetFilterVersion();
	obReg.OperationRegistrationCount = 1;
	obReg.RegistrationContext = NULL;
	RtlInitUnicodeString(&obReg.Altitude, L"321125");
	obReg.OperationRegistration = &opReg;
	memset(&opReg, 0, sizeof(opReg));
	opReg.ObjectType = PsProcessType;
	opReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
	opReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)&MyObjectCallBack;
	obst = ObRegisterCallbacks(&obReg, &Globle_Object_Handle);
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

首先運行計算器,然后啟動驅動保護,此時我們在任務管理器中就無法結束計算器進程了。


### 監控進程中模塊加載

系統中的模塊加載包括用戶層模塊DLL和內核模塊SYS的加載,在 Windows X64 環境下我們可以調用 PsSetLoadImageNotifyRoutine內核函數來設置一個映像加載通告例程,當有驅動或者DLL被加載時,回調函數就會被調用從而執行我們自己的回調例程。

#include <ntddk.h>
#include <ntimage.h>

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;
}

VOID MyLoadImageNotifyRoutine(PUNICODE_STRING FullImageName,HANDLE ProcessId,PIMAGE_INFO ImageInfo)
{
	PVOID pDrvEntry;
	if (FullImageName != NULL && MmIsAddressValid(FullImageName)) // MmIsAddress 驗證地址可用性
	{
		if (ProcessId == 0)
		{
			pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
			DbgPrint("模塊名稱:%wZ --> 裝載基址:%p --> 鏡像長度: %d", FullImageName, pDrvEntry,ImageInfo->ImageSize);
		}
	}
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLoadImageNotifyRoutine);
	DbgPrint("驅動卸載完成...");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLoadImageNotifyRoutine);
	DbgPrint("驅動加載完成...");
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

接着我們給上方的代碼加上判斷功能,只需在上方代碼的基礎上小改一下即可,需要注意回調函數中的第二個參數,如果返回值為零則表示加載SYS,如果返回非零則表示加載DLL

VOID UnicodeToChar(PUNICODE_STRING dst, char *src)
{
	ANSI_STRING string;
	RtlUnicodeStringToAnsiString(&string, dst, TRUE);
	strcpy(src, string.Buffer);
	RtlFreeAnsiString(&string);
}

VOID MyLoadImageNotifyRoutine(PUNICODE_STRING FullImageName,HANDLE ModuleStyle,PIMAGE_INFO ImageInfo)
{
	PVOID pDrvEntry;
	char szFullImageName[256] = { 0 };
	if (FullImageName != NULL && MmIsAddressValid(FullImageName)) // MmIsAddress 驗證地址可用性
	{
		if (ModuleStyle == 0)  // ModuleStyle為零表示加載sys非零表示加載DLL
		{
			pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
			UnicodeToChar(FullImageName, szFullImageName);
			if (strstr(_strlwr(szFullImageName), "hook.sys"))
			{
				DbgPrint("准備攔截SYS內核模塊:%s", _strlwr(szFullImageName));
			}
		}
	}
}

上方代碼就可以判斷加載的模塊並作出處理動作了,但是我們仍然無法判斷到底是那個進程加載的hook.sys驅動,因為回調函數很底層,到了一定的深度之后就無法判斷到底是誰主動引發的行為了,一切都是系統的行為。

判斷了是驅動后,接着我們就要實現屏蔽驅動,通過ImageInfo->ImageBase 來獲取被加載驅動程序hook.sys的映像基址,然后找到NT頭的OptionalHeader節點,該節點里面就是被加載驅動入口的地址,通過匯編在驅動頭部寫入ret返回指令,即可實現屏蔽加載特定驅動文件。

#include <ntddk.h>
#include <intrin.h>
#include <ntimage.h>

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;
}
VOID UnicodeToChar(PUNICODE_STRING dst, char *src)
{
	ANSI_STRING string;
	RtlUnicodeStringToAnsiString(&string, dst, TRUE);
	strcpy(src, string.Buffer);
	RtlFreeAnsiString(&string);
}
// 使用開關寫保護需要在 C/C++ 優化中啟用內部函數
KIRQL  WPOFFx64()         // 關閉寫保護
{
	KIRQL  irql = KeRaiseIrqlToDpcLevel();
	UINT64  cr0 = __readcr0();
	cr0 &= 0xfffffffffffeffff;
	_disable();
	__writecr0(cr0);
	return  irql;
}
void  WPONx64(KIRQL  irql) // 開啟寫保護
{
	UINT64  cr0 = __readcr0();
	cr0 |= 0x10000;
	_enable();
	__writecr0(cr0);
	KeLowerIrql(irql);
}

BOOLEAN DenyLoadDriver(PVOID DriverEntry)
{
	UCHAR fuck[] = "\xB8\x22\x00\x00\xC0\xC3";
	KIRQL kirql;
	/* 在模塊開頭寫入以下匯編指令
	Mov eax,c0000022h
	ret
	*/
	if (DriverEntry == NULL) return FALSE;
	kirql = WPOFFx64();
	memcpy(DriverEntry, fuck,sizeof(fuck) / sizeof(fuck[0]));
	WPONx64(kirql);
	return TRUE;
}

VOID MyLoadImageNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ModuleStyle, PIMAGE_INFO ImageInfo)
{
	PVOID pDrvEntry;
	char szFullImageName[256] = { 0 };
	if (FullImageName != NULL && MmIsAddressValid(FullImageName)) // MmIsAddress 驗證地址可用性
	{
		if (ModuleStyle == 0)  // ModuleStyle為零表示加載sys非零表示加載DLL
		{
			pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
			UnicodeToChar(FullImageName, szFullImageName);
			if (strstr(_strlwr(szFullImageName), "hook.sys"))
			{
				DbgPrint("攔截SYS內核模塊:%s", szFullImageName);
				DenyLoadDriver(pDrvEntry);
			}
		}
	}
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
	PsRemoveLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLoadImageNotifyRoutine);
	DbgPrint("驅動卸載完成...");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	PsSetLoadImageNotifyRoutine((PLOAD_IMAGE_NOTIFY_ROUTINE)MyLoadImageNotifyRoutine);
	DbgPrint("驅動加載完成...");
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

屏蔽DLL加載,只需要在上面的代碼上稍微修改一下就好,這里提供到另一種寫法。

char *UnicodeToLongString(PUNICODE_STRING uString)
{
	ANSI_STRING asStr;
	char *Buffer = NULL;;
	RtlUnicodeStringToAnsiString(&asStr, uString, TRUE);
	Buffer = ExAllocatePoolWithTag(NonPagedPool, uString->MaximumLength * sizeof(wchar_t), 0);
	if (Buffer == NULL)
		return NULL;
	RtlCopyMemory(Buffer, asStr.Buffer, asStr.Length);
	return Buffer;
}
VOID MyLoadImageNotifyRoutine(PUNICODE_STRING FullImageName, HANDLE ModuleStyle, PIMAGE_INFO ImageInfo)
{
	PVOID pDrvEntry;
	char *PareString = NULL;

	if (MmIsAddressValid(FullImageName))
	{
		if (ModuleStyle != 0)  // 非零則監控DLL加載
		{
			PareString = UnicodeToLongString(FullImageName);
			if (PareString != NULL)
			{
				if (strstr(PareString, "hook.dll"))
				{
					pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase);
					if (pDrvEntry != NULL)
						DenyLoadDriver(pDrvEntry);
				}
			}
		}
	}
}

我們以屏蔽SYS內核模塊為例,當驅動文件WinDDK.sys被加載后,嘗試加載hook.sys會提示拒絕訪問,說明我們的驅動保護生效了。


關鍵的內核進程騷操作已經分享完了,是不是一臉懵逼十臉茫然?這尼瑪是什么鬼,有啥用?其實這東西用處可大了,殺軟的主動防御系統,游戲的保護系統等都會用到這些東西,還覺得這些東西沒用嗎?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM