6.4 安全工具开发基础
黑客攻防一直都在共同进步、发展着,一正一邪,而正邪只在一念之差。黑客攻防从技术层面上看完全没有差别,其差别只在于使用者的想法。前面介绍了恶意程序的编写,那么自然也要介绍一些关于安全工具的开发,从而保证了安全。
6.4.1 行为监控工具开发基础
现在有一种流行的防病毒软件被称作HIPS,中文名字叫做主机防御系统,比如EQ。该软件可以在进程创建时、有进程对注册表进行写入时或有驱动被加载时,给用户予以选择,选择是否拦截进程的创建、是否拦截注册表的写入、是否拦截驱动的加载等功能。
HIPS纯粹是以预防为主,比如有陌生的进程在被创建阶段,就可以让用户禁止,这样就避免了特征码查杀的滞后性。对于杀毒软件的特征码查杀而言,如果杀毒软件不更新病毒数据库,那么依赖病毒特征码的杀毒软件就无法查杀新型的病毒了,那么对新型的病毒就成为一个摆设了。
行为监控的原理主要就是对相关的关键API函数进行HOOK,比如前面介绍的进程拦截。当一个木马程序要秘密启动的时候,对CreateProcessW()函数进行了HOOK,在进程被创建前,会询问用户是否启动该进程,那么木马的隐秘启动就被暴露出来了。对于没有安全知识的大众来说,也许仍然会让木马运行,但是木马的运行可以被我们所发现了。对于没有安全知识的大众来说,使用HIPS可能有点困难。因为不是每个使用计算机的人都是对计算机有所了解的,计算机对于他们而言可能只用来打打游戏,或看看电影之类的。这该如何做呢?现在通常使用的方法就是使用白库和黑库,也就是所谓的白名单和黑名单。在进程被创建时,把要创建的进程到黑白库中去匹配,然后做相应的动作,或者放行,或者拦截。
下面就来实现一个应用层下的简单的进程防火墙,注册表防火墙的功能。
一、简单进程防火墙
进程防火墙指的是放行/拦截准备要创建的进程。通过前面章节的介绍我们知道,进程的创建是依靠CreateProcessW()函数完成的。只要HOOK CreateProcessW()函数就可以实现进程防火墙的功能。对于注册表来说,要对非法进程进行删除或写入注册表键值进行管控,因此需要HOOK两个注册表函数,分别是注册表写入函数RegSetValueExW()和注册表删除函数RegDeleteValueW()。由于使用了HOOK,那么就必然要涉及DLL的编写,我们分DLL和EXE两部分来进行详细的介绍。
二、实现HOOK部分的DLL程序的编写
因为要对目标进程进行HOOK,因此要编写DLL程序。我们创建一个DLL程序,并加入前面的已封装的ILHook.h头文件和ILHook.cpp的实现文件。
为了能在所有的基于消息的进程中注入我们的DLL,必须使用Windows钩子,这样就可以将DLL轻易地注入到基于消息的进程中。代码如下:
以上函数用来定义导出函数,用于加载完成HOOK功能的DLL文件。这里利用WH_GETMESSAGE这个钩子类型,在前面的内容中是介绍过的,这里就不做过多的介绍了。
定义3个CILHook类的对象,分别用来对CreateProcessW()函数、RegSetValueExW()函数和RegDeleteValueW()函数进行挂钩。定义如下:
HOOK部分是在DllMain()函数中完成的,代码如下:
对于放行/拦截这部分是给用户选择的,那么就要给出提示让用户进行选择,至少要给出放行/拦截的类型,比如是注册表写入或是进程的创建,还要给出是哪个进程进行的操作。要把这个信息反馈给用户,我们定义一个结构体,将该结构体的信息发送给用于加载DLL的EXE文件,并让EXE给出提示。结构体定义如下:
定义一些常量用来标识放行/拦截的类型,定义如下:
将这些定义好以后,就可以开始完成HOOK函数了,我们主要给出CreateProcessW()函数的HOOK实现,其余两个函数的HOOK实现请大家自行实现。代码如下:
这里使用了一个SendMessage()函数,该函数用来发送一个WM_COPYDATA消息,将结构体传给了加载DLL的EXE程序,使EXE程序把提示显示给用户。
SendMessage()函数的功能非常强大,该函数的定义如下:
该函数的第一个参数是目标窗口的句柄,第二个参数是消息类型,最后两个参数是消息的附加参数,根据消息类型的不同而不同。
以上代码就是DLL程序的全部了,剩下两个对注册表操作的HOOK函数由大家自己完成。
三、行为监控前台程序的编写
先来看一下程序能达到的效果,然后再给大家讲解程序EXE部分的实现代码,如图6-22和图6-23所示。
图6-22 程序主界面
从上面两个图可以看出,程序的确是可以拦截进程的启动的,当单击“允许”按钮后,进程会被正常创建,当单击“取消”按钮后,进程将被阻止创建。这就是我们最终要完成的功能,来看看主要的实现代码。
图6-23 拦截提示框
EXE的部分主要就是如何来启动行为监控功能,还有就是如果接收DLL程序通过SendMessage()函数发出的消息来给用户弹出提示框。进行拦截的部分已经在DLL程序中通过HOOK实现了,所以重点也就在界面上和消息的接收上了。
先看如何启动和停止行为的监控。代码如下:
从代码中不难看出,直接调用了DLL的两个导出函数,就可以开启我们的打开。在关闭时为什么调用了CloseHandle()函数和FreeLibrary()函数呢。大家把FreeLibrary()函数去掉,然后单击“停止”监控行为,但是还是处在被监控的状态下。因为恢复Inline Hook是在DLL被卸载的情况。因此,在卸载时,我们调用GetModuleHandle()获得本进程的DLL句柄后,虽然CloseHandle()了,但是只是减少了对DLL的引用计数,并没有真正的释放,必须再次使用FreeLibrary()函数才可以使DLL被卸载掉,从而恢复Inline Hook。
对于EXE程序接收DLL消息的代码如下:
这部分代码就是对WM_COPYDATA消息的一个响应,整个代码基本是对界面进行了操作。在代码中有一个CTips类的对象,这个类是用来自定义窗口的。该窗口就是用来提示放行和拦截的窗口。带窗口的主要代码如下:
DLL程序中的SendMessage()函数的返回要等待WM_COPYDATA的消息结束,并从中获得返回值来决定下一步是否执行,因此这里只要简单地返回TRUE或FALSE即可。
对于行为监控,就介绍这么多。这个例子演示了如何通过Inline Hook来达到对进程创建、注册表操作的管控。当然,我们的代码并不能管控所有的进程,而且我们的行为监控过于简单,很容易被恶意程序突破。我们主要是通过实例来完成对行为监控原理的学习,希望可以起到抛砖引玉的作用。
6.4.2 专杀工具
现在免费的杀毒软件越来越多了,例如360安全卫士、金山毒霸、瑞星……有很多人不会去安装杀毒软件,如果爆发了传播速度较快,感染规模较大的病毒或蠕虫的话,杀毒厂商会为了阻止这种比较“暴力”的病毒或蠕虫的传播与感染,会推出免费的供网友使用的专杀工具。
专杀工具是针对某一个或某一类的病毒、木马或蠕虫等恶意软件开发的工具。专业的杀毒软件需要专业的反病毒公司来进行开发,而专杀工具可能是由反病毒公司开发,也可能是由个人来进行开发。
一、病毒分析方法简介
对于编写专杀工具,除非是感染型病毒,通常情况下并不需要对病毒做逆向分析,只需要对病毒进行行为分析就可以编写专杀工具。而如果病毒是感染型的,需要修复被病毒感染的文件,那么就不能只是简单地对病毒进行行为分析,必须对病毒进行逆向分析,从而修复被病毒所感染的文件。
(1)行为分析。病毒、木马等恶意程序都有一些比较隐蔽的“小动作”,而这些动作一般情况下是正常程序所没有的。比如,把自己添加进启动项,或把自己的某个DLL文件注入到其他进程中去,或把自己复制到系统目录下……这些行为都不是正常的行为。我们拿到一个病毒样本以后,通常是将病毒复制到虚拟机中,然后打开一系列的监控工具,比如注册表监控、文件监控、进程监控、网络监控等,将各种准备工作做好以后,在虚拟机中把病毒运行起来,看病毒对注册表进行了哪些操作,对文件进行了哪些操作,连接了哪个IP地址、创建了多少进程。通过观察这一系列操作,就可以写一个程序。只要把它创建的进程结束掉,把它写入注册表的内容删除掉,把它新建的文件删除掉,就等于把这个病毒杀掉了。当然整个过程并不会像说起来这么容易。
(2)逆向分析。当病毒感染了可执行文件以后,感染的是什么内容是无法通过行为监控工具发现的。而病毒对可执行文件的感染,有可能是添加一个新节来存放病毒代码,也可能是通过节与节之间的缝隙来存放病毒代码的。无论是哪种方式,都需要通过逆向的手段进行分析。通常的逆向工具有OD、IDA、WinDBG。
二、病毒查杀方法简介绍
病毒的查杀方法有很多种,在网络安全日益普及的今天,在杀毒软件公司大力宣传的今天,想必大部分关心网络安全的人对于病毒查杀的技术有了一些了解。当今常见的主流病毒查杀技术有特征码查杀、启发式查杀、虚拟机查杀和主动防御等。下面简单地说说特征码查杀和启发式查杀两种病毒查杀方法。
(1)特征码查杀。特征码查杀是杀毒软件厂商查杀病毒的较为原始的一种方法。该方法是通过从病毒体内提取病毒特征码,从而能够有效识别出病毒。这种方法只能查杀已知病毒,对于未知病毒则无能为力。
(2)启发式查杀。静态地通过一系列的“带权规则组合”对文件进行判定,如果值高于某个界限则被认定为病毒,否则不为病毒。启发式查杀可以相对有效地识别出病毒,但是往往也会出现误报的情况。
我们来编写一个简单的病毒专杀工具,这个病毒专杀工具非常简单,用到的知识都是前面的知识。先准备一个简单的病毒样例,然后在虚拟机中进行一次行为分析,然后写出一个简单的病毒专杀工具。虽然前面的例子都有代码让大家练习,但是这次我们要对病毒进行行为分析,然后编写代码,完成我们的专杀工具。
我们将在虚拟机中进行病毒分析,因此安装虚拟机是一个必须的步骤。虚拟机也是一个软件,它用来模拟计算机的硬件,在虚拟机中可以安装操作系统,安装好操作系统后可以安装各种各样的应用软件,与操作真实的计算机是没有任何区别的。在虚拟机中的操作完全不影响我们真实的系统。除了对病毒进行分析需要安装虚拟机以外,在进行双机调试系统内核时安装虚拟机也是不错的选择。在虚拟机中安装其他种类的操作系统也非常方便,总之使用虚拟机的好处非常多。这里使用VMware这款虚拟机,请大家自行安装。
在安装好虚拟机以后,在虚拟机上放置几个行为分析的工具,包括FileMon、RegMon和Procexp三个工具。分别对几个工具进行设置,对FileMon和RegMon进行字体设置,并设置过滤选项,如图6-24和图6-25所示。
图6-24 对RegMon设置过滤
对于FileMon和RegMon的字体设置在菜单“选项”-> “字体”命令下,通常笔者选择“宋体”、“9号”,大家可以根据自己的喜好进行设置。在设置过滤条件时,“包含”后面输入的是需要监控的文件,这里的“a2.exe”是病毒的名字。也就是说,只监控与该病毒名相关的操作。
图6-25 对FileMon设置过滤
下面对Procexp进行设置,我们需要在进程创建或关闭时持续5秒钟高亮显示进程,以进行观察。设置方法为单击菜单“Options” -> “Difference Highlight Duration”命令,在弹出的对话框中设置“Difference Highlight Duration”为“5”,如图6-26所示。
将上面几个工具都设置完后,运行一下病毒,观察几个工具的反应,如图6-27、图6-28和图6-29所示。
图6-26 对Procexp的设置
图6-27 在Procexp中看到的mirwzntk.exe的病毒进程
图6-28 RegMon对注册表监控的信息
在图6-27中看到的病毒进程名为“mirwzntk.exe”,而不是“a2.exe”。这个进程是病毒“a2.exe”创建的,在病毒做完其相关工作后,将自己删除。图6-28和图6-29不一一进行说明了,下面作一下行为分析的总结。
图6-29 FileMon对文件监控的信息
病毒在注册中写入了一个值,值的内容为 “mirwznt.dll”,写入的位置如下: HKLM\SOFTWARE\MICROSOFT\WINDOWSNT\CURRENTVERSION\WINDOWS\APPINIT _DLLS。病毒在C:\WINDOWS\system32\下创建了两个文件,分别是“mirwznt.dll”和 “mirwzntk.exe,创建病毒进程mirwzntk.exe,并生成了一个.bat的批处理程序用于删除自身,也就是删除“a2.exe”。
下面来写一个专杀工具对该病毒进行查杀,如图6-30所示。
图6-30 mirwzntk病毒的专杀工具
四、编写病毒专杀工具之专杀工具编写
在查杀病毒的技术中有一种方法类似特征码查杀法,这种方法并不从病毒体内提取特征码,而是计算病毒的散列值。也就是对病毒的大小进行散列计算,然后在查杀的过程中计算每个文件的散列,然后进行比较。这种方法简单且易实现,常见的有MD5、CRC32等一些计算散列的算法。
下面选用CRC32算法计算函数的散列值,这里给出一个现成的CRC32函数,只需直接调用就可以了,代码如下:
该函数的参数有两个,一个是指向缓冲区的指针,第二个是缓冲区的长度。将文件全部读入缓冲区内,然后用CRC32函数计算文件的CRC32散列值。看一下查杀的源代码吧。
整个代码的内容都是前面介绍过的知识,这里只是一个综合的应用。对于有些情况,需要对磁盘的文件进行遍历,那么可以使用FindFirstFile0和FindNextFile0这两个函数,或者是用CFileFind类对磁盘文件进行遍历。其具体使用方法请参考MSDN,这里只给出一段简单的参考代码供大家学习使用。
6.4.3 U盘防御软件
在早期互联网还不发达的时候,病毒都是通过软盘、光盘等媒介进行传播的。到后来互联网被普及以后,通过互联网进行传播的病毒大面积地相继出现。虽然软盘已经被淘汰,但是并没有因为软盘的淘汰而使移动磁盘的病毒减少。相反,U盘的普及使得移动磁盘对病毒的传播更加得方便。U盘的数据传输速度和数据存储容量等多方面都比软盘要先进很多,因此,软盘可以传播病毒,U盘当然也可以传播病毒。
通过U盘来传播病毒通常是使用操作系统的自动运行功能,并配合U盘下的Autorun.inf文件来实现的。如果让操作系统不自动运行移动磁盘,或者保证移动磁盘下不存在Autorun.inf文件,那样通过U盘感染病毒的几率就小很多了。
一、通过系统配置禁止自动运行
先介绍一下关于如何通过系统配置来禁止U盘中Autorun.inf的自动运行。通常情况下需要进行两方面的设置,一方面是通过“管理工具”中的“服务”来进行设置,另一方面是通过“组策略”来进行设置。一般这两处都需要进行修改。下面分别介绍一下如何对这两处进行设置。
先来看一下如何在“服务”中进行设置。首先打开控制面板中的“管理工具”,然后找到 “服务”将其双击打开。在服务列表中找到名称为“Shell Hardware Detection”的服务,双击该服务,打开“Shell Hardware Detection的属性”对话框。单击“停止”按钮将该服务停止,再把“启动类型”修改为“已禁用”状态,如图6-31所示。
将服务中的“Shell Hardware Detection”禁用后,再来对“组策略”进行设置。首先在“运行”中输入“gpedit.msc”,然后依次单击左边的树形控件,“计算机配置”-> “管理模板”-> “系统”,然后在右边双击“关闭自动播放”选项,弹出“关闭自动播放属性”对话框,在“设置”选项卡中选择“已启用”单选项,在”关闭自动播放“处选择”所有驱动器“选项,设置完成后单击”确定“按钮。再到左边的树形控件中选择“用户配置”-> “管理模板”-> “系统”,到右边找到“自动关闭播放”选项,设置方法同上,如图6-32所示。
图6-31 禁用“Shell Hardware Detection”服务
图6-32 组策略中的“关闭自动播放”
通过以上设置,的确是可以相对有效地保护计算机不中U盘相关的病毒。不过,我们不能因此而满足,因为我们的目的是打造一个U盘防御的软件。
二、打造一个简易的U盘防御软件
我们将打造一个U盘防火墙,当插入U盘时会有提示,并且自动检查U盘下是否有 Autorun.inf文件,并解析Autorun.inf文件。除此而外,通过U盘防火墙可以打开U盘,从而可以安全地使用U盘。
如何才能知道有U盘被插入电脑呢?可以使用定时器不断地去检查,也可以开启一个线程不断地去检查,也可以通过Windows的消息得到通知。前两种方法笨了些,我们主动不断地去检查是否有U盘的插入,不如被动地等待Windows的消息来通知我们。
三、WM_DEVICECHANGE和OnDeviceChange()
在Windows下有一个消息可以通知应用程序计算机配置发生了变化,这个消息是 WM_DEVICECHANGE。该消息的消息过程定义如下:
该消息通过两个附加参数来进行使用,其中wParam表示设备改变的事件,1Param表示事件对应的数据。我们要得到设备被插入的消息类型,因此wParam的取值为 DBT_DEVICEARRIVAL,而该消息对应的数据的数据类型为DEV_BROADCAST_HDR,该结构体的定义如下:
在该结构体中,主要看的是dbch_devicetype,也就是设备的类型。如果设备类型为 DBT_DEVTYP_VOLUME,则把当前结构体转换为DEV_BROADCAST_VOLUME结构体,该结构体定义如下:
在该结构体中主要是dbcv_unitmask和dbcv_flags。dbcv_unitmask通过位来表示逻辑盘符,第0位表示A盘,第1位表示B盘。dbcv_flags表示受影响的盘符或媒介,该值为0时表示U盘或移动硬盘。
上面介绍了关于WM_DEVICECHANGE这个消息,由于是在MFC下进行开发的,因此可以使用OnDeviceChange()这个消息响应函数来进行代替WM_DEVICECHANGE消息。虽然使用了OnDeviceChange()这个消息响应函数而没有使用WM_DEVICECHANGE,但是响应函数的附加参数与WM_DEVICECHANGE相同。OnDeviceChange()函数定义如下:
四、通过OnDeviceChange()消息来获得被插入U盘的盘符
通过上面对WM_DEVICECHANGE消息的介绍,下面使用MFC下的OnDeviceChange()消息响应函数来编写一个获取被插入U盘的盘符的小程序,为编写U盘防火墙做一个简单的准备工作。首先来添加消息映射,添加代码如下:
在头文件中添加消息响应函数的定义,定义如下:
最后添加消息响应函数的实现,代码如下:
我们将其编译连接并运行,插入一个U盘,得到如图6-33所示的提示。
图6-33 检测到的U盘盘符
上面的这段代码可以将其封装为一个函数,封装后的函数定义如下:
在使用类似DBTDEVICEARRIVAL这样以DBT开头的宏时应包含头文件”“dbt.h”文件。
五、U盘防火墙的完善
前面已经获得了被插入U盘的盘符,接下来就可以对U盘上的Autorun.inf文件进行分析,并删除要运行的程序,还可以安全地打开U盘。改写一下OnDeviceChange()函数,以实现要完成的功能,代码如下:
安全打开U盘的实现代码如下:
用DeleteFile()函数删除U盘中要运行的程序可能会失败,因为有时U盘并没有完全准备好。可以通过判断来完成,但这里就不给出代码了,大家可自行修改完成。在代码中,涉及到两个新的API函数,分别是GetPrivateProfileString()和ShellExecute()函数。这两个函数的功能分别是获取配置文件中指定键的键值,和运行指定的文件或文件夹,大家可以通过查询MSDN进行详细地了解。
在上面的程序中,通过提示让用户选择是否要删除掉U盘中要被执行的文件,如果用户对此并没有太多的了解和认识的话很有可能不删除。如果删了本不该删的文件,那么用户对该软件的友好感便会降低,那应该如何做呢?应该建立一个白名单和黑名单,无论是通过散列进行比较,还是通过文件名进行比较,都可以。当然,越精确的匹配方法越好。这样,就可以提早一步为用户进行判断了,然后给出一个安全建议,不但提高了软件的友好感,而且会显得相对较为专业了。
6.4.4 目录监控工具
我们介绍了通过HOOK技术对进程创建的监控,然后介绍了通过CRC32对病毒进行查杀,还介绍了通过使用WM_DEVICECHANGE消息对U盘的防护。接下来,简单讨论一下如何通过ReadDirectoryChangesW()来编写一个监视目录变化的程序。
对目录及目录中的文件实时监控,可以有效地发现文件被改动的情况。就好像,我们在本地安装了IIS服务器,并搭建了一个网站平台,有时候会遭到黑客的篡改,而我们无法及时地恢复被篡改的页面,导致出现了非常不好的影响。如果能及时地发现网页被篡改,并及时地恢复本来的页面就好了,那么该如何做呢?
下面通过一个简单的例子来介绍如何监控某目录及目录下文件的变动情况。首先需要了解的函数为ReadDirectoryChangesW(),该函数的定义如下:
参数说明如下。
(1) hDirectory:该参数指向一个要监视目录的句柄。该目录需要用 FILE_LIST_DIRECTORY的访问权限打开。
(2) lpBuffer:该参数指向一个内存的缓冲区,它用来存放返回的结果。结果为一个 FILE_NOTIFY_INFORMATION的数据结构。
(3) nBufferLength:表示缓冲区的大小。
(4) bWatchSubtree:该参数为TRUE时,表示监视指定目录下的文件及子目录下的文件操作。如果该参数为FALSE,则只监视指定目录下的文件,不包含子目录下的文件。
(5) dwNotifyfilter:该参数指定要返回何种文件变更后的类型,该参数的常量值参见 MSDN。
(6) lpBytesReturned:该参数返回传给lpBuffer结果的字节数。
(7) lpOverlapped:该参数执行一个OVERLAPPED结构体,该结构体用于异步操作,否则该数据为NULL。
目录监控完整源代码
该函数的使用非常简单,下面通过一个例子来学习该函数的使用。该例子是对E盘目录进行监控,我们将程序编写完成后对E盘进行简单的文件操作,以观察程序的输出结构。完整的代码如下:
将程序编译连接并运行,在E盘下进行简单的操作,查看程序对E盘的监视输出记录,如图6-34所示。
图6-34 目录监控输出记录
对于目录监视的这个例子,可以将其改为一个简单的文件防篡改程序。首先将要监视的文件目录进行一次备份,然后对文件目录进行监视,如果有文件发生了修改,那么就使用备份目录下的指定文件恢复被修改的文件。