之前的文章Windows 驱动开发 新手入门(一)大概了解的驱动程序的结构和一些主要的函数,本篇我们写一个NT式驱动,用来监控进程的创建与销毁(PsSetCreateProcessNotifyRoutine)
,如果想直接控制进程是否能创建等,可以使用Ex
函数(PsSetCreateProcessNotifyRoutineEx
)。
由于VS2019中无法帮我们快速创建NT式驱动,我们使用上一篇文章的项目改造一下即可,删除cpp文件,创建一个ProcessMonitor.c
文件,老样子细节我会在下面单说。
#include
#include
//定义驱动名称,符号链接名称,事件对象名称
#define DEVICE_NAME L"\\Device\\devMonitorProc"
#define LINK_NAME L"\\??\\slinkMonitorProc" // ??代表以前的DosDevices
#define EVENT_NAME L"\\BaseNamedObjects\\MonitorProcEvent" //event对象名称
//缓冲区方式,因为MDL方式不安全
#define IOCTL_NTPROCDRV_GET_PROCINFO CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
typedef struct _CallbackInfo
{
HANDLE hParentId;
HANDLE hProcessId;
BOOLEAN bCreate;
}CallbackInfo, *pCallbackInfo;
//设备对象扩展结构,存放一些必要信息
typedef struct _DEVICE_EXTENSION
{
HANDLE hEventHandle;//事件对象句柄
PKEVENT pEvent;// 用于和内核通信事件的对象指针
HANDLE hPParentId; //在回调函数中保存进程信息,传递给用户层
HANDLE hPProcessId;
BOOLEAN bPCreate; //true表示进程被创建,false表示进程被删除
}DEVICE_EXTENSION,*PDEVICE_EXTENSION;
//定义全局的设备对象指针
PDEVICE_OBJECT g_pDeviceObject;
//直接返回完成信息
NTSTATUS DispatchCreateClose(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS;
//完成此请求
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//I/O派遣例程处理
NTSTATUS DispatchIoctl(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("monitorProc : DispatchIotcl...\n");
//默认返回失败
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
//取得此IRP的I/O堆栈指针
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
//取得设备扩展结构指针
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
//获取I/O控制代码
ULONG uIoContrlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
//取得I/O缓冲区的指针和其长度
pCallbackInfo pCallbackInfoBuff = (pCallbackInfo)pIrp->AssociatedIrp.SystemBuffer;
ULONG uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;//输入缓冲区长度
ULONG uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;//输出缓冲区长度
switch(uIoContrlCode)
{
case IOCTL_NTPROCDRV_GET_PROCINFO: //想用户程序返回有事件发生的进程信息
{
if(uOutSize >= sizeof(CallbackInfo)) //输出缓冲区长度要大于返回结构大小
{
pCallbackInfoBuff->hParentId = pDevExt->hPParentId;
pCallbackInfoBuff->hProcessId = pDevExt->hPProcessId;
pCallbackInfoBuff->bCreate = pDevExt->bPCreate;
status = STATUS_SUCCESS;
}
DbgPrint("dispatch pCallbackInfoBuff->hProcessId = %d\n",pCallbackInfoBuff->hProcessId);
}
break;
}
if(status == STATUS_SUCCESS)
pIrp->IoStatus.Information = uOutSize;
else
pIrp->IoStatus.Information = 0;
//完成请求
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return status;
}
//进程监视回调函数
VOID ProcessMonitorCallback(
IN HANDLE hParentId,
IN HANDLE hProcessId,
IN BOOLEAN bCreate)
{
//得到设备扩展的结构指针
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)g_pDeviceObject->DeviceExtension;
//处理当前的设备扩展结构,传给应用层
pDevExt->hPParentId = hParentId;
pDevExt->hPProcessId = hProcessId;
pDevExt->bPCreate = bCreate;
DbgPrint("hProcessId = %d\n",hProcessId);
//触发,通知应用层监听程序,第三个参数是 是否等待,如果TRUE,需要驱动调用kewait
KeSetEvent(pDevExt->pEvent,0,FALSE);
//设置为非授信状态
KeClearEvent(pDevExt->pEvent);
}
//驱动卸载函数
void DriverUnload(PDRIVER_OBJECT pDriveObj)
{
//移除通知函数
PsSetCreateProcessNotifyRoutine(ProcessMonitorCallback,TRUE);
//关闭event
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)g_pDeviceObject->DeviceExtension;
KeClearEvent(pDevExt->pEvent);
ZwClose(pDevExt->hEventHandle);
//删除设备连接符号
UNICODE_STRING strLink;
RtlInitUnicodeString(&strLink,LINK_NAME);
IoDeleteSymbolicLink(&strLink);
IoDeleteDevice(pDriveObj->DeviceObject); //删除设备对象
DbgPrint("driver unloaded ...\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegisterString)
{
NTSTATUS status = STATUS_SUCCESS;
//初始化各个历程
pDriverObj->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
pDriverObj->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
pDriverObj->DriverUnload = DriverUnload;
//创建,并初始化设备对象
UNICODE_STRING strDevName;
RtlInitUnicodeString(&strDevName,DEVICE_NAME);
//创建设备对象
PDEVICE_OBJECT pDevObj;
status = IoCreateDevice(
pDriverObj,
sizeof(DEVICE_EXTENSION),//为设备扩展结构申请空间
&strDevName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&pDevObj
);
if(!NT_SUCCESS(status))
{
return status;
}
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
//创建符号链接
UNICODE_STRING strLinkName;
RtlInitUnicodeString(&strLinkName,LINK_NAME);
//创建 关联
status = IoCreateSymbolicLink(&strLinkName,&strDevName);
if(!NT_SUCCESS(status))
{
IoDeleteDevice(pDevObj);
return status;
}
//将设备指针保存到全局变量中,方便后续使用
g_pDeviceObject = pDevObj;
//为了能够监视用户层进程,创建事件对象
UNICODE_STRING szpEventString;
RtlInitUnicodeString(&szpEventString,EVENT_NAME);
//创建一个event对象
pDevExt->pEvent = IoCreateNotificationEvent(&szpEventString,&pDevExt->hEventHandle);
//设置非授信状态
KeClearEvent(pDevExt->pEvent);
//设置回调函数历程
status = PsSetCreateProcessNotifyRoutine(ProcessMonitorCallback,FALSE);
return status;
}
NT式驱动在驱动入口DriverEntry
负责创建设备
和符号链接
.
这篇文章中我们用到了设备扩展结构
,我们可以在IoCreateDevice
的第二个参数中看到其大小,这个结构(_DEVICE_EXTENSION
)是我们自定义的,用来和应用层进行通信。
设置创建进程通知的回调为我们自定义的ProcessMonitorCallback
,作用是将创建进程的信息
写入设备扩展结构
,并且通过event
事件用来通知应用程序
。之后应用程序就可以通过DeviceIoControl
传给驱动
一个缓冲区,我们可以将设备扩展结构中的信息写入这个缓冲区中,应用程序就可以得知创建进程的信息了。
关于event这里跳过,应该都懂。
pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL]
我们单独设置了一个回调DispatchIoctl
,它是用来和应用程序通信的。
在DispatchIoctl
中,我们用到irp
来和进程进行通信。
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
这行代码我们获取到当前irp指针
对应的堆栈地址 pIrpStack
,这里需要注意的是将pIrpStack
和pIrp
区分开。
然后我们通过这个堆栈指针
获取到设备的IO控制码
。
//获取I/O控制代码
ULONG uIoContrlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
在代码中判断如果
IO控制码
为宏定义的IOCTL_NTPROCDRV_GET_PROCINFO
,那么我们将驱动获取到的信息写入pIrp->AssociatedIrp.SystemBuffer
,这个pIrp->AssociatedIrp.SystemBuffer
实际上是应用程序
和设备通信
时,发送的缓冲区地址,我们修改内容后应用程序的缓冲区内容就会被修改。
之后将pIrp->IoStatus.Information
设为写出内容的大小,然后设置pIrp->IoStatus.Status
后调用IoCompleteRequest
完成IO请求。
在驱动代码最上面我们宏定义了过这个控制码,为#define IOCTL_NTPROCDRV_GET_PROCINFO CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
,其中0x800
为自定义,只要不冲突即可,METHOD_BUFFERED
为IO方式。
这是一个正常加载驱动
的控制台程序
,关于APC
的东西如果看不懂可以略过
,我只是为了正常移除驱动服务。
我们程序中主要做的就是加载驱动
后,通过监听event
,来判断驱动是否已经
将信息写入到设备扩展结构了
,如果写入完成,我们通过DeviceIoControl
让驱动将设备扩展结构
的信息写入我们应用程序
能读取的内存地址
中。
// 载入驱动.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include
#include
#include
#include
#include
//定义IO控制码
#define IOCTL_NTPROCDRV_GET_PROCINFO CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
//有缓冲区模式和直接模式
//#define IOCTL_DO_BUFFER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
//#define IOCTL_IN_DIRECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
//对应驱动里的CALLBACK结构
typedef struct _CALL_BACK
{
HANDLE hParentId;
HANDLE hProcessId;
BOOLEAN bCreate;
}CALLBACKINFO, *PCALLBACKINFO;
typedef struct _Param {
HANDLE pProcessEvent;
HANDLE pDevice;
HANDLE hEvent;
}PARAM, *PPARAM;
HANDLE threadHandle;//用来接收驱动消息的线程
unsigned threadWork(LPVOID param) {
PPARAM pParam = (PPARAM)param;
CALLBACKINFO callbackInfo, callbackTemp = {
0 };
UINT waitState;
do {
DWORD dw = WaitForSingleObjectEx(pParam->hEvent, 100, TRUE);
if (dw == WAIT_IO_COMPLETION) {
CloseHandle(pParam->pProcessEvent);
CloseHandle(pParam->pDevice);
return 1;
}
//等待驱动 通知创建进程事件触发
waitState = WaitForSingleObject(pParam->pProcessEvent, INFINITE);
if (waitState == WAIT_OBJECT_0) {
DWORD nBytesReturn;
//发送callbackInfo地址,让驱动将进程信息写进来,我们就能在应用层读取创建进程的信息了
BOOL bRet = DeviceIoControl(pParam->pDevice, IOCTL_NTPROCDRV_GET_PROCINFO, NULL, 0, &callbackInfo, sizeof(callbackInfo), &nBytesReturn, NULL);
if (bRet)
{
if (callbackInfo.hParentId != callbackTemp.hParentId || callbackInfo.hProcessId != callbackTemp.hProcessId || callbackInfo.bCreate != callbackTemp.bCreate) {
if (callbackInfo.bCreate)
printf_s("有进程被创建了 PID:%d,父进程 PID:%d\n", callbackInfo.hProcessId, callbackInfo.hParentId);
else
printf_s("有进程被终止了 PID:%d,父进程 PID:%d\n", callbackInfo.hProcessId, callbackInfo.hParentId);
callbackTemp = callbackInfo;
}
}
else {
printf_s("获取进程信息失败 %d %d\n", bRet, nBytesReturn);
}
}
else {
printf_s("wait state: 0x%X\n", waitState);
}
} while (waitState == WAIT_OBJECT_0);
CloseHandle(pParam->pProcessEvent);
CloseHandle(pParam->pDevice);
return 1;
}
void WINAPI apcFunc(ULONG_PTR dwParam) {
//Nothing to do in here
}
void onExit(){
if (threadHandle!=INVALID_HANDLE_VALUE)
QueueUserAPC(apcFunc, threadHandle, NULL);//提醒主线程回收
}
BOOL WINAPI handlerRoutine(DWORD dwCtrlType) {
char mesg[128];
_itoa_s(dwCtrlType, mesg, 128, 10);
switch (dwCtrlType)
{
case CTRL_C_EVENT:// 当用户按下了CTRL + C, 或者由GenerateConsoleCtrlEvent API发出.
QueueUserAPC(apcFunc, threadHandle, NULL);
MessageBoxA(NULL, "CTRL_C received!", "CEvent", MB_OK);
break;
case CTRL_CLOSE_EVENT://当试图关闭控制台程序,系统发送关闭消息。
QueueUserAPC(apcFunc, threadHandle, NULL);
MessageBoxA(NULL, "CLOSE received!", "CEvent", MB_OK);
break;
case CTRL_LOGOFF_EVENT:// 用户退出时,但是不能决定是哪个用户.
QueueUserAPC(apcFunc, threadHandle, NULL);
MessageBoxA(NULL, "LOGOFF received!", "CEvent", MB_OK);
break;
case CTRL_SHUTDOWN_EVENT://当系统被关闭时.
QueueUserAPC(apcFunc, threadHandle, NULL);
MessageBoxA(NULL, "SHUTDOWN received!", "CEvent", MB_OK);
break;
default:
break;
}
return 1;
}
int main()
{
TCHAR szDriverPath[MAX_PATH];
TCHAR szLinkName[] = _T("slinkMonitorProc");
TCHAR * p;
//可能是目录问题 造成乱码
GetFullPathName(_T("NT驱动进程监控.sys"), MAX_PATH, szDriverPath, &p);
//由于签名问题,无法这样创建,所以我使用调试工具
//打开SCM管理器
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
return -1;
//创建或打开服务
SC_HANDLE hService = CreateService(hSCM, szLinkName, szLinkName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, szDriverPath, NULL, 0, NULL, NULL, NULL);
if (hService == NULL)
{
int nError = GetLastError();
if (nError == ERROR_SERVICE_MARKED_FOR_DELETE) {
printf_s("指定的服务已经被标志为删除...\n");
return -1;
}
if (nError == ERROR_SERVICE_EXISTS) {
printf_s("服务已存在,正在开启...\n");
hService = OpenService(hSCM, szLinkName, SERVICE_ALL_ACCESS);
if (hService == NULL) {
printf_s("服务开启失败...\n");
return -1;
}
}
}else{
printf_s("服务创建成功!\n");
}
//启动服务
if (!StartService(hService,0, NULL))//这里调用DriverEntry
{
int nError = GetLastError();
if (nError != ERROR_SERVICE_ALREADY_RUNNING) {
printf_s("启动服务出错!%d\n", nError);
DeleteService(hService);
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
system("pause");
return -1;
}
}
//打开驱动程序所控制的设备句柄
TCHAR sz[256];
_sntprintf_s(sz, 256, TEXT("\\\\.\\%s"), szLinkName);//就是\\.\linkName
HANDLE hDevice = CreateFile(sz, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
printf_s("打开设备失败!%d\n", GetLastError());
system("pause");
return -1;
}
//打开事件内核对象
HANDLE hProcessEvent = OpenEvent(SYNCHRONIZE,FALSE, _T("Global\\MonitorProcEvent"));
if (hProcessEvent==INVALID_HANDLE_VALUE) {
printf_s("获取event handle 出错\n");
CloseHandle(hProcessEvent);
CloseHandle(hDevice);
SERVICE_STATUS ss;
ControlService(hService, SERVICE_CONTROL_STOP, &ss);
DeleteService(hService);
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
printf_s("程序结束\n");
system("pause");
return 0;
}
PARAM param;
param.pProcessEvent = hProcessEvent;
param.pDevice = hDevice;
param.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
printf("registering at exit callback\n");
atexit(onExit);
SetConsoleCtrlHandler(handlerRoutine, TRUE);
threadHandle = (HANDLE)_beginthreadex(NULL, 0, threadWork, (LPVOID)¶m, 0, NULL);
WaitForSingleObjectEx(threadHandle, INFINITE, true);
//由于签名问题 我使用调试的方法 不创建服务
//等待服务完全停止运行
SERVICE_STATUS ss;
ControlService(hService, SERVICE_CONTROL_STOP, &ss);
//从SCM数据库中移除服务
DeleteService(hService);
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
printf_s("程序结束\n");
system("pause");
return 0;
}