DSP

驱动层与应用层的事件同步(主动防御原理浅析)

2019-07-13 19:46发布

在电脑上,大家都装有各种各样的防护软件,也习惯了防护软件弹出的各种提示框, 很多人会好奇,这些软件是如何在危险程序运行前就拦截到关键行为,并弹出提示框的呢?今天,我在这里简单讲解一下实现的原理,即驱动层与应用层的事件同步。下面切入正题。
要实现驱动层与应用层的事件同步, 主要经历下几个步骤: 1、在应用层使用CreateEvent创建事件(自动重置类型或手动重置类型); 2、将事件句柄发送到驱动程序中。 3、应用层使用WaitForSingleObject或WaitForMultipleObjects等待事件。 4、驱动层拦截到关键事件,使用KeSetEvent将事件设置为有信号。 5、应用层等待到事件信号,向驱动层发送控制消息,获取信息。 6、弹出消息提示用户。 7、设置事件为无信号,等待下一次通知。
下面,我用一个监测系统进程创建的简单例子, 简单说明一下实现的步骤。
首先,先从应用层开始。 打开VisualStudio, 创建一个基于对话框的MFC应用程序, 在界面上添加两个按钮,如图:


添加一个全局的事件对象句柄: HANDLE g_hKernelEvent = NULL;
在对话框的OnInitDialog()方法中,创建手动重置的事件对象。 //创建手动重置的事件 g_hKernelEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
为启动按钮添加处理代码: void CKernelHanleDemoDlg::OnBnClickedBtnStart() { DWORD dwRet; //将创建的事件句柄传入驱动程序 BOOL bRet = IOControl(IOCTL_START, &g_hKernelEvent, sizeof(g_hKernelEvent), NULL, 1024, &dwRet); //正在运行 g_bIsRunning = TRUE; //创建监听线程 HANDLE hThread = CreateThread(NULL, 0, KernelHandleThread, NULL, 0, NULL); CloseHandle(hThread); Sleep(1); }
为停止按钮添加处理代码: void CKernelHanleDemoDlg::OnBnClickedBtnStop() { g_bIsRunning = FALSE; DWORD dwRet; BOOL bRet = IOControl(IOCTL_STOP, NULL, 0, NULL, NULL, &dwRet); }
启动和停止按钮的功能,就是向驱动发送控制代码,实现对事件通知的控制。
代码中的IOControl函数和KernelHandleThread线程函数, 如下: BOOL IOControl(DWORD Ctl_code, LPVOID InputBuffer, DWORD nInBufferSize, LPVOID OutputBuffer, DWORD nOutBufferSize, LPDWORD dwRet) { BOOL bRet = FALSE; //打开驱动的符号链接 HANDLE hDevice = CreateFile(L"\\.\KernelHandle",GENERIC_READ|GENERIC_WRITE,0, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if (INVALID_HANDLE_VALUE == hDevice) { MessageBox(NULL,L"Failed To Open Device!",NULL,MB_OK); return FALSE; } bRet = DeviceIoControl(hDevice,Ctl_code,InputBuffer,nInBufferSize,OutputBuffer,nOutBufferSize,dwRet,NULL); CloseHandle(hDevice); return bRet; } //监测线程 DWORD WINAPI KernelHandleThread(LPVOID lpParameter) { DWORD dwRet; while(g_bIsRunning) { //等待同步事件信号 WaitForSingleObject(g_hKernelEvent, INFINITE); char szBuffer[20] = {0}; WCHAR szMsgBuffer[100] = {0}; int nProcessId = 0; //等待完成,向驱动发送请求,获取PID IOControl(IOCTL_GET_DATA, NULL, 0, szBuffer, 20, &dwRet); //转换成PID nProcessId = atoi(szBuffer); wsprintf(szMsgBuffer, L"有新进程创建, 进程ID:%d", nProcessId); //弹出消息 MessageBox(NULL,szMsgBuffer, L"驱动消息", MB_OK|MB_ICONINFORMATION); //设置同步事件为无信号,等待下一次通知 ResetEvent(g_hKernelEvent); } return 0; }
至此,应用层主要做的工作已经完成,即:传入事件句柄, 等待驱动通知。
下面,进行驱动层的代码部分。 驱动部分,我使用WDK 7600.16385.1 + VisualDDK开发, 请各位看官按照自己的开发环境进行改动。
首先, 添加两个全局变量, 用于保存事件句柄信息: //同步事件对象 PRKEVENT g_pEventObject = NULL; //句柄信息 OBJECT_HANDLE_INFORMATION g_ObjectHandleInfo;
在DriverEntry中, 添加驱动控制分发函数: DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = KernelHandleDefaultHandler;
然后添加驱动控制的处理代码: NTSTATUS KernelHandleDefaultHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS status = STATUS_SUCCESS; ULONG ulReturn = 0; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); //驱动控制代码 ULONG ulCtrlCode = stack->Parameters.DeviceIoControl.IoControlCode; //输入输出缓冲区 PVOID InputBuffer = (PVOID)Irp->AssociatedIrp.SystemBuffer; PVOID OutputBuffer = (PVOID)Irp->AssociatedIrp.SystemBuffer; //输入输出缓冲区大小 ULONG ulInputBufferSize = stack->Parameters.DeviceIoControl.InputBufferLength; ULONG ulOutputBufferSize = stack->Parameters.DeviceIoControl.OutputBufferLength; switch(ulCtrlCode) { case IOCTL_START: { //设置同步事件 if (InputBuffer == NULL || ulInputBufferSize < sizeof(HANDLE)) { KdPrint(("Set Event Error~! ")); break; } //取得句柄对象 HANDLE hEvent = *(HANDLE*)InputBuffer; status = ObReferenceObjectByHandle(hEvent, GENERIC_ALL, NULL, KernelMode, (PVOID*)&g_pEventObject, &g_ObjectHandleInfo); KdPrint(("g_pEventObject = 0x%X ", g_pEventObject)); //设置进程创建通知函数 if (!g_bIsNotifyRoutineSetted) { PsSetCreateProcessNotifyRoutine(CreateProcessNotifyFunction, FALSE); g_bIsNotifyRoutineSetted = TRUE; } break; } case IOCTL_STOP: { if (g_bIsNotifyRoutineSetted) { //移除进程创建通知函数 PsSetCreateProcessNotifyRoutine(CreateProcessNotifyFunction, TRUE); g_bIsNotifyRoutineSetted = FALSE; } //释放对象引用 if (g_pEventObject != NULL) { ObDereferenceObject(g_pEventObject); g_pEventObject = NULL; } break; } case IOCTL_GET_DATA: { int nLength = strlen(g_szPIDInfo); if (OutputBuffer == NULL && ulOutputBufferSize < nLength) { KdPrint(("OutputBufferSize is too small ~! ")); break; } //复制进程PID到输出缓冲区 RtlCopyBytes((PCHAR)OutputBuffer, g_szPIDInfo, nLength+1); break; } default: break; } Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = ulOutputBufferSize; IoCompleteRequest(Irp, IO_NO_INCREMENT); return Irp->IoStatus.Status; }
在驱动中,拦截进程创建, 一般使用Hook NtCreateProcess来实现,但在我们的这个例子里,只需要获取进程创建的进程ID, 那么,有一个更加简单的方法,即使用PsSetCreateProcessNotifyRoutine函数, 可以方便的获取进程的创建信息。 函数原型如下: NTSTATUS PsSetCreateProcessNotifyRoutine( _In_ PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, _In_ BOOLEAN Remove );
其中,NotifyRoutine指定了处理进程创建通知的函数, Remove为False时,代表添加通知函数, 为TRUE时,代表移除通知函数。 NotifyRoutine是一个函数指针,原型为: VOID (*PCREATE_PROCESS_NOTIFY_ROUTINE) (     IN HANDLE  ParentId,     IN HANDLE  ProcessId,     IN BOOLEAN  Create     );
无需解释过多,Create为True时,代表进程创建,为False时,代表进程退出,因此,我们的处理函数,就写成如下面这样:

//PID信息 CHAR g_szPIDInfo[20]; VOID CreateProcessNotifyFunction(IN HANDLE hParentId, IN HANDLE hProcessId, IN BOOLEAN bCreate ) { //如果是进程创建 if (bCreate) { //格式化字符串 RtlZeroMemory(g_szPIDInfo, 20); RtlStringCchPrintfA(g_szPIDInfo, 20, "%d", (int)hProcessId); //设置事件为有信号,通知应用层 KeSetEvent(g_pEventObject, 0, FALSE); } }
这样, 当有进程创建时, 就会将创建的进程的PID发送到缓冲区,应用层接收事件信号后,就会读取缓冲区中的PID内容,将弹出消息提示了, 如图:

文章中的代码, 讲解了主要的代码部分,省略了部分代码, 完整的代码,我已上传到 {MOD}。 链接: http://pan.baidu.com/s/1bn9zxHP 密码: 9t7j
解压密码:000000
注: 本文代码的开发环境为: Visual Studio 2010 + WDK 7600.16385.1 + VisualDDK,  编译成功后,请手动加载驱动。
本帖为原创,转帖请说明出处,谢谢合作。 本帖地址:http://blog.csdn.net/sonsie007/article/details/38356961