前言

我们这次会依据上次的内容,编程实现一个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,
"\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

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

“开启监控”按钮的功能是首先需要获取欲注入的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函数的钩取,来保护我们的系统免受侵害。也希望这次的程序能够起到抛砖引玉的作用,使大家有所收获。

病毒木马查杀实战第021篇:Ring3层主动防御之编程实现的更多相关文章

  1. 病毒木马查杀实战第025篇:JS下载者脚本木马的分析与防御

    前言 这次我与大家分享的是我所总结的关于JS下载者脚本木马的分析与防御技术.之所以要选择这样的一个题目,是因为在日常的病毒分析工作中,每天都会遇到这类病毒样本,少则几个,多则几十个(当然了,更多的样本 ...

  2. 病毒木马查杀实战第011篇:QQ盗号木马之专杀工具的编写

    前言 由于我已经在<病毒木马查杀第004篇:熊猫烧香之专杀工具的编写>中编写了一个比较通用的专杀工具的框架,而这个框架对于本病毒来说,经过简单修改也是基本适用的,所以本文就不讨论那些重叠的 ...

  3. 病毒木马查杀实战第010篇:QQ盗号木马之十六进制代码分析

    前言 按照我的个人习惯,在运用诸如IDA Pro与OllyDBG对病毒进行逆向分析之前,我都会利用一些自动化的工具,通过静态或动态的分析方法(参见<病毒木马查杀第008篇:熊猫烧香之病毒查杀总结 ...

  4. 病毒木马查杀实战第009篇:QQ盗号木马之手动查杀

    前言 之前在<病毒木马查杀第002篇:熊猫烧香之手动查杀>中,我在不借助任何工具的情况下,基本实现了对于"熊猫烧香"病毒的查杀.但是毕竟"熊猫烧香" ...

  5. 病毒木马查杀实战第020篇:Ring3层主动防御之基本原理

    前言 假设说我们的计算机中安装有杀毒软件,那么当我们有意或无意地下载了一个恶意程序后.杀软一般都会弹出一个对话框提示我们,下载的程序非常可能是恶意程序,建议删除之类的.或者杀软就不提示.直接删除了:或 ...

  6. 病毒木马查杀实战第015篇:U盘病毒之脱壳研究

    前言 因为我们的终于目标是编写出针对于这次的U盘病毒的专杀工具.而通过上次的分析我们知道,病毒有可能在不同的计算机中会以不同的名称进行显示.假设真是如此,那么就有必要在此分析出病毒的命名规律等特征,然 ...

  7. 病毒木马查杀实战第017篇:U盘病毒之专杀工具的编写

    前言 经过前几次的讨论,我们对于这次的U盘病毒已经有了一定的了解,那么这次我们就依据病毒的行为特征,来编写针对于这次U盘病毒的专杀工具. 专杀工具功能说明 因为这次是一个U盘病毒,所以我打算把这次的专 ...

  8. 病毒木马查杀实战第022篇:txt病毒研究

    前言 反病毒爱好者们非常喜欢讨论的一个问题就是,现在什么样的病毒才算得上是主流,或者说什么样的病毒才是厉害的病毒呢?我们之前的课程所解说的都是Ring3层的病毒.所以有些朋友可能会觉得.那么Ring0 ...

  9. 病毒木马查杀实战第013篇:一个基于.NET的“敲竹杠”病毒研究

    前言 恶意程序发展至今,其功能已经从最初的单纯破坏,不断发展为隐私的窥探,信息的盗取,乃至如今非常流行的"敲竹杠"病毒,用于勒索.可见随着时代的发展,病毒的作者们往往也是想利用自己 ...

随机推荐

  1. 漫漫Java路1—基础知识2—注释和命名规则

    ## 注释 1. 单行注释 ```java //这是一个注释 ``` 2. 多行注释 ```java /* 这是一个注释 */ ``` 3. 文档注释 ```java /** * * * */ ``` ...

  2. [UWP] 模仿哔哩哔哩的一键三连

    1. 一键三连 什么是一键三连? 哔哩哔哩弹幕网中用户可以通过长按点赞键同时完成点赞.投币.收藏对UP主表示支持,后UP主多用"一键三连"向视频浏览者请求对其作品同时进行点赞.投币 ...

  3. LNMP配置——Nginx配置 —— 配置静态文件不记录日志并添加过期时间

    一.配置 #vi /usr/local/nginx/conf/vhost/test.com.conf 写入; server { listen 80; server_name test.com test ...

  4. url里bookmark是什么意思

    <a rel="bookmark" href="abc.com"> 点击查看 </a> rel 这个属性的全称是  relationsh ...

  5. JSP、EL表达式、JSTL标签库干货(建议收藏)

    JSP(Java Server Pages)类似于ASP技术,它是在传统的网页HTML文件(.htm,.html)中插入Java程序段(Scriptlet)和JSP标记(tag),从而形成JSP文件, ...

  6. Linux Shell 统计一(行\列)数值的总和及行、列转换

    (对一列数字求和) 在日常工作当中需要对文本过滤出来的数字进行求和运算,例如想统计一个MySQL分区表现在有多大 # ls -lsh AdPlateform#P#p*.ibd  |grep G 2.6 ...

  7. python inspect库

    一.介绍 inspect模块用于收集python对象的信息,可以获取类或函数的参数的信息,源码,解析堆栈,对对象进行类型检查等等. inspect模块主要提供了四种用处: 对是否是模块.框架.函数进行 ...

  8. 攻防世界 reverse android-app-100

     android-app-100  suctf-2016 jeb启动,找到点击事件: 验证流程: 输入作为参数 --> processObjectArrayFromNative 得到一返回值(r ...

  9. 走进docker-swarm 带大家快速掌握docker自带编排工具

    什么是Docker Swarm? 对比Docker 前面我们介绍过Docker可以理解成是一个我们的服务的独立运行的容器,那么在实际工作中,我们的系统可能是一个微服务应用,系统中根据业务拆分成多个模块 ...

  10. Spring Cloud Alibaba(2)---Nacos概述

    Spring Cloud Alibaba(2)---nacos概述 上一篇博客讲了有关 SpringCloudAlibaba的概述,这篇开始讲SpringCloudAlibaba组件之一---Naco ...