zoukankan      html  css  js  c++  java
  • 病毒木马查杀实战第021篇:Ring3层主动防御之编程实现

    前言

           我们这次会依据上次的内容,编程实现一个Ring3层的简单的主动防御软件。整个程序使用MFC实现,程序开始监控时,会将DLL程序注入到explorer.exe进程中,这样每当有新的进程创建,程序首先会进行特征码匹配,从而判断目标程序是否为病毒程序,如果是,则进行拦截,反之不拦截。停止监控时,再卸载掉DLL程序。以下就是程序各个部分的代码实现。

    封装InlineHook类

           对于这次所使用的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。

    编写DLL程序

           这里需要创建一个简单的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,
            "x2ax2ax2axcexe4x2axbaxbax2axc4xd0x2axc9xfax2axb8"
    		"xd0x2axc8xbex2axcfxc2x2axd4xd8x2axd5xdfx2ax2ax2a"
    	},
    	{
            // unpacked.exe
            "unpacked.exe",
            0x1920,
            "x13x8bx45xf0xe8x00x00x00x00x81x04x24xd7x86x00x00"
    		"xffxd0xebx11x6ax10x68x30x80x40x00xffx75xfcx53xff"
    	}
    };
    
    // 特征码检测函数
    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

    “开启监控”按钮代码的编写

           “开启监控”按钮的功能是首先需要获取欲注入的DLL程序的完整路径,也就是在当前目录中。之后需要查找当前进程中是否存在explorer.exe进程,并获取其PID值,之后就可以利用该PID值进行DLL的注入了。而DLL注入的流程,已在上次讲过。完整代码如下:
    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文件已经被卸载掉了,也就说明,我们的程序很好地完成了相应的功能。

    小结

           我们这次所讨论的主动防御程序还是比较简陋的,也只能够防范特征库中所包含的病毒,而对于未知病毒则无能为力了,这就是利用特征库查毒的局限性。其实大家可以在我们的代码的基础上进行功能的完善,通过对各种各样的API函数的钩取,来保护我们的系统免受侵害。也希望这次的程序能够起到抛砖引玉的作用,使大家有所收获。
  • 相关阅读:
    处理器及其调度
    java面向对象
    操作系统概述
    mysql 基础操作
    java集合类详解
    java数组
    java方法
    Python—进程间通信
    Python—TCP的黏包问题以及UDP的分片问题
    Python—网络通信编程之tcp非阻塞通信(socketserver)
  • 原文地址:https://www.cnblogs.com/csnd/p/11785750.html
Copyright © 2011-2022 走看看