本系列教程版权归“i春秋”所有,转载请标明出处。
我们这次会依据上次的内容,编程实现一个Ring3层的简单的主动防御软件。整个程序使用MFC实现,程序开始监控时,会将DLL程序注入到explorer.exe进程中,这样每当有新的进程创建,程序首先会进行特征码匹配,从而判断目标程序是否为病毒程序,如果是,则进行拦截,反之不拦截。停止监控时,再卸载掉DLL程序。以下就是程序各个部分的代码实现。
对于这次所使用的Hook技术,我打算采取面向对象的方法,用C++封装一个Inline Hook类,便于以后的使用。一般来说,封装的类都有两个文件,一个是类的头文件,另一个是类的实现文件。类名一般都是以字母“C”为开头,表示“Class Name”,因此这里的类名为“CInlineHook”,头文件我起名为“InlineHook.h”,那么类的实现文件就可以命名为“InlineHook.cpp”。首先是类的头文件代码:
#include <windows.h> class CInlineHook { public: CInlineHook(); // 构造函数,用于初始化 ~CInlineHook(); // 析构函数,用户程序结束后资源的释放 // Hook函数 BOOL Hook(LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc); // 取消Hook函数 void UnHook(); // 重新进行Hook函数 BOOL ReHook(); private: PROC m_pfnOrig; // 自定义的函数的地址 BYTE m_bOldBytes[5]; // 原始函数入口代码 BYTE m_bNewBytes[5]; // 构造的跳转指令的代码 };头文件中主要是声明一些需要使用的函数与变量,代码中已给出了相应的注释。接下来是类的实现文件(InlineHook.cpp)的代码:
#include "stdafx.h" #include "InlineHook.h" CInlineHook::CInlineHook() { // 对成员变量的初始化 m_pfnOrig = NULL; ZeroMemory(m_bOldBytes, 5); ZeroMemory(m_bNewBytes, 5); } CInlineHook::~CInlineHook() { // 取消Hook UnHook(); } //挂钩函数,参数依次为模块名称、函数名称以及自定义的钩子函数 BOOL CInlineHook::Hook(LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc) { BOOL bRet = FALSE; // 获取指定模块中函数的地址 m_pfnOrig = (PROC)GetProcAddress(GetModuleHandle(pszModuleName), pszFuncName); if ( m_pfnOrig != NULL ) { // 保存该地址处前5个字节的内容 DWORD dwNum = 0; ReadProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bOldBytes, 5, &dwNum); // 构造JMP指令,"\xe9"为jmp的Opcode m_bNewBytes[0] = '\xe9'; // pfnHookFunc是Hook后的地址,m_pfnOrig是原来的地址,5是指令长度 *(DWORD *)(m_bNewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_pfnOrig - 5; // 将构造好的地址写入该地址处 WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bNewBytes, 5, &dwNum); bRet = TRUE; } return bRet; } //取消函数的挂钩 void CInlineHook::UnHook() { if ( m_pfnOrig != 0 ) { DWORD dwNum = 0; WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bOldBytes, 5, &dwNum); } } //重新对函数进行挂钩 BOOL CInlineHook::ReHook() { BOOL bRet = FALSE; if ( m_pfnOrig != 0 ) { DWORD dwNum = 0; WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bNewBytes, 5, &dwNum); bRet = TRUE; } return bRet; }
以上就是整个InlineHook的封装,代码非常简单,这里不再赘述,利用它可以很容易地实现对函数的Hook。
这里需要创建一个简单的Win32 DynamicLink Library项目:
图1
并把上面编写的“InlineHook.h”和“InlineHook.cpp”加入该项目中:
图2
然后在源文件中新建一个名为HookCreateProcess.cpp的文件,添加如下代码:// HookCreateProcess.cpp : Defines the entry point for the DLL application. // #include "stdafx.h" #include "InlineHook.h" #include "windows.h" #define NAMELEN 20 #define SIGNLEN 32 typedef struct SIGN { char szVirusName[NAMELEN]; LONG lFileOffset; BYTE bVirusSign[SIGNLEN+1]; }_SIGN, *PSIGN; // 病毒程序的特征码 SIGN Sign[2] = { { // setup.exe "setup.exe", 0x0C040, "\x2a\x2a\x2a\xce\xe4\x2a\xba\xba\x2a\xc4\xd0\x2a\xc9\xfa\x2a\xb8"\ "\xd0\x2a\xc8\xbe\x2a\xcf\xc2\x2a\xd4\xd8\x2a\xd5\xdf\x2a\x2a\x2a" }, { // unpacked.exe "unpacked.exe", 0x1920, "\x13\x8b\x45\xf0\xe8\x00\x00\x00\x00\x81\x04\x24\xd7\x86\x00\x00"\ "\xff\xd0\xeb\x11\x6a\x10\x68\x30\x80\x40\x00\xff\x75\xfc\x53\xff" } }; // 特征码检测函数 BOOL CheckSig(LPCWSTR FilePath) { DWORD dwSigNum = 0; DWORD dwNum = 0; BYTE buffer[SIGNLEN+1]; int i; HANDLE hFile = NULL; hFile = CreateFileW(FilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); for(i=0; i <= 1; i++) { // 将待检测程序的文件指针指向特征码的偏移位置 SetFilePointer(hFile, Sign[i].lFileOffset, NULL, FILE_BEGIN); // 读取目标程序指定偏移位置的特征码 ReadFile(hFile, buffer, sizeof(buffer), &dwNum, NULL); // 特征码的比对 if(memcmp(Sign[i].bVirusSign, buffer, SIGNLEN) == 0) { CloseHandle(hFile); return TRUE; } } CloseHandle(hFile); return FALSE; } // 创建一个名为CreateProcessHook的CInlineHook类 CInlineHook CreateProcessHook; // 我们实现的Hook函数 BOOL WINAPI MyCreateProcessW( LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { BOOL bRet = FALSE; if ( !CheckSig(lpApplicationName) ) { // 如果经过特征码匹配,目标程序不是病毒,则卸载钩子,执行程序,再安装钩子 CreateProcessHook.UnHook(); bRet = CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); CreateProcessHook.ReHook(); } else { // 如果经过特征码匹配,目标程序是病毒,则进行拦截 MessageBox(NULL, "您启动的程序是病毒,已经被拦截!", "重要提示", MB_OK); } return bRet; } BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch ( ul_reason_for_call ) { case DLL_PROCESS_ATTACH: { // Hook CreateProcessW()函数 CreateProcessHook.Hook("kernel32.dll", "CreateProcessW", (PROC)MyCreateProcessW); break; } case DLL_PROCESS_DETACH: { CreateProcessHook.UnHook(); break; } } return TRUE; }
上述程序在编译运行后,就会生成我们所需要的DLL文件。其原理是钩取成功后,每次遇到CreateProcess()函数,都会解析它的第一个参数,获取所要启动的程序完整路径,然后利用之前讲过的特征码的匹配方式进行匹配,以判定目标程序是否安全。如果遇到病毒程序,则进行拦截,使其无法运行,正常程序则放行。
程序使用MFC实现,界面中只有两个按钮:
图3
然后为这两个按钮分别添加两个变量:
图4
我们希望在程序运行时,“开启监控”按钮是可用状态,而“关闭监控”是不可用的状态,因此需要在BOOL CSimpleHIPSDlg::OnInitDialog()中添加如下代码:
图5
void CSimpleHIPSDlg::OnButtonOn() { // TODO: Add your control notification handler code here BOOL bRet = FALSE; DWORD dwPid = 0; // 获取欲注入的DLL文件的完整路径 char *szDllName = getcwd(NULL, 0); strcat(szDllName, "\\HookCreateProcess.dll"); // 查找explorer.exe进程,获取其PID值 bRet = FindTargetProcess("explorer.exe", &dwPid); // 如果找到explorer.exe进程,则注入DLL if(bRet == TRUE) { // 利用PID值,获取进程的句柄 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); if(hProcess == NULL) { AfxMessageBox("进程打开失败!"); return; } // 长度为DLL名称的长度加上字符终止符 int nDllLen = strlen(szDllName) + sizeof(char); // 申请内存空间 PVOID pDllAddr = VirtualAllocEx( hProcess, // process to allocate memory NULL, // desired starting address nDllLen, // size of region to allocate MEM_COMMIT, // type of allocation PAGE_READWRITE); // type of access protection if(pDllAddr == NULL) { AfxMessageBox("申请内存区域失败!"); CloseHandle(hProcess); return; } DWORD dwWriteNum = 0; if (!WriteProcessMemory(hProcess, pDllAddr, szDllName, nDllLen, &dwWriteNum)) { AfxMessageBox("进程写入失败!"); // 失败就释放原先申请的内存区域,撤销内存页的提交状态 VirtualFreeEx(hProcess, pDllAddr, nDllLen, MEM_DECOMMIT); return; } // 获取LoadLibraryA的地址 FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); // 创建远程线程 HANDLE hThread = CreateRemoteThread(hProcess, // handle to process NULL, // SD 0, // initial stack size (LPTHREAD_START_ROUTINE)pFunAddr, // thread function pDllAddr, // thread argument 0, // creation option NULL); // thread identifier if (hThread == NULL) { AfxMessageBox("创建远程线程失败!"); // 释放原先申请的内存区域,撤销内存页的提交状态 VirtualFreeEx(hProcess, pDllAddr, nDllLen, MEM_DECOMMIT); return; } AfxMessageBox("监控成功开启!"); m_BtnOn.EnableWindow(FALSE); m_BtnOff.EnableWindow(TRUE); // 等待线程退出 WaitForSingleObject(hThread, INFINITE); // 释放原先申请的内存区域 撤销内存页的提交状态 VirtualFreeEx(hProcess, pDllAddr, nDllLen, MEM_DECOMMIT); //关闭句柄 CloseHandle(hThread); CloseHandle(hProcess); } // 如果未找到explorer.exe进程,则进行提示 else { AfxMessageBox("未找到explorer.exe进程,监控失败!"); return; } }
上述程序中使用了查找指定进程的函数FindTargetProcess(),它与之前所讲的“熊猫烧香专杀工具”中的代码是一致的,这里不再赘述。因为程序使用了getcwd()函数获取当前路径,所以需要添加头文件direct.h,而为了实现进程的遍历,又需要包含头文件Tlhelp32.h。
“关闭监控”按钮代码的编写
“关闭监控”按钮的功能是查找explorer.exe进程中是否含有我们所注入的HookCreateProcess.dll文件,如果有,则将其卸载掉。为了保险起见,还需要先进行提升权限的操作。提升权限的代码与之前所讲的“熊猫烧香专杀工具”中的代码是一致的,这里不再赘述。完整的代码如下:void CSimpleHIPSDlg::OnButtonOff() { // TODO: Add your control notification handler code here BOOL flag = FALSE; DWORD dwPid = 0; char *szDllName = "HookCreateProcess.dll"; // 提升权限 EnableDebugPrivilege(SE_DEBUG_NAME); // 查找explorer.exe进程,获取其PID值 FindTargetProcess("explorer.exe", &dwPid); // 获取系统运行模块的列表 HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid); MODULEENTRY32 Me32 = { 0 }; Me32.dwSize = sizeof(MODULEENTRY32); // 检索与进程相关联的第一个模块的信息 BOOL bRet = Module32First(hSnap, &Me32); while ( bRet ) { // 查找所注入的DLL if ( strcmp(Me32.szModule, szDllName) == 0 ) { flag = TRUE; break; } //检索下一个模块信息 bRet = Module32Next(hSnap, &Me32); } if (flag == FALSE) { AfxMessageBox("找不到相应的模块!"); return; } CloseHandle(hSnap); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); if ( hProcess == NULL ) { AfxMessageBox("进程打开失败!"); return ; } FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "FreeLibrary"); HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunAddr, Me32.hModule, 0, NULL); if (hThread == NULL) { AfxMessageBox("创建远程线程失败!"); return; } AfxMessageBox("监控成功关闭!"); m_BtnOn.EnableWindow(TRUE); m_BtnOff.EnableWindow(FALSE); //等待线程退出 WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); }
至此,所有程序编写完毕。
我们将主动防御程序与准备注入的DLL程序放置在同一目录中,运行主动防御程序,此时“关闭监控”按钮是不可用的状态:
图6
然后我们可以利用ProcessExplorer来协助我们进行观察。点击“开始监控”,可以发现在explorer.exe进程中,多出了一个名为HookCreateProcess.dll的文件,说明我们的注入是成功的,而且“开启监控”按钮也处于了不可用的状态:
图7
此时可以尝试一下运行setup.exe以及unpacked.exe这两个病毒程序:
图8
我们的主动防御系统都能够成功地将病毒程序拦截,而正常程序则会被主动放行,说明我们的程序达到了预期的目的。而点击“关闭监控”,通过Process Explorer可知,DLL文件已经被卸载掉了,也就说明,我们的程序很好地完成了相应的功能。