首页 > 代码库 > 反病毒攻防研究第012篇:利用Inline HOOK实现主动防御
反病毒攻防研究第012篇:利用Inline HOOK实现主动防御
一、前言
之前文章中所讨论的恶意程序的应对方法,都是十分被动的,即只有当恶意程序被执行后,才考虑补救措施。这样,我们就会一直处于后手状态,而如果说病毒的危害性极大,那么即便我们完美地修复了诸如注册表项,服务项等敏感位置,并且删除了病毒本身,但是它依旧可能已经破坏了系统中非常重要的文件,造成了不可逆的损伤。因此这篇文章就来简单讨论一下利用Inline HOOK技术实现主动防御,在病毒执行前,就主动将危险函数劫持,如同一道防火墙,保护我们计算机的安全。
二、Inline HOOK原理
我们平时所使用的API函数都保存在操作系统提供的DLL文件中,当程序需要使用某个API函数时,在程序运行后,程序会隐式地将API函数所在的DLL加载到内存中。这样,程序就会像调用自己的函数一样调用API。依据这个原理,假设我们要对系统中的某个API函数进行HOOK,那么首先就需要在指定进程的内存中找到该函数的地址,然后修改该函数首地址的代码为“jmp MyProc”指令,这里的“MyProc”就是我们自己编写的欲执行的函数。这样一来,当指定的进程想要正常调用该函数时,就会直接跳到我们自己所编写的函数中去执行,这样就完成了Inline HOOK。将流程归纳如下:
(1)构造跳转指令,即jmp MyProc。
(2)在内存中找到欲HOOK函数的地址(这可以通过使用GetProcAddress()函数实现),然后保存欲HOOK位置处的前5个字节(跳转指令需占用5个字节)。
(3)将构造的跳转指令写入需要HOOK的位置处,这样当被HOOK的位置执行时,就会跳转到我们的函数执行。
(4)如果要执行原来的函数,那么需要取消HOOK,还原第(2)步中保存的5个字节。
(5)执行原来的流程。
那么我们接下来的编程就依照这个流程来进行。
三、封装InlineHOOK类
为了方便起见,对于HOOK技术的编程我这里采用面向对象的思想。用C++封装一个Inline HOOK类,这样以后的编程也可以使用它。
一般来说,封装的类都有两个文件,一个是类的头文件,另一个是类的实现文件。类名一般都是以字母“C”为开头,表示“Class Name”,因此这里的类名为“CInlineHOOK”,头文件我们起名为“CInlineHOOK.h”,那么类的实现文件命名为“CInlineHOOK.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]; // 构造的跳转指令的代码 };头文件中主要是声明一些需要使用的函数与变量,代码中已给出了相应的注释。接下来是类的实现文件的代码:
#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; }
以上就是整个Inline HOOK的封装,代码非常简单,这里不再赘述。以后的研究文章中,还会用到这个类。因为利用它可以很容易地实现对函数的HOOK。
四、对“病毒”所用到的函数进行HOOK
这里我们结合我之前所写的文章《反病毒攻防研究第010篇:DLL注入(中)——DLL注入与卸载器的编写》进行讨论。在那篇文章中,我使用DLL注入器将模拟病毒程序的DLL文件注入到了记事本进程中,这样当DLL成功注入后,用于模拟病毒的对话框程序就自动运行了。而在这篇文章中,我同样也是要编写一个DLL文件,用这个DLL文件来劫持“病毒”程序。这里是假设MessageBoxA()就是一个危险函数,也是我们所要劫持的目标。首先将本篇文章所生成的DLL注入,然后再注入“病毒”DLL进行验证。
同样需要建立一个简单的Win32 Dynamic Link Library项目,然后添加如下代码:// HookMessageBoxA.cpp : Defines the entry point for the DLL application. // #include "stdafx.h" #include "InlineHOOK.h" CInlineHOOK MsgHOOK; // 我们实现的HOOK函数 int WINAPI MyMessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType ) { //先卸载Inline HOOK,以显示用户提示对话框 MsgHOOK.UnHOOK(); if ( MessageBox(NULL,"检测到可疑程序正在启动,是否拦截?", "提示", MB_YESNO) == IDNO ) { //用户选择了不进行拦截 MessageBoxA(hWnd,lpText,lpCaption,uType); MessageBox(NULL, "疑似恶意程序未被拦截", "提示", MB_OK); MsgHOOK.ReHOOK(); } else { //用户选择了进行拦截 MessageBox(NULL, "疑似恶意程序拦截成功", "提示", MB_OK); MsgHOOK.ReHOOK(); } return 0; } BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch ( ul_reason_for_call ) { //当DLL被某进程加载时DllMain被调用 case DLL_PROCESS_ATTACH: { MsgHOOK.HOOK("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA); break; } //当DLL被某进程卸载时DllMain被调用 case DLL_PROCESS_DETACH: { MsgHOOK.UnHOOK(); break; } } return TRUE; }然后将我们之前封装的类的两个文件添加到工程中,如下图所示:
编译生成DLL文件,就可以进行测试了。
五、程序的测试
首先打开记事本程序,获取其进程ID,然后利用《反病毒攻防研究第010篇:DLL注入(中)——DLL注入与卸载器的编写》中所编写出来的DLL注入工具,将上述DLL文件注入到记事本进程中。然后再注入“病毒”程序,如下图所示:图2 成功拦截可疑程序
可见我们的Inline HOOK已经成功将MessageBox()函数拦截下来了,此时点击“是”:
图3 提示恶意程序拦截成功
程序会提示说拦截成功,而用于模拟病毒的对话框也并未弹出。此时再查看一下记事本进程中的模块信息:
图4 查看记事本进程的模块信息
可见我们所注入的两个DLL文件都在记事本进程中,但是HackedDll.dll已经失效,对我们的系统已经不会产生威胁了。如果在图2中所示的对话框选择了“否”,表明我们不进行拦截,则会如下图所示:
图5 放弃拦截
此时用于模拟病毒的对话框程序弹出,如果这个是真的病毒的话,那么我们的计算机就已经被感染了。点击“确定”:
图6 提示恶意程序未被拦截
至此,我们的Inline HOOK程序的目的达到,说明是可行的。
六、小结
其实,HOOK的应用范围非常广泛,本篇文章所举的例子不过是冰山一角。灵活利用这种思想,在安全领域可以达成很多的目的。比如杀毒软件就会用HOOK技术钩住一些API函数,如钩住RegSetValueEx()函数,以防止病毒对注册表的写入。而由本篇文章的分析又可以发现,我们所学的知识往往是一环扣一环的,只有把之前的知识学好了,打下了坚实的基础,才能不断探索更加高深的领域。以前的知识,我也会在未来的文章中多次提及并运用,所以也希望读者能够勤于动手,切忌眼高手低。反病毒攻防研究第012篇:利用Inline HOOK实现主动防御