Windows 驱动开发 新手入门(二)

Windows 驱动开发 新手入门(二)

  • 引言
  • 创建NT式驱动
  • 关于提示无法找到证书
    • 代码分解
      • DEVICE_EXTENSION
      • PsSetCreateProcessNotifyRoutine
      • IRP_MJ_DEVICE_CONTROL派遣函数
      • IO控制码 IoControlCode
  • 创建加载驱动程序

引言

之前的文章Windows 驱动开发 新手入门(一)大概了解的驱动程序的结构和一些主要的函数,本篇我们写一个NT式驱动,用来监控进程的创建与销毁(PsSetCreateProcessNotifyRoutine),如果想直接控制进程是否能创建等,可以使用Ex函数(PsSetCreateProcessNotifyRoutineEx)。

创建NT式驱动

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

关于提示无法找到证书

Windows 驱动开发 新手入门(二)_第1张图片
自己创建一个测试证书,或提供有效证书即可。

代码分解

NT式驱动在驱动入口DriverEntry负责创建设备符号链接.

DEVICE_EXTENSION

这篇文章中我们用到了设备扩展结构,我们可以在IoCreateDevice的第二个参数中看到其大小,这个结构(_DEVICE_EXTENSION)是我们自定义的,用来和应用层进行通信。

PsSetCreateProcessNotifyRoutine

设置创建进程通知的回调为我们自定义的ProcessMonitorCallback,作用是将创建进程的信息写入设备扩展结构,并且通过event事件用来通知应用程序。之后应用程序就可以通过DeviceIoControl传给驱动一个缓冲区,我们可以将设备扩展结构中的信息写入这个缓冲区中,应用程序就可以得知创建进程的信息了。

关于event这里跳过,应该都懂。

IRP_MJ_DEVICE_CONTROL派遣函数

pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL]我们单独设置了一个回调DispatchIoctl,它是用来和应用程序通信的。

DispatchIoctl中,我们用到irp来和进程进行通信。

PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

这行代码我们获取到当前irp指针对应的堆栈地址 pIrpStack,这里需要注意的是将pIrpStackpIrp区分开。

然后我们通过这个堆栈指针获取到设备的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请求。

IO控制码 IoControlCode

在驱动代码最上面我们宏定义了过这个控制码,为#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)&param, 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;
}

你可能感兴趣的:(C/C++)