注意:下面的所有案例必須使用.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, ðread)))
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_PROCESS
或TERMINATE_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
會提示拒絕訪問,說明我們的驅動保護生效了。
關鍵的內核進程騷操作已經分享完了,是不是一臉懵逼十臉茫然?這尼瑪是什么鬼,有啥用?其實這東西用處可大了,殺軟的主動防御系統,游戲的保護系統等都會用到這些東西,還覺得這些東西沒用嗎?