Windows內核基礎知識-8-監聽進程、線程和模塊
Windows內核有一種強大的機制,可以在重大事件發送時得到通知,比如這里的進程、線程和模塊加載通知。
本次采用鏈表+自動快速互斥體來實現內核的主要架構。
進程通知
只要在內核里面注冊了進程通知那么創建進程就會反饋給內核里面。
//注冊/銷毀進程通知函數
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,//回調函數
BOOLEAN Remove//False表示注冊,TRUE表示銷毀
);
PCREATE_PROCESS_NOTIFY_ROUTINE_EX PcreateProcessNotifyRoutineEx;
void PcreateProcessNotifyRoutineEx(
PEPROCESS Process,//得到的進程EPROCESS結構體
HANDLE ProcessId,//得到的進程句柄
PPS_CREATE_NOTIFY_INFO CreateInfo//得到的進程信息,如果是銷毀就是NULL,創建就是一個指針
)
{...}
注意:在用到上述回調函數的驅動必須在PE的PE映像頭里設有IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY標志,可以通過vs中的linker添加命令行:/integritycheck
實現進程通知
創建一個驅動項目,名為SysMon,文件結構圖如下:
AutoLock和FastMutex是用來封裝一個快速互斥體方便和保護多線程訪問同一內容。pch是預編譯頭SysMonCommon.h是給User和Kernel公用的結構體文件,SysMon是驅動主要邏輯的代碼文件。
首先是pch.h和pch.cpp,這個就是一個預編譯頭用來加速編譯速度,預編譯頭只編譯一次,內部用二進制保存下來並用於后面的編譯,這樣可以顯著的加快編譯速度:(就可以把不會變的頭文件直接加進去來提速,但是后面的每一個cpp文件都必須包含pch.h,而頭文件不用,頭文件可以直接用pch的內容)
//pch.h
//pch.cpp
然后是AutoLock和FastMutex,這個在前面
//FastMutex.h
//AutoLock.h
#pragma once
//封裝成一個自動的互斥體
template<typename TLock>
struct AutoLock {
AutoLock(TLock& lock):_lock(lock){
_lock.Lock();
}
~AutoLock()
{
_lock.Unlock();
}
private:
TLock& _lock;
};
//AutoLock.cpp
#include"pch.h"
#include"AutoLock.h"
接着是公用的結構體文件: SysMonCommon.h:
這里我們采用一些正式開發比較常用的辦法:
//添加枚舉類來進行區別響應的事件,這個采用的是C++11的有范圍枚舉(scoped enum)特性
enum class ItemType : short{
None,
ProcessCreate,
ProcessExit
};
//公有的內容就可以設置為一個頭結構體,后面的再繼承它來擴充
struct ItemHeader{
ItemType Type;
USHORT Size;
LARGE_INTEGER Time;//系統的時間類
};
//添加具體的事件信息結構體,退出一個進程沒啥好知道的,知道個退出的進程ID就行
struct ProcessExitInfo : ItemHeader{
ULONG ProcessId;
};
最后是SysMon.h:
//這個頭文件主要用來實現驅動的主要邏輯代碼,因為我們采用鏈表來存儲所有的信息,所以鏈表也要加在這里面
//采用模板類來讓所有的結構體都可以利用鏈表串聯起來而防止編寫很多重復的代碼
template<typename T>
struct FullItem{
LIST_ENTRY entry;
ProcessExitInfo Data;
}
//再建立一個統領全局的全局變量結構體,來存儲所有的信息
//包含了驅動程序的所有全局狀態的數據結構體
struct Globals{
LIST_ENTRY ItemsHead;//鏈表的頭指針
int ItemCount;//事件的個數
FastMutex Mutex;//快速互斥體
}
DriverEntry例程
DriverEntry主要處理的就是建立設備對象,綁定符號鏈接,然后符號鏈接可以給User用,Device給Kernel用,再綁定IRP派遣函數,然后注冊響應通知。
//這里有一些函數可以先添加申明,代碼邏輯后面再講
DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
auto status = STATUS_SUCCESS;
InitializeListHead(&g_Globals.ItemHead);//初始化鏈表
g_Globals.Mutex.Init(); //初始化互斥體
//建立設備對象和符號鏈接
PDEVICE_OBJECT DeviceObject = NULL;
UNICODE_STRING symLinkName = RTL_CONSTANT_STRING(L"\\??\\sysmon");
bool symLinkCreate = FALSE;
do {
UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\sysmon");
status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &DeviceObject);
if (!NT_SUCCESS(status))
{
KdPrint(("failed to create device Error:(0x%08X)",status));
break;
}
DeviceObject->Flags |= DO_DIRECT_IO;//直接IO
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{
KdPrint(("failed to create SymbolcLink Error:(0x%08X)\n",status));
break;
}
symLinkCreate = TRUE;
//注冊進程提醒函數
status = PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, FALSE);
if (!NT_SUCCESS(status))
{
KdPrint(("failed to register process callback (0x%08X)\n",status));
break;
}
if (!NT_SUCCESS(status))
{
if (symLinkCreate)
IoDeleteSymbolicLink(&symLinkName);
if (DeviceObject)
IoDeleteDevice(DeviceObject);
}
DriverObject->DriverUnload = SysMonUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = SysMonCreateClose;
DriverObject->MajorFunction[IRP_MJ_READ] = SysMonRead;
return status;
}
處理進程退出通知
前面講到注冊進程通知函數里面有一個回調函數,這個函數就是用來得到進程響應的信息,不管是進程退出還是創建都可以
//前面在注冊進程提醒函數的時候有用到這條代碼,所以我們需要完善的就是這個回調函數就行:
// status = PsSetCreateProcessNotifyRoutineEx(OnProcessNotify, FALSE);
//前面進程通知的時候有講函數原型,所以這里直接貼代碼:
//PushItem是一個后續會完善的一個函數,用來將內容添加到鏈表里
void OnProcessNotify(PEPROCESS Process,HANDLE ProcessId,PPS_CREATE_NOTIFY_INFO CreateInfo)
{
UNREFERENCED_PARAMETER(Process);
//如果進程被銷毀CreateInfo這個參數為NULL
if (CreateInfo)
{
//進程創建事件獲取內容
}
else
{
//進程退出
//保存退出的進程的ID和事件的公用頭部,ProcessExitInfo是封裝的專門針對退出進程保存的信息結構體,DRIVER_TAG是分配的內存的標簽位。
auto info = (FullItem<ProcessExitInfo>*)ExAllocatePoolWithTag(PagedPool, sizeof(FullItem<ProcessExitInfo>), DRIVER_TAG);
if (info == nullptr)
{
KdPrint(("when process exiting,failed to allocation\n"));
return;
}
//分配成功就開始收集信息
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);//獲取進程時間
item.Type = ItemType::ProcessExit;//設置捕獲的進行信息類型為枚舉類的退出進程
item.ProcessId = HandleToULong(ProcessId);//把句柄轉換為ulong類型(其實是一個)
item.Size = sizeof(ProcessExitInfo);
PushItem(&info->Entry);//將該數據添加到鏈表尾部
}
}
處理進程創建通知
這個其實有了前面的經驗就知道了,只需要在進程響應回調函數里面的if語句中再添加代碼就好了:
void OnProcessNotify(PEPROCESS Process,HANDLE ProcessId,PPS_CREATE_NOTIFY_INFO CreateInfo)
{
UNREFERENCED_PARAMETER(Process);
//如果進程被銷毀CreateInfo這個參數為NULL
if (CreateInfo)
{
//進程創建事件獲取內容
USHORT allocSize = sizeof(FullItem<ProcessCreateInfo>);
USHORT commandLineSize = 0;
if (CreateInfo->CommandLine)//如果有命令行輸入
{
commandLineSize = CreateInfo->CommandLine->Length;
allocSize += commandLineSize;//要分配的內存大小
}
//分配進程創建結構體大小
auto info = (FullItem<ProcessCreateInfo>*)ExAllocatePoolWithTag(PagedPool, allocSize, DRIVER_TAG);
if (info == nullptr)
{
KdPrint(("SysMon: When process is creating,failed to allocate memory"));
return;
}
auto& item = info->Data;
KeQuerySystemTimePrecise(&item.Time);
item.Type = ItemType::ProcessCreate;
item.Size = allocSize;
item.ProcessId = HandleToULong(ProcessId);
item.ParentProcessId = HandleToULong(CreateInfo->ParentProcessId);
if (commandLineSize > 0)
{
::memcpy((UCHAR*)&item+sizeof(item),CreateInfo->CommandLine->Buffer,commandLineSize);//把命令行的內容復制到開辟的內存空間后面
item.CommandLineLength = commandLineSize / sizeof(WCHAR);//以wchar為單位
item.CommandLineOffset = sizeof(item);//從多久開始偏移是命令字符串的首地址
}
else
{
item.CommandLineLength = 0;
item.CommandLineOffset = 0;
}
PushItem(&info->Entry);
}
else
{
//進程退出
//保存退出的進程的ID和事件的公用頭部,ProcessExitInfo是封裝的專門針對退出進程保存的信息結構體,DRIVER_TAG是分配的內存的標簽位。
auto info = (FullItem<ProcessExitInfo>*)ExAllocatePoolWithTag(PagedPool, sizeof(FullItem<ProcessExitInfo>), DRIVER_TAG);