首页 > 代码库 > 反病毒攻防研究第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;
}
        然后将我们之前封装的类的两个文件添加到工程中,如下图所示:

图1 添加类文件到工程中

        编译生成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实现主动防御