首页 > 代码库 > 安全类工具制作第007篇:行为监控工具的开发
安全类工具制作第007篇:行为监控工具的开发
一、前言
现今的杀毒软件都会带有“行为监控”的功能。该功能可以在可疑进程被创建时,或者注册表敏感位置被写入时等情况下,给予用户以提示,让用户来选择是否对相应的可疑操作进行拦截,从而达到主动防御的目的。这样就可以避免传统杀软依靠特征码查杀病毒的滞后性。行为监控工具可以在恶意程序还未正式产生危害时,就将其扼杀。
在制作病毒专杀工具的时候,往往也会使用行为监控工具。比如由微软推出的大名鼎鼎的Process Monitor就是这类工具的典范。通过监控在虚拟机中运行的病毒的种种行为,来采取相应的对策,从而编写出专杀工具(基于真实病毒的专杀工具制作的整套流程,我会在本系列以后的文章中详细讲述)。而我这次所编写的就是能够实现类似于Process Monitor的监控程序。当然,我这里更主要的是程序原理的论述,文章中实际编写出来的程序自然无法与Process Monitor相提并论(因为Process Monitor一方面应该是钩取了一些我们并不常见的函数,另一方面它应该是运用了内核级的一些技术,这些会在我未来的文章中进行讨论)。当然我相信读者会对其不断完善,从而私人订制出一套自己的安全工具。
二、行为监控的原理
任何程序功能的实现,必然需要调用相应的API函数,因此,想要监测程序的行为,就需要对其调用的API函数进行钩取并分析。我之前的文章中主要讨论过两类HOOK技术,一个是Inline HOOK(详见《反病毒攻防研究第012篇:利用Inline HOOK实现主动防御》),另一个是IAT HOOK技术(详见《逆向工程第004篇:令计算器程序显示汉字(下)》)。通过这两种HOOK技术,就能够对程序中的API函数实现拦截的操作。行为监控的原理就是对敏感API函数进行钩取,之后可以由用户选择是否进行拦截,这样就能够预防恶意程序损害我们的系统。
具体到本程序,为了讨论的简单起见,我这里仅仅钩取用于进程创建的CreateProcess()这个API函数(大家可以依据本章所讲述的原理自行丰富程序的功能)。由于使用了HOOK,那么就必然要涉及DLL的编写。而为了能在所有基于消息的进程中注入我们的DLL,就必须使用Windows钩子。
需要说明的是,我在《反病毒攻防研究第012篇:利用InlineHOOK实现主动防御》中所讲述的方法仅仅针对于一个程序进行防御,但是这次我打算针对系统中的所有CreateProcess()函数进行钩取,这就需要使用Windows下全局钩取的方法。Windows提供的钩子非常多,其中有一种类型的钩子非常实用,那就是WH_GETMESSAGE这个类型的钩子。它可以很方便地将DLL文件注入到所有基于消息机制的程序中,从而实现DLL所编写的功能。
经过上述分析就已经明确了,本程序的实现,需要编写一个DLL程序用于注入,再编写一个EXE程序用于前台的控制。
三、DLL程序的编写
这里需要创建一个简单的Win32Dynamic Link Library项目,并把之前所编写的“CInlineHOOK.h”和“CInlineHOOK.cpp”加入该项目中(详见《反病毒攻防研究第012篇:利用Inline HOOK实现主动防御》),然后添加如下代码:
// EasyPM.cpp : Defines the entry point for the DLL application. // #include "stdafx.h" #include "InlineHOOK.h" #include <stdio.h> #pragma data_seg(".shared") HHOOK g_hHook = NULL; HWND g_ExeHwnd = NULL; #pragma data_seg() #pragma comment (linker, ".shared, RWS") extern "C" __declspec(dllexport) void SetHookOn(HWND hWnd); extern "C" __declspec(dllexport) void SetHookOff(); CInlineHOOK CreateProcessWHook; HINSTANCE g_hInst = NULL; //定义一些常量用来标识类型,可依据程序的完善而增加这部分 #define PM_CREATEPROCESS 0x00000001L //定义一个结构体,将该结构体的信息发送给用于加载DLL的EXE文件,并让EXE给出提示 typedef struct _PM_INFO { WCHAR wProcessName[0x200]; DWORD dwPMClass; }PM_INFO, *PPM_INFO; BOOL WINAPI MyCreateProcessW( __in_opt LPCWSTR lpApplicationName, __inout_opt LPWSTR lpCommandLine, __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in BOOL bInheritHandles, __in DWORD dwCreationFlags, __in_opt LPVOID lpEnvironment, __in_opt LPCWSTR lpCurrentDirectory, __in LPSTARTUPINFOW lpStartupInfo, __out LPPROCESS_INFORMATION lpProcessInformation ) { PM_INFO sz = { 0 }; if ( wcslen(lpCommandLine) != 0 ) { wcscpy(sz.wProcessName, lpCommandLine); } else { wcscpy(sz.wProcessName, lpApplicationName); } sz.dwPMClass = PM_CREATEPROCESS; COPYDATASTRUCT cds = { NULL, sizeof(PM_INFO), (void *)&sz }; BOOL bRet = FALSE; // 注意FindWindow的第二个参数应填写程序主窗口的"Caption" // 这里的SendMessage()函数用来发送一个WM_COPYDATA消息,将结构体 // 传给了加载DLL的EXE程序,使EXE程序把相应的信息显示给用户。 if ( SendMessage(FindWindow(NULL, "EasyPM"), WM_COPYDATA, GetCurrentProcessId(), (LPARAM)&cds) != -1 ) { CreateProcessWHook.UnHOOK(); bRet = CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); CreateProcessWHook.ReHOOK(); } return bRet; } BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch ( ul_reason_for_call ) { case DLL_PROCESS_ATTACH: { g_hInst = (HINSTANCE)hModule; CreateProcessWHook.HOOK("kernel32.dll", "CreateProcessW", (PROC)MyCreateProcessW); break; } case DLL_PROCESS_DETACH: { CreateProcessWHook.UnHOOK(); if ( g_hHook != NULL ) { SetHookOff(); } break; } } return TRUE; } // 钩子函数 LRESULT CALLBACK GetMsgProc( int code, // hook code WPARAM wParam, // removal option LPARAM lParam // message ) { return CallNextHookEx(g_hHook, code, wParam, lParam); } void SetHookOn(HWND hWnd) { g_ExeHwnd = hWnd; // 安装WH_GETMESSAGE钩子用于监视被投递到消息队列中的消息 SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hInst, 0); } void SetHookOff() { // 卸载钩子 UnhookWindowsHookEx(g_hHook); g_hHook = NULL; }以上就是全部DLL程序的代码,并不复杂,我已经添加了相应的注释,大家可以在此基础上添加其它API函数的代码,以便更加全面地监控我们的系统。
四、程序界面的制作
本程序需要一个“List Control”和三个“Button”控件:
然后为“List Control”控件添加一个名为“m_PMReports”的变量,为“启动”和“停止”按钮分别添加名为“m_BtnOn”和“m_BtnOff”的变量。并添加如下初始化代码:
typedef void (*SETHOOKON)(HWND); typedef void (*SETHOOKOFF)(); void CEasyPMDlg::InitPMReports() { m_PMReports.SetExtendedStyle(m_PMReports.GetExtendedStyle() | LVS_EX_GRIDLINES); m_PMReports.InsertColumn(0, "序号"); m_PMReports.InsertColumn(1, "时间"); m_PMReports.InsertColumn(2, "进程"); m_PMReports.InsertColumn(3, "类型"); m_PMReports.SetColumnWidth(0, 40); m_PMReports.SetColumnWidth(1, 160); m_PMReports.SetColumnWidth(2, 200); m_PMReports.SetColumnWidth(3, 200); } void CEasyPMDlg::OnBtnOn() { // TODO: Add your control notification handler code here m_hInst = LoadLibrary("EasyPM.dll"); SETHOOKON SetHookOn = (SETHOOKON)GetProcAddress(m_hInst, "SetHookOn"); SetHookOn(GetSafeHwnd()); FreeLibrary(m_hInst); m_BtnOn.EnableWindow(FALSE); m_BtnOff.EnableWindow(TRUE); } void CEasyPMDlg::OnBtnOff() { // TODO: Add your control notification handler code here m_hInst = GetModuleHandle("EasyPM.dll"); SETHOOKOFF SetHookOff = (SETHOOKOFF)GetProcAddress(m_hInst, "SetHookOff"); SetHookOff(); CloseHandle(m_hInst); FreeLibrary(m_hInst); m_BtnOn.EnableWindow(TRUE); m_BtnOff.EnableWindow(FALSE); } void CEasyPMDlg::OnBtnClear() { // TODO: Add your control notification handler code here m_PMReports.DeleteAllItems(); }
最后需要在相应的位置进行声明,这里不再赘述。
五、主程序的编写
主程序其实就是主要用于接收DLL的消息,代码如下:
#define PM_CREATEPROCESS 0x00000001L typedef struct _PM_INFO { WCHAR wProcessName[0x200]; DWORD dwPMClass; }PM_INFO, *PPM_INFO; BOOL CEasyPMDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) { // TODO: Add your message handler code here and/or call default WCHAR sz[2048]; PPM_INFO pPMInfo = (PPM_INFO)pCopyDataStruct->lpData; wcscpy(sz,pPMInfo->wProcessName); int nNum = m_PMReports.GetItemCount(); CString Str; //显示序号 Str.Format("%d", nNum); m_PMReports.InsertItem(nNum, Str); //获取系统时间 SYSTEMTIME StTime; GetLocalTime(&StTime); Str.Format("%04d/%02d/%02d %02d:%02d:%02d", StTime.wYear, StTime.wMonth, StTime.wDay, StTime.wHour, StTime.wMonth, StTime.wSecond); //显示时间 m_PMReports.SetItemText(nNum, 1, Str); //显示进程名称,注意由于这里是宽字符,所以要用大写的S Str.Format("%S", sz); m_PMReports.SetItemText(nNum, 2, Str); //显示类型,可以依据钩取函数的增加而完善case语句 switch ( pPMInfo->dwPMClass ) { case PM_CREATEPROCESS: { Str = "进程创建(CreateProcess)"; break; } } m_PMReports.SetItemText(nNum, 3, Str); return CDialog::OnCopyData(pWnd, pCopyDataStruct); }这部分代码就是对WM_COPYDATA消息的一个响应,这些代码基本就是对界面进行了操作。因为使用的消息映射,所以还需在相应的位置进行声明,首先是在BEGIN_MESSAGE_MAP(CEasyPMDlg, CDialog)下添加:
ON_WM_COPYDATA()最后在头文件“EasyPMDlg.h”中的“//{{AFX_MSG(CEasyPMDlg)”下添加:
afx_msg BOOL OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct);
以上就是全部代码,完成后就可以编译链接了,运行时不要忘了将之前编写的“EasyPM.dll”放在和本EXE程序同一目录下。
六、程序的测试
这里我先运行本程序,单击界面中的“启动”按钮,然后依次打开“记事本”、“画图”和“计算器”程序,如下图所示:
七、小结
我们的代码并不能监控到所有的进程,而且我们的程序也过于简单,很容易被恶意程序所突破。不过这个程序仅仅是一个雏形,我在此希望能够起到抛砖引玉的作用。本程序还可以加入进程拦截的功能,有兴趣的读者可以自行完善。安全类工具制作第007篇:行为监控工具的开发