一. 无处可寻的病毒
大学生张云毕业后在一家公司担任计算机维护员的工作,这天主任把他找去维修一台出现异常的计算机,这台计算机上什么程序都未运行,可是机内安装的卡巴斯基杀毒软件却在不停的提示在系统目录发现特洛伊木马程序,而后自动进行查杀,可是刚查杀完毕就又跳出了同样的提示,一旦断开网络连接,这个现象立刻终止,再连接网络,立即再次提示发现特洛伊木马程序……如此反复循环,最终导致任何正常工作都无法进行。由于机器上储存有大量重要资料和数据库,如果要重装系统,后面的环境恢复工作必将十分庞大,主任通过杀毒软将无法解决,只好把所有希望都寄托在这名小伙子身上了。
张云信心十足的坐下来,凭着他往日积累的手工检测和查杀病毒的经验,很快就清理了一堆恶意程序和流氓软件,在经过几次慎重检查所有涉及的启动项都没有异常程序后,张云再次把网络给连接上了,正欲离开办公室,身后卡巴斯基发现病毒的嘶叫声猛的吓了他一跳,怎么还有病毒?张云茫然不知所措了……
二. 永无休止的躲藏
隐藏是病毒的天性,在业界对病毒的定义里,“隐蔽性”就是病毒的一个最基本特征,任何病毒都希望在被感染的计算机中隐藏起来不被发现,因为病毒都只有在不被发现的情况下,才能实施其破坏行为。为了达到这个目的,许多病毒使用了各种不同的技术来躲避反病毒软件的检验,这样就产生了各种各样令普通用户头痛的病毒隐藏形式。由于木马后门的行为特征已具备病毒条件,因此本文将木马后门也统一归纳为病毒来描述。
开山鼻祖:隐藏窗口 & 隐藏进程 & 隐藏文件
在计算机流行的早期,计算机病毒和木马后门等危害程序在普通用户范围的普及并不是很广泛,这个时期的用户群对计算机和网络安全的防范意识可以说是几乎没有的,普通用户的系统也多为脆弱的Windows 95/98系列和电话线拨号的慢速网络,而那一段时间正是外国木马“BO”和国产木马雏形“冰河”、“NetSpy”等在如今看来各方面技术都颇为简单的远程控制软件大行其道的黄金时期,很多用户根本就没有防火墙和杀毒软件(即使有,也是以杀CIH的为主),即使远方的黑客把用户的计算机翻了个底朝天,用户也不会有所察觉,这一时期接触此类技术的人相对较少,因此并未造成如今这个病毒到处蔓延的局面。
因为这个阶段国内用户的机器环境仍然以Windows 9x为主流,所以病毒编写者们并不需要消耗太多的脑筋就可以做到让病毒悄无声息运行的效果,并让它在Alt+Del+Ctrl呼出的任务管理器中不可见。
我们都知道,在Windows下运行的程序界面都被定义为“窗口”,程序通过这个途径与用户产生交互,每个完整的程序都必须拥有至少一个窗口,但是如果编写者将这个窗口在运行期间设置为“不可见”呢?这样一来,用户就不会察觉到这个程序在桌面上运行了,但是如果有一定经验的用户打开任务管理器,他就会因为发现系统里多出来的进程而产生怀疑,因此病毒编写者在这个时期采取了初级形式的隐藏手段:隐藏进程。
其实所谓隐藏进程,是利用微软未公开的一个API(Application Programming Interface,应用程序接口)函数“RegisterServiceProcess”将自身注册为“服务进程”,而恰巧Windows 9x中的任务管理器是不会显示此类进程的,结果就被病毒钻了空子,让“冰河”等木马在国内大部分普通用户的机器上安家落户。
而早期后门技术里,还有一个最基本的行为就是隐藏文件,与今天的各种隐藏手段相比,它可谓是“不入流”级别了——这里提到的“隐藏”,就是简单的将文件属性设置为“隐藏”而已,除此之外,再无别的保护手段了,然而,由于系统设计时为了避免初学者胡乱删除文件而默认“不显示系统和隐藏文件”的做法(到了Windows 2000/XP时代,这个做法更升级到“隐藏受保护的系统文件”了),却恰好给这些病毒提供了天然的隐身场所——大部分对电脑操作不熟悉的用户根本不知道“隐藏文件”的含义,更别提设置为“显示所有文件”了,在那个安全软件厂商刚开始探索市场的时代,用户更是不会留意太多安全产品及其实际含义,因而这个时期成了各种初期木马技术发展的重要阶段,利用这种手段制作的木马被统称为“第一代木马”。
以现在的技术和眼光看来,这些早期技术作品的发现和清理是相对较简单的了,因为它们采用的“进程隐藏”技术在NT体系上的Windows2000/XP/2003等操作系统上已经无效了,直接使用系统自带的任务管理器便能发现和迅速终止进程运行,而后在“控制面板”——“文件夹选项”里面设置“显示所有文件”和取消“隐藏受保护的系统文件”,就能发现那个被隐藏起来的木马程序了。对于Windows 9x用户,使用任意一款第三方的进程管理工具如“Windows优化大师”的进程管理组件即可轻松发现。
继续发展:使用线程注射技术的DLL木马
虽然现在使用“线程注射”的木马病毒和流氓软件已经遍地开花了,但是从那个混沌时代经历过来的人都不会忘记首个采用“线程注射”的DLL木马“广外幽灵”在当时所带来的恐惧,“线程注射”到底是种什么东西呢?下面就让我们来详细讲解一下。
首先,用户可能不会了解“线程”(Thread)的意思,而要讲解“线程”,就不能不先提到“进程”(Process)的概念。许多刚接触计算机的用户无法理解“进程”是什么东西:常常听到高手说打开任务管理器关闭某某进程,但是一看到任务管理器列表里的一堆东西,头就大了。许多用户知道使用任务管理器关闭一些失去响应的任务,但是如果某个任务没有在“应用程序”列表里出现,用户就不知所措了。到底什么是“进程”呢?“进程”是指一个可执行文件在运行期间请求系统在内存里开辟给它的数据信息块,系统通过控制这个数据块为运行中的程序提供数据交换和决定程序生存期限,任何程序都必须拥有至少一个进程,否则它不被系统承认。进程从某一方面而言就是可执行文件把自身从存储介质复制在内存中的映像,它通常和某个在磁盘上的文件保持着对应关系,一个完整的进程信息包括很多方面的数据,我们使用进程查看工具看到的“应用程序”选项卡包含的是进程的标题,而“进程”选项卡包含的是进程文件名、进程标识符、占用内存等,其中“进程文件名”和“进程标识符”是必须掌握的关键,“进程标识符”是系统分配给进程内存空间时指定的唯一数字,进程从载入内存到结束运行的期间里这个数字都是保持不变的,而“进程文件名”则是对应着的介质存储文件名称,根据“进程文件名”我们就可以找到最初的可执行文件位置。
任务管理器的“应用程序”项里列出来的“任务”,是指进程在桌面上显示出来的窗口对象,例如用户打开Word 2003撰写文档,它的进程“WINWORD.EXE”会创建一个在桌面上显示的前台窗口,这个窗口就是任务管理器里看得见的“任务”了,而实际上真正在运行的是进程“WINWORD.EXE”。并不是所有的进程都会在任务管理器里留下“任务”的,像QQ、MSN和所有后台程序,它们并不会在任务列表里出现,但是你会在进程列表里找到它们,如果要它们在任务列表里出现该怎么办呢?只要让它们产生一个在桌面上出现的窗体就可以了,随便打开一个好友聊天,就会发现任务列表里终于出现了QQ的任务。因此,真正科学的终止程序执行方案是针对“进程”来结束程序的运行,而不是在任务列表里关闭程序,因为木马作者们是不会让自己的木马在任务列表里出现的,但是进程列表里一般人都是逃不过的。
而“线程”,则是在一个进程里产生的多个执行进度实例,举个简单例子,一个网络文件传输程序如果只有一个线程(单线程)运作,那么它的执行效率会非常低下,因为它既需要从网络上读取文件数据,又需要把文件保存到磁盘,同时还需要绘制当前传输进度条,由于在代码的角度里这些操作只能一条条的顺序执行,程序就不能很好的做到在保存数据的同时绘制传输进度条,即使程序员将其勉强凑到一块执行,在用户方面看来,这个程序的响应会非常缓慢甚至直接崩溃,而“多线程”技术则是为了解决这种问题而产生的,采用“多线程”技术编写的应用程序在运行时可以产生多个同时执行的操作实例,例如一个采用“多线程”技术的网络文件传输程序就能同时分出三个进度来同时执行网络数据传输、文件保存操作和绘制传输进度条的操作,于是在用户看来,这个程序运行非常流畅,这就是线程的作用。在程序运行时,只能产生一个进程,但是在这个进程的内存空间(系统为程序能正常执行而开辟的独立内存领域)里,可以产生多个线程,其中至少有一个默认的线程,被称为“主线程”,它是程序主要代码的运行部分。
那么,“线程注射”又是什么含义呢?其实它的全称是“远程线程注射”(RemoteThread Injection),通常情况下,各个进程的内存空间是不可以相互访问的,这也是为程序能够稳定运行打下基础,这个访问限制让所有进程之间互相独立,这样一来,任何一个非系统关键进程发生崩溃时都不会影响到其他内存空间里的进程执行,从而使NT架构的稳定性远远高于Win9x架构。但是在一些特定的场合里,必须让进程之间可以互相访问和管理,这就是“远程线程”技术的初衷,这个技术实现了进程之间的跨内存空间访问,其核心是产生一个特殊的线程,这个线程能够将一段执行代码连接到另一个进程所处的内存空间里,作为另一个进程的其中一个非核心线程来运行,从而达到交换数据的目的,这个连接的过程被称为“注射”(Injection)。远程线程技术好比一棵寄生在大树上的蔓藤,一旦目标进程被注射,这段新生的线程就成为目标进程的一部分代码了,只要目标进程不被终止,原进程无论是否还在运行都不会再影响到执行结果了。
与“线程注射”离不开的是“Hook”技术,这个“Hook”,又是什么呢?其官方定义如下:
钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
在这里,木马编写者首先把一个实际为木马主体的DLL文件载入内存,然后通过“线程注射”技术将其注入其他进程的内存空间,最后这个DLL里的代码就成为其他进程的一部分来实现了自身的隐藏执行,通过调用“Hook”机制,这个DLL木马便实现了监视用户的输入输出操作,截取有用的资料等操作。这种木马的实际执行体是一个DLL文件,由于Windows系统自身就包含着大量的DLL文件,谁也无法一眼看出哪个DLL文件不是系统自带的,所以这种木马的隐蔽性又提高了一级,而且它的执行方式也更加隐蔽,这是由Windows系统自身特性决定的,Windows自身就是大量使用DLL的系统,许多DLL文件在启动时便被相关的应用程序加载进内存里执行了,可是有谁在进程里直接看到过某个DLL在运行的?因为系统是把DLL视为一种模块性质的执行体来调用的,它内部只包含了一堆以函数形式输出的模块,也就是说每个DLL都需要由一个用到它的某个函数的EXE来加载,当DLL里的函数执行完毕后就会返回一个运行结果给调用它的EXE,然后DLL进程退出内存结束这次执行过程,这就是标准的DLL运行周期,而采用了“线程注射”技术的DLL则不是这样,它们自身虽然也是导出函数,但是它们的代码是具备执行逻辑的,这种模块就像一个普通EXE,只是它不能直接由自身启动,而是需要有一个特殊作用的程序(称为加载者)产生的进程把这个DLL的主体函数载入内存中执行,从而让它成为一个运行中的木马程序。了解Windows的用户都知道,模块是紧紧依赖于进程的,调用了某个模块的进程一旦退出执行,其加载的DLL模块也就被迫终止了,但是在DLL木马里,这个情况是不会因为最早启动的EXE被终止而发生的,因为它使用了“远程线程注射”技术,所以,在用户发现异常时,DLL木马早就不知道被注入哪个正常进程里了,即使用户发现了这个木马DLL,也无法把它终止,因为要关闭它就必须在那么多的系统进程里找到被它注射的进程,并将其终止,对一般用户来说,这是个不可能完成的任务。
自从“广外幽灵”开创了DLL木马时代的先河以来,现在采用线程注射的DLL木马和恶意程序已经随处可见了,除了普遍被采用的另行编写DLL加载器程序躲在启动项里运行加载DLL主体之外,“求职信”还带来了一种比较少见的通过注册表“HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs”项目加载自身DLL的启动方法,而相对于以上几种早期方法,现在更有一种直接利用系统服务启动自身的木马程序,这才是真正的难缠!
“服务”是Windows系统的一大核心部分,在NT架构系统中,服务是指执行指定系统功能的程序、例程或进程,以便支持其他程序,尤其是底层(接近硬件)程序。通过网络提供服务时,服务可以在Active Directory中发布,从而促进了以服务为中心的管理和使用。服务是一种应用程序类型,它在后台运行。服务应用程序通常可以在本地和通过网络为用户提供一些功能,例如客户端/服务器应用程序、Web服务器、数据库服务器以及其他基于服务器的应用程序。 “服务”自身也是一种程序,由于使用的领域和作用不同,服务程序也有两种形式:EXE和DLL,采用DLL形式的服务是因为DLL能实现Hook,这是一些服务必需的数据交换行为,而NT架构系统采用一个被称为“svchost.exe”的程序来执行DLL的加载过程,所有服务DLL都统一由这个程序根据特定分组载入内存,然而,如今越来越多病毒作者瞄上了这个系统自带的加载器,因为它永远也不能被查杀。
病毒作者将木马主体写成一个符合微软开发文档规范的服务性质DLL模块文件,然后通过一段安装程序,将木马DLL放入系统目录,并在服务管理器(SCM)里注册自身为通过svchost.exe加载的服务DLL组件之一,为了提高隐蔽性,病毒作者甚至直接替换系统里某些不太重要而默认开启的服务加载代码,如“Distributed Link Tracking Client”,其默认的启动命令是“svchost -k netsvcs”,如果有个病毒替换了启动命令为自己建立的分组“netsvsc”,即“svchost -k netsvsc”,在这种旁门左道加社会工程学的攻势下,即使是具备一般查毒经验的用户也难以在第一时间内察觉到问题出自服务项,于是病毒得以成功逃离各种查杀。
目前被发现使用此方法的木马已经出现,其中一个进程名为“AD1.exe”的广告程序就是典型例子,它通过替换“Distributed Link Tracking Client”服务的svchost启动项来躲过一般的手工查杀,同时它自身还是个病毒下载器,一旦系统感染了这个恶意程序,各种木马都有可能光临你的机器。
要清理DLL木马,用户需要借助于Sysinternals出品的第三方进程管理工具“Process Explorer”,利用它的“Find Handle or DLL”功能,能迅速搜索到某个DLL依附的进程信息并终结,让DLL失去载体后就能成功删除,而DLL木马的文件名为了避免和系统DLL发生冲突,一般不会起得太专业,甚至有“safaf.dll”、“est.dll”这样的命名出现,或者在某些系统下根本不会出现的文件名,如“kernel.dll”、“rundll32.dll”等。除了使用“Process Explorer”查找并终止进程以外,还可以用IceSword强行卸载某个进程里的DLL模块来达到效果。
对于服务性质的DLL,我们仍然使用“Process Explorer”进行查杀,由于它的层次结构,用户可以很直观的看到进程的启动联系,如果一台机器感染了杀不掉的顽固木马,有经验的用户做的第一件事情就是禁止掉不相关或者不重要的程序和服务在开机时运行,然后使用“Process Explorer”观察各个进程的情况,通过svchost.exe启动的DLL木马虽然狡猾,但是它释放出EXE文件运行时,一切都暴露了:一个svchost.exe服务进程执行了一个AD1.exe,还有比这更明显的吗?
Svchost的分组信息位于注册表的“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost”项目,这是svchost加载DLL时的分组依据,如果用户发现了一个奇怪的分组信息,那就要提高警惕了。
隐藏技术发展的颠峰:Rootkit木马
随着安全技术的发展和计算机用户群的技术提高,一般的木马后门越来越难生存,于是一部分有能力的后门作者把眼光投向了系统底层——Ring 0。位于Ring 0层的是系统核心模块和各种驱动程序模块,所以位于这一层的木马也是以驱动的形式生存的,而不是一般的EXE。后门作者把后门写成符合WDM规范(Windows Driver Model)的驱动程序模块,把自身添加进注册表的驱动程序加载入口,便实现了“无启动项”运行。一般的进程查看器都只能枚举可执行文件EXE的信息,所以通过驱动模块和执行文件结合的后门程序便得以生存下来,由于它运行在Ring 0级别,拥有与系统核心同等级的权限,因此它可以更轻易的把自己隐藏起来,无论是进程信息还是文件体,甚至通讯的端口和流量也能被隐藏起来,在如此强大的隐藏技术面前,无论是任务管理器还是系统配置实用程序,甚至系统自带的注册表工具都失去了效果,这种木马,就是让人问之色变的Rootkit。
要了解Rootkit木马的原理,就必须从系统原理说起,我们知道,操作系统是由内核(Kernel)和外壳(Shell)两部分组成的,内核负责一切实际的工作,包括CPU任务调度、内存分配管理、设备管理、文件操作等,外壳是基于内核提供的交互功能而存在的界面,它负责指令传递和解释。由于内核和外壳负责的任务不同,它们的处理环境也不同,因此处理器提供了多个不同的处理环境,把它们称为运行级别(Ring),Ring让程序指令能访问的计算机资源依次逐级递减,目的在于保护计算机遭受意外损害——内核运行于Ring 0级别,拥有最完全最底层的管理功能,而到了外壳部分,它只能拥有Ring 3级别,这个级别能操作的功能极少,几乎所有指令都需要传递给内核来决定能否执行,一旦发现有可能对系统造成破坏的指令传递(例如超越指定范围的内存读写),内核便返回一个“非法越权”标志,发送这个指令的程序就有可能被终止运行,这就是大部分常见的“非法操作”的由来,这样做的目的是为了保护计算机免遭破坏,如果外壳和内核的运行级别一样,用户一个不经意的点击都有可能破坏整个系统。
由于Ring的存在,除了由系统内核加载的程序以外,由外壳调用执行的一般程序都只能运行在Ring 3级别,也就是说,它们的操作指令全部依赖于内核授权的功能,一般的进程查看工具和杀毒软件也不例外,由于这层机制的存在,我们能看到的进程其实是内核“看到”并通过相关接口指令(还记得API吗?)反馈到应用程序的,这样就不可避免的存在一条数据通道,虽然在一般情况下它是难以被篡改的,但是不能避免意外的发生,Rootkit正是“制造”这种意外的程序。简单的说,Rootkit实质是一种“越权执行”的应用程序,它设法让自己达到和内核一样的运行级别,甚至进入内核空间,这样它就拥有了和内核一样的访问权限,因而可以对内核指令进行修改,最常见的是修改内核枚举进程的API,让它们返回的数据始终“遗漏”Rootkit自身进程的信息,一般的进程工具自然就“看”不到Rootkit了。更高级的Rootkit还篡改更多API,这样,用户就看不到进程(进程API被拦截),看不到文件(文件读写API被拦截),看不到被打开的端口(网络组件Sock API被拦截),更拦截不到相关的网络数据包(网络组件NDIS API被拦截)了,我们使用的系统是在内核功能支持下运作的,如果内核变得不可信任了,依赖它运行的程序还能信任吗?
但即使是Rootkit这一类恐怖的寄生虫,它们也并非所向无敌的,要知道,既然Rootkit是利用内核和Ring 0配合的欺骗,那么我们同样也能使用可以“越权”的检查程序,绕过API提供的数据,直接从内核领域里读取进程列表,因为所有进程在这里都不可能把自己隐藏,除非它已经不想运行了。也就是说,内核始终拥有最真实的进程列表和主宰权,只要能读取这个原始的进程列表,再和进程API枚举的进程列表对比,便能发现Rootkit进程,由于这类工具也“越权”了,因而对Rootkit进行查杀也就不再是难事,而Rootkit进程一旦被清除,它隐藏自身的措施也就不复存在,内核就能把它“供”出来了,用户会突然发现那个一直“找不到”的Rootkit程序文件已经老实的呆在文件管理器的视图里了。这类工具现在已经很多,例如IceSword、Patchfinder、gdb等。
道高一尺,魔高一丈,因为目前的主流Rootkit检测工具已经能检测出许多Rootkit木马的存在,因此一部分Rootkit作者转而研究Rootkit检测工具的运行检测算法机制,从而制作出新一代更难被检测到的木马——FUTO Rootkit。
国产优秀检测工具IceSword在FUTO面前败下阵来,因为FUTO编写者研究的检测工具原型就是一款与之类似的Black & Light,所以我们只能换用另一款Rootkit检测工具DarkSpy,并开启“强力模式”,方可正常查杀Rootkit。
但是由于检测机制的变化,DarkSpy要检测到FUTO的存在,就必须保证自己的驱动比FUTO提前加载运行,这就涉及到优先级的问题,也是让业界感觉不太满意的一种方式,因为这样做的后果会导致系统运行效率下降,不到紧急关头,都不要轻易采用这种方法,然而现在的瑞星卡卡助手所推广的“破甲”技术,实现原理是与之类似的,它也会对系统造成一定影响,因而,这个介于安全和效率之间的选择,唯有留给用户自己思考了。
另一种隐藏:给自己做个“壳”
今年底,国内杀毒软件厂商瑞星推出了2007测试版,细心的用户应该都能观察到,瑞星这次更新的重点基本在于“脱壳技术”,这个“壳”是什么,为什么厂商那么重视呢?
“壳”(Shell),顾名思义,就是一种包裹容器,在计算机方面,它指一种把应用程序压缩精简或者加密处理后用自身代码形成一个新程序的技术,“壳”在运行时将自身包裹的程序资源释放到内存中执行,就恢复了原来程序的面目,由于“壳”的初衷就是加密和精简程序文件的体积,因此许多杀毒软件其实根本无法检测出一个加了壳的病毒,因为针对“壳”而产生的脱壳技术相对复杂,如何完善的检测出大部分被“壳”处理过的病毒一直是业界的难题,利用这一特点,一部分病毒利用“壳”把自身包裹起来,因为杀毒软件对其无能为力,病毒便能先发制人,把杀毒软件消灭以后才释放真实的病毒文件运行感染,这种明目张胆的隐藏可谓恶毒。
由于各种原因,我们只能等待杀毒厂商提供一套完善的解决方案,因为手工脱壳对于一般用户来说是非常不实际的。
<大杂烩:混合型木马
“灰鸽子”,国内一款优秀的远程控制工具,同时也是危害广大用户的木马病毒,它是目前主流的一种结合了Rootkit驱动、远程线程注射的混合型DLL木马,它将两个技术整合起来,最终形成了这种在正常模式下无法发现进程和文件的强大后门。
针对此类病毒,用户需要结合IceSword和Process Explorer等工具发现被篡改的SSDT和木马DLL文件,而后进入安全模式删除。现在已经有流氓软件掌握了高优先级启动方法,使得其在安全模式下也能正常运作,如果遭遇这种恶劣病毒,用户只能求助于DOS了。
三. “进化论”和“安全威胁”
病毒技术一刻不停的发展着,与之相对的反病毒技术也在追逐,这场猫和老鼠的大战永远不会停止,战争过后,留下的是面目全非的操作系统。虽然从某个角度来看,这种技术追逐会给业界带来无数种计算机科技发展的可能,但是,普通用户要在网络上被迫“适者生存”的时代,还是不要到来的好。