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

前言

       假设说我们的计算机中安装有杀毒软件,那么当我们有意或无意地下载了一个恶意程序后。杀软一般都会弹出一个对话框提示我们,下载的程序非常可能是恶意程序,建议删除之类的。或者杀软就不提示。直接删除了;或者当我们运行了某一个程序,包括有可疑操作,比方创建开机启动项,那么杀软一般也会对此进行提醒;或者当我们在计算机中插入U盘。杀软往往也会第一时间对U盘进行扫描,确认没有问题后,再打开U盘……上述这些,事实上都属于杀软的“主动防御”功能。

 

“主动防御”简单介绍

       杀毒软件通常集成监控识别、病毒扫描和清除、自己主动升级病毒库、主动防御等功能,有的杀毒软件还带有数据恢复等功能。是计算机防御系统的重要组成部分。关于当中的“病毒扫描和清除”,我们之前在专杀工具的编写以及特征码查杀的课程中。已经解说过主要的原理。

而杀毒软件发展至今。各大安全厂商也越来越将目光放在“主动防御”技术的研发上。之所以会如此重视这项技术,是由于优秀的“主动防御”系统即便不升级病毒库,依然能够预防各种未知木马和新病毒。由于不论什么恶意程序,它假设想要实现各种各样的功能,终于都是须要调用各种API函数的。那么我们就能够对敏感API函数进行监控,在API函数运行之前,先解析API函数各个參数的内容。从而判定目标程序到底是想实现什么样的功能,假设是恶意操作,就进行拦截,让该API函数无法运行。

假设是正常操作。则直接放行。

       比方恶意程序都喜欢将自己加入进系统的启动项中,那么我们就能够开启对注冊表的相关函数的监控。假设检測到目标程序会加入注冊表启动项。那么在加入之前,先进行拦截,询问用户是否允许该程序加入启动项,假设用户允许。则取消拦截。将程序加入启动项。假设用户不允许,或者“主动防御”系统就认定目标程序是恶意程序,那么就会阻止恶意程序的这一功能,以保证我们计算机的安全。

通过我们之前的课程对于病毒的分析能够知道,病毒程序往往会有多种行为,那么也就须要我们对于计算机系统的方方面面进行监控,甚至还须要一定的算法来分析各个API函数调用的相互关系。从而对目标程序进行判定。正是由于这样,“主动防御”系统才不是那么倚重病毒库的支持。因此非常多高手的计算机中往往不安装不论什么杀毒软件,可是却一定要安装“主动防御”系统。

       成熟的杀毒软件或者专门的“主动防御”系统。它们的编程实现是基于 Ring0层的,由于也仅仅有在内核级别,才干够实现良好的监控效果。

而我们的课程为了简单起见,也便于大家理解,讨论的是最简单的基于Ring3层的“主动防御”系统的实现。虽说是R3级的,可是也涉及了不少的知识。希望大家一定要先将这次所讲的理论弄清楚。这样在接下来的编程实现中,才会游刃有余。

       我们须要的知识有下面几点:

       1、Inline Hook的基本原理

       2、DLL注入的基本方法

       3、对系统进行全局监控

 

Inline Hook的基本原理

       API函数都保存在操作系统提供的DLL文件里。

当我们在程序中调用某个API函数并运行程序后,程序会隐式地将API函数所在的DLL文件载入到进程中,这样,程序就会像调用自己的函数一样调用API函数。我们能够看一下这个实验程序:

#include<stdio.h>
#include<windows.h>

int main()
{
    char szCommandLine[] = "notepad.exe";  // 所要启动的程序
    STARTUPINFO si = {sizeof(si)};
    PROCESS_INFORMATION pi;
    si.dwFlags = STARTF_USESHOWWINDOW; // 指定wShowWindow成员有效
    si.wShowWindow = TRUE;                // 此成员设为TRUE的话则显示新建进程的主窗体
    BOOL bRet = CreateProcess(
                        NULL,              // 不在此指定可运行文件的文件名称
                        szCommandLine,   // 命令行參数
                        NULL,              // 默认进程安全性
                        NULL,              // 默认进程安全性
                        FALSE,             // 指定当前进程内句柄不能够被子进程继承
                        CREATE_NEW_CONSOLE,// 为新进程创建一个新的控制台窗体
                        NULL,              // 使用本进程的环境变量
                        NULL,              // 使用本进程的驱动器和文件夹
                        &si,
                        &pi );
    if(bRet)
	{
        // 关掉不使用的句柄
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
	}
	getchar();
    return 0;
}

       上述程序运行后,会打开“记事本”程序。由于我们整个“主动防御”程序的设计就是环绕着CreateProcess()这个函数展开的。所以我们的样例也是以这个函数来解说的。

我们能够使用OD载入这个程序来看一下函数调用位置处的语句:


图1

       能够看到,当一系列的push让參数入栈后,程序调用了call语句来调用kernel32.dll中的CreateProcess()函数。事实上call语句要实现两个功能。首先是向栈中压入当前指令在内存中的位置,即利用push语句保存返回地址。之后是利用jmp语句跳转到所调用函数的入口处。能够先单步运行到上图中的call语句处,然后按下F7步入这个call:


图2

       此时程序跳到了0x7C80236B的位置,也就是CreateProcess()真正实现的代码位置。通过观察当前内存中的载入状态,可知该地址位于kernel32.dll中:


图3

       能够总结一下,当我们所编写的exe文件须要调用CreateProcess()函数的时候,会将kernel32.dll载入内存(事实上无论运行什么程序,kernel32.dll一般都会自己主动载入内存),然后调用该动态链接库中的CreateProcess()函数(事实上真正调用的是CreateProcessA或CreateProcessW)。由于真正的CreateProcess()函数的实如今kernel32.dll模块中。这里所说的调用,事实上能够理解为直接跳到该函数的地址去运行。

基于这些知识,我们全然能够改动API函数在内存中的映像,从而实现对API函数的钩取。

详细来说就是直接使用汇编指令jmp来改变代码的运行流程,先不让它跳到0x7C80236B的位置,而是跳到我们自己编写的代码处运行,在运行完我们自己的代码后,再决定是否让它再跳到0x7C80236B继续运行。

       那么如今的问题是应该怎样构造jmp语句。

事实上关于这个问题,我在《缓冲区溢出分析》系列的课程中,以前讲过一个“jmp back”的方法,这里的方式与它是一样的。最好还是先来看一下。我们的程序中的jmp语句的特点。进入main函数后,正好就有一个jmp:


图4

       程序通过这个jmp。就来到的main函数的真正位置。这里分析一下位于0x00401005处的反汇编代码,是“E9 06000000”。

当中的“E9”就是jmp对应的机器码,后面的“06000000”事实上是一个跳转偏移,偏移的计算公式例如以下:

jmp后的偏移值 = 目的地址 – 当前地址 - 5

       公式中减去5,是由于jmp指令进行跳转。须要五个字节实现。那么针对于我们当前的这个程序。结合上图,目的地址为0x00401010。当前地址为0x00401005。即:

jmp后的偏移值 = 0x00401010 - 0x00401005 – 5 = 6

       也就是“E9”后面的“06”的由来。经过上述分析可知。我们仅仅要在欲钩取的函数位置处,改动前五个字节为我们的jmp语句。使其跳向我们自己的函数位置就能够了。

由于这样的方法是在程序的流程中直接进行嵌入jmp指令来改变流程的,所以就把它叫做Inline Hook。

 

DLL注入的基本方法

       我们希望被钩取的函数所在的程序,在每次调用CreateProcess()的时候,都能够主动运行我们的jmp语句,这就须要我们对目标程序的功能进行扩展。这能够通过让目标程序载入我们编写的DLL来完毕。那么为了让目标程序能够载入DLL。就须要利用DLL注入的方法来实现。

       详细到我们所要编写的主动防御程序,事实上函数钩取,也就是Hook CreateProcess()功能,是通过一个DLL程序来实现的,而我们的主函数的作用,就是将DLL注入到对应的进程中,当停止监控时,再将DLL卸载。

DLL的注入与卸载是一系列严格的流程,注入的流程例如以下:

       1、OpenProcess获得要注入进程的句柄

       2、VirtualAllocEx在远程进程中开辟出一段内存,长度为strlen(dllname)+1;

       3、WriteProcessMemory将Dll的名字写入第二步开辟出的内存中。

       4、CreateRemoteThread将LoadLibraryA作为线程函数。參数为Dll的名称,创建新线程

       5、CloseHandle关闭线程句柄

       卸载的流程例如以下:

       1、CreateRemoteThread将GetModuleHandle注入到远程进程中。參数为被注入的Dll名

       2、GetExitCodeThread将线程退出的退出码作为Dll模块的句柄值。

       3、CloseHandle关闭线程句柄

       3、CreateRemoteThread将FreeLibraryA注入到远程进程中,參数为第二步获得的句柄值。

       4、WaitForSingleObject等待对象句柄返回

       5、CloseHandle关闭线程及进程句柄。

       事实上上述流程的道理并不难,而我们下次课程中的程序的编写,就根据上述流程来进行。

 

对系统进行全局监控

       在Ring3层。我们经常使用的对系统进行全局监控的方式是使用Windows的全局钩子。在操作系统中安装全局钩子以后,仅仅要目标进程符合我们所设定的条件,全局钩子的DLL文件会被操作系统自己主动或强行地载入到该进程中。而DLL文件存放的正是钩子函数的代码,也即我们想要钩取实现的功能。

可见,这样的钩子须要使用DLL注入的方式来实现。假设使用这样的方式,我们须要使用SetWindowsHookEx()函数来进行钩子的设置。并将该函数的第一个參数设置为WH_GETMESSAGE,即监视被投递到消息队列中的消息。

那么关于这样的方式的详细实现方法,由于并非我们讨论的重点,因此大家能够參考对应的资料。

我们这次所採用的是一种比較简单的办法,并不须要进行全局监控。而是监控explorer.exe进程。

       事实上绝大多数的进程都是由explorer.exe进程创建的。比方我们打开ProcessExplorer。然后运行一下我们上面所编写的程序:


图5

       可见在explorer.exe进程下有非常多的子进程,而我们的CreateProcessTest.exe正是其子进程的一员,包括由该程序所启动的notepad.exe程序。因此仅仅要我们对该进程进行监控,钩取其CreateProcess()函数。就能够达到我们想要实现的监控目的。当然,使用这样的方式并不见得能够起到绝对的监控效果。毕竟方法还是太简单了,可是基本能够达到我们希望的效果。

 

小结

       这次我们讨论了基于Ring3层的主动防御技术的基本原理。我们讨论的都是最简单的方式。希望大家能够弄清楚这次我们所解说的每个知识。这样在下次的编程实现中,才不会有困惑。


posted @ 2017-08-20 12:41  clnchanpin  阅读(607)  评论(0编辑  收藏  举报