驱动层与应用层的事件同步(主动防御原理浅析)
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
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮