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>),