9.7.2 病毒

打开报纸,总是能够看到关于病毒或蠕虫攻击计算机的新闻。它们显然已经成为现今影响个人和公司安全的主要问题。本节我们将介绍病毒,接下来将介绍蠕虫。

笔者在撰写本节时曾犹豫要不要给出太多的细节,担心它们会让一些人产生邪念。然而现在有很多书籍提供了更为详细的内容,有些甚至给出代码(Ludwig,1998)。而且互联网上也有很多病毒方面的信息,笔者写出的这些并不足以构成什么威胁。另外,人们在不知道病毒工作原理的情况下很难去防御它们,而且关于病毒的传播有许多错误的观念需要纠正。

那么,什么是病毒呢?长话短说,病毒(virus)是一种特殊的程序,它可以通过把自己植入到其他程序中来进行“繁殖”,就像生物界中真正的病毒那样。除了繁殖自身以外,病毒还可以做许多其他的事情。蠕虫很像病毒,但其不同点是通过自己复制自己来繁殖。不过这不是我们关注的重点,因此下面我们将用“病毒”来统称上面两种恶意程序。有关蠕虫的内容会在9.7.3节中讲解。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

1.病毒工作原理

让我们看一下病毒有那些种类以及它们是如何工作的。病毒的制造者,我们称之为Virgil,可能用汇编语言(或者C语言)写了一段很小但是有效的病毒。在他完成这个病毒之后,他利用一个叫做dropper的工具把病毒插入到自己计算机的程序里,然后让被感染的程序迅速传播。也许贴在公告板上,也许作为免费软件共享在Internet上。这一程序可能是一款激动人心的游戏,一个盗版的商业软件或其他能引人注意的软件。随后人们就开始下载这一病毒程序。

一旦病毒程序被安装到受害者的计算机里,病毒就处于休眠状态直到被感染的程序被执行。发作时,它感染其他程序并执行自己的操作。通常,在某个特定日期之前病毒是不执行任何操作的,直到某一天它认为自己在被关注前已被广泛传播时才发作。被选中的日期可能是发送一段政治信息(如在病毒编写者所在的宗教团体受辱的100周年或500周年纪念日触发)。

在下面的讨论中,我们来看一下感染不同文件的七种病毒。他们是共事者、可执行程序、内存、引导扇区、驱动器、宏以及源代码病毒。毫无疑问,新的病毒类型不久就会出现。

2.共事者病毒

共事者病毒(companion virus)并不真正感染程序,但当程序执行的时候它也执行。下面的例子很容易解释这个概念。在MS-DOS中,当用户输入


prog


MS-DOS首先查找叫做prog.com的程序。如果没有找到就查找叫做prog.exe的程序。在Windows里,当用户点击Start(开始)和Run(运行)后,同样的结果会发生。现在大多数的程序都是.exe文件,.com文件几乎很少了。

假设Virgil知道许多人都在MS-DOS提示符下或点击Windows的Run运行prog.exe。他就能简单地制造一个叫做prog.com的病毒,当人们试图运行prog(除非输入的是全名prog.exe)时就可以让病毒执行。当prog.com完成了工作,病毒就让prog.exe开始运行而用户显然没有这么聪明。

有时候类似的攻击也发生在Windows操作系统的桌面上,桌面上有连接到程序的快捷方式(符号链接)。病毒能够改变链接的目标,并指向病毒本身。当用户双击图标时,病毒就会运行。运行完毕后,病毒又会启动正常的目标程序。

3.可执行程序病毒

更复杂的一类病毒是感染可执行程序的病毒。它们中最简单的一类会覆盖可执行程序,这叫做覆盖病毒(overwriting virus)。它们的感染机制如图9-27所示。

阅读 ‧ 电子书库
图 9-27 在UNIX系统上查找可执行文件的递归过程

病毒的主程序首先将自己的二进制代码复制到数组里,这是通过打开argv[0]并将其读取以便安全调用来完成的。然后它通过将自己变为根目录来截断由原来的根目录开始的整个文件系统,将根目录作为参数调用search过程。

递归过程search打开一个目录,每次使用readdir命令逐一读取入口地址,直到返回值为NULL,说明所有的入口都被读取过。如果入口是目录,就将当前目录改为该目录,继续递归调用search;如果入口是可执行文件,就调用infect过程来感染文件,这时把要感染的文件名作为参数。以“.”开头的文件被跳过以避免“.”和“..”目录带来的问题。同时符号链接也被跳过,因为系统可以通过chdir系统调用进入目录并通过转到“..”来返回,这种对硬连接成立,对符号链接不成立。更完善的程序同样可以处理符号链接。

真正的感染程序infect(尚未介绍)仅仅打开在其参数中指定的文件并把数组里存放的病毒代码复制到文件里,然后再关闭文件。

病毒可以通过很多种方法不断“改善”。第一,可以在infect里插入产生随机数的测试程序然后悄然返回。如调用超过了128次病毒就会感染,这样就降低了病毒在大范围传播之前就被被检测出来的概率。生物病毒也具有这样的特性:那些能够迅速杀死受害者的病毒不如缓慢发作的病毒传播得快,慢发作给了病毒以更多的机会扩散。另外一个方法是保持较高的感染率(如25%),但是一次大量感染文件会降低磁盘性能,从而易于被发现。

第二,infect可以检查文件是否已被感染。两次感染相同的文件无疑是浪费时间。第三,可以采取方法保持文件的修改时间及文件大小不变,这样可以协助把病毒代码隐藏起来。对大于病毒的程序来说,感染后程序大小将保持不便;但对小于病毒大小的程序来说,感染后程序将变大。多数病毒都比大多数程序小,所以这不是一个严重的问题。

一般的病毒程序并不长(整个程序用C语言编写不超过1页,文本段编译后小于2KB),汇编语言编写的版本将更小。Ludwig(1998)曾经给出了一个感染目录里所有文件的MS-DOS病毒,用汇编语言编写并编译后仅有44个字节。

稍后的章节将研究反病毒程序,这种反病毒程序可以跟踪病毒并除去它们。而且,在图9-27里很有趣的情况是,病毒用来查找可执行文件的方法也可以被反病毒程序用来跟踪被感染的文件并最终清除病毒。感染机制与反感染机制是相辅相成的,所以为了更有效地打击病毒,我们必须详细理解病毒工作的原理。

从Virgil的观点来说,病毒的致命问题在于它太容易被发现了。毕竟当被感染的程序运行时,病毒就会感染更多的文件,但这时该程序就并不能正常运行,那么用户就会立即发现。所以,有相当多的病毒把自己附在正常程序里,在病毒发作时可以让原来的程序正常工作。这类病毒叫做寄生病毒(parasitic virus)。

寄生病毒可以附在可执行文件的前端、后端或者中间。如果附在前端,病毒首先要把程序复制到RAM中,把自己附加到程序前端,然后再从RAM里复制回来,整个过程如图9-28b所示。遗憾的是,这时的程序不会在新的虚拟地址里运行,所以病毒要么在程序被移动后重新为该程序分配地址,要么在完成自己的操作后缩回到虚拟地址0。

阅读 ‧ 电子书库
图 9-28 a)一段可执行程序;b)病毒在前端;c)病毒在后端;d)病毒充斥在程序里的多余空间里

为了避免从前端装入病毒代码带来的复杂操作,大多数病毒是后端装入的,把它们自己附在可执行程序末端而不是前端,并且把文件头的起始地址指向病毒,如图9-28c所示。现在病毒要根据被感染程序的不同在不同的虚拟地址上运行,这意味着Virgil必须使用相对地址,而不是绝对地址来保证病毒是位置独立的。对资深的程序员来说,这样做并不难,并且一些编译器根据需要也可以完成这件事。

复杂的可执行程序格式,如Windows里的.exe文件和UNIX系统中几乎所有的二进制格式文件都拥有多个文本和数据段,可以用装载程序在内存中迅速把这些段组装和分配。在有些系统中(如Windows),所有的段都包含多个512字节单元。如果某个段不满,链接程序会用0填充。知道这一点的病毒会试图隐藏在这些空洞里。如果正好填满多余的空间,如图9-28d所示,整个文件大小将和未感染的文件一样保持不变,不过却有了一个附加物,所以隐含的病毒是幸运的病毒。这类病毒叫做空腔病毒(cavity virus)。当然如果装载程序不把多余部分装入内存,病毒也会另觅途径。

4.内存驻留病毒

到目前为止,我们假设当被感染的程序运行时,病毒也同时运行,然后将控制权交给真正的程序,最后退出。内存驻留病毒(memory-resident virus)与此相反,它们总是驻留在内存中(RAM),要么藏在内存上端,要么藏在下端的中断变量中。聪明的病毒甚至可以改变操作系统的RAM分布位图,让系统以为病毒所在的区域已经占用,从而避免了被其他程序覆盖。

典型的内存驻留病毒通过把陷阱或中断向量中的内容复制到任意变量中之后,将自身的地址放置其中,俘获陷阱或中断向量,从而将该陷阱或中断指向病毒。最好的选择是系统调用陷阱,这样病毒就可以在每一次系统调用时运行(在核心态下)。病毒运行完之后,通过跳转到所保存的陷阱地址重新激活真正的系统调用。

为什么病毒在每次系统调用时都要运行呢?这是因为病毒想感染程序。病毒可以等待直到发现一个exec系统调用,从而判断这是一个可执行二进制(而且也许是一个有价值的)代码文件,于是决定感染它。这一过程并不需要大量的磁盘活动,如图9-27所示,所以难以被发现。捕捉所有的系统调用也给了病毒潜在的能力,可以监视所有的数据并造成种种危害。

5.引导扇区病毒

正如我们在第5章所讨论的,当大多数计算机开机时,BIOS读引导磁盘的主引导记录放入RAM中并运行。引导程序判断出哪一个是活动分区,从该分区读取第一个扇区,即引导扇区,并运行。随后,系统要么装入操作系统要么通过装载程序导入操作系统。但是,多年以前Virgil的朋友发现可以制作一种病毒覆盖主引导记录或引导扇区,并能造成灾难性的后果。这种叫做引导扇区病毒(boot sector virus),它们现在已十分普遍了。

通常引导扇区病毒[包括MBR(主引导记录)病毒],首先把真正的引导记录扇区复制到磁盘的安全区域,这样就能在完成操作后正常引导操作系统。Microsoft的磁盘格式化工具fdisk往往跳过第一个磁道,所以这是在Windows机器中隐藏引导记录的好地方。另一个办法是使用磁盘内任意空闲的扇区,然后更新坏扇区列表,把隐藏引导记录的扇区标记为坏扇区。实际上,由于病毒相当庞大,所以它也可以把自身剩余的部分伪装成坏扇区。如果根目录有足够大的固定空间,如在Windows 98中,根目录的末端也是一个隐藏病毒的好地方。真正有攻击性的病毒甚至可以为引导记录扇区和自身重新分配磁盘空间,并相应地更新磁盘分布位图或空闲表。这需要对操作系统的内部数据结构有详细的了解,不过Virgil有一个很好的教授专门讲解和研究操作系统。

当计算机启动时,病毒把自身复制到RAM中,要么隐藏在顶部,要么在未使用的中断向量中。由于此时计算机处于核心态,MMU处于关闭状态,没有操作系统和反病毒程序在运行,所以这对病毒来说是天赐良机。当一切准备就绪时,病毒会启动操作系统,而自己则往往驻留在内存里,所以它能够监视情况变化。

然而,存在一个如何获取今后对系统的控制权的问题。常用的办法要利用一些操作系统管理中断向量的技巧。如Windows系统在一次中断后并不重置所有的中断向量。相反,系统每次装入一个设备驱动程序,每一个都获取所需的中断向量。这一过程要持续一分钟左右。

这种设计给了病毒以可乘之机。它可以捕获所有中断向量,如图9-29a所示。当加载驱动程序时,部分向量被覆盖,但是除非时钟驱动程序首先被载入,否则会有大量的时钟中断用来激活病毒。丢失了打印机中断的情况如图9-29b所示。只要病毒发现有某一个中断向量已被覆盖,它就再次覆盖该向量,因为这样做是安全的(实际上,有些中断向量在启动时被覆盖了好几次,Virgil很明白是怎么回事)。重新夺回打印机控制权的示意图如图9-29c所示。在所有的一切都加载完毕后,病毒恢复所有的中断向量,而仅仅为自己保留了系统调用陷阱向量。至此,内存驻留病毒控制了系统调用。事实上,大多数内存驻留病毒就是这样开始运行的。

阅读 ‧ 电子书库
图 9-29 a)病毒捕获了所有的中断向量和陷阱向量后;b)操作系统夺回了打印机中断向量;c)病毒意识到打印机向量的丢失并重新夺回了控制权

6.设备驱动病毒

深入内存有点像洞穴探险——你不得不扭曲身体前进并时刻担心物体砸落在头上。如果操作系统能够友好并光明正大地装入病毒,那么事情就好办多了。其实只要那么一点点努力,就可以达到这一目标。解决办法是感染设备驱动程序,这类病毒叫做设备驱动病毒(device driver virus)。在Windows和有些UNIX系统中,设备驱动程序是位于磁盘里或在启动时被加载的可执行程序。如果有一个驱动程序被寄生病毒感染,病毒就能够在每次启动时被正大光明地载入。而且,当驱动程序运行在核心态下,一旦被加载就会调用病毒,从而给病毒获取系统调用的陷阱向量的机会。这样的情况促使我们限制驱动程序运行在用户态,这样的话即使驱动程序被病毒感染,它们也不能像在内核态的驱动程序一样,造成很大的危害。

7.宏病毒

许多应用程序,如Word和Excel,允许用户把一大串命令写入宏文件,以便日后一次按键就能够执行。宏可附在菜单项里,这样当菜单项被选中时宏就可以运行。在Microsoft Office中,宏可以包含完全用Visual Basic编程语言编写的程序。宏程序是解释执行而不是编译执行的,但解释执行只影响运行速度而不影响其执行的效果。宏可以是针对特定的文档,所以Office就可以为每一个文档建立宏。

现在我们看一看问题所在。Virgil在Word里建立了一个文档并创建了包含OPEN FILE功能的宏。这个宏含有一个宏病毒代码。然后他将文档发送给受害人,受害人很自然地打开文件(假设E-mail程序还没有打开文件),导致OPEN FILE宏开始运行。既然宏可以包含任意程序,它就可以做任何事情,如感染其他的Word文档,删除文件等。对Microsoft来说,Word在打开含有宏的文件时确实能给出警告,但大多数用户并不理解警告的含义并继续执行打开操作。而且,合法文件也会包含宏。还有很多程序甚至不给出警告,这样就更难以发现病毒了。

随着E-mail附件数量的增长,发送嵌有宏病毒的文档成为越来越严重的问题。比起把真正的引导扇区隐藏在坏块列表以及把病毒藏在中断向量里,这样的病毒更容易编写。这意味着更多缺乏专业知识的人都能制造病毒,从而降低了病毒产品的质量,给病毒制造者带来了坏名声。

8.源代码病毒

寄生病毒和引导区病毒对操作系统平台有很高的依赖性;文件病毒的依赖性就小得多(Word运行在Windows和Macintosh上,但不是UNIX)。最具移植性的病毒是源代码病毒(source code virus)。请想象图9-27,若该病毒不是寻找可执行二进制文件,而是寻找C语言程序并加以改变,则仅仅改动一行即可(调用access)。infect过程可以在每个源程序文件头插入下面一行:


#include<virus.h>


还可以插入下面一行来激活病毒:


run_virus();


判断在什么地方插入需要对C程序代码进行分析,插入的地方必须能够允许合法的过程调用并不会成为无用代码(如插入在return语句后面)。插入在注释语句里也没什么效果,插入在循环语句里倒可能是个极好的选择。假设能够正确地插入对病毒代码的调用(如正好在main过程结束前,或在return语句结束前),当程序被编译时就会从virus.h处(虽然proj.h可能会引起更少的注意)获得病毒。

当程序运行时,病毒也被调用。病毒可以做任何操作,如查找并感染其他的C语言程序。一旦找到一个C语言程序,病毒就插入上面两行代码,但这样做仅对本地计算机有效,并且virus.h必须安放妥当。要使病毒对远程计算机也奏效,程序中必须包括所有的病毒源代码。这可以通过把源代码作为初始化后字符串来实现,特别是使用一串32位的十六进制整数来防止他人识破企图。字符串也许会很长,但是对于今天的大型代码而言,这是可以轻易实现的。

对初学读者来说,所有这些方法看起来都比较复杂。有人也许会怀疑这样做是否在操作上可行。事实上是可行的。Virgil是极为出色的程序员,而且他手头有许多空闲时间。读者可以看看当地的报纸就知道了。

9.病毒如何传播

病毒的传播需要很多条件。让我们从最古典的方式谈起。Virgil编写了一个病毒,把它放进了自己的程序(或窃取来的程序)里,然后开始分发程序,如放入共享软件站点。最后,有人下载并运行了程序。这时有好几种可能。病毒可能开始感染硬盘里的大多数文件,其中有些文件被用户共享给了自己的朋友。病毒也可以试图感染硬盘的引导扇区。一旦引导扇区被感染,就很容易在核心态下放置内存驻留病毒。

现在,Virgil也可以利用其他更多的方式。可以用病毒程序来查看被感染的计算机是否连接在局域网上,如一台机器很可能属于某个公司或大学的。然后,就可以通过该局域网感染所有服务器上未被保护的文件。这种感染不会扩散到已被保护的文件,但是会让被感染的文件运行起来十分奇怪。于是,运行这类程序的用户会寻求系统管理员的帮助,系统管理员会亲自试验这些奇怪的文件,看看是怎么会事。如果系统管理员此时用超级用户登录,病毒会感染系统代码、设备驱动程序、操作系统和引导扇区。犯类似这样的一个错误,就会危及局域网上所有计算机的安全。

运行在局域网上的计算机通常有能力通过Internet或私人网络登录到远程计算机上,或者甚至有权无须登录就远程执行命令。这种能力为病毒提供了更多传播的机会。所以往往一个微小的错误就会感染整个公司。要避免这种情况,所有的公司应该制定统一的策略防止系统管理员犯错误。

另一种传播病毒的方法是在经常发布程序的USENET新闻组或网站上张贴已被感染病毒的程序。也可以建立一个需要特别的浏览器插件的网页,然后确保插件被病毒感染上。

还有一种攻击方式是把感染了病毒的文档通过E-mail方式或USENET新闻组方式发送给他人,这些文档被作为邮件的附件。人们从未想到会去运行一个陌生人邮给他们的程序,他们也许没有想到,点击打开附件导致在自己的计算机上释放了病毒。更糟的是,病毒可以寻找用户的邮件地址簿,然后把自己转发给地址簿里所有的人,通常这些邮件是以看上去合法的或有趣的标题开头的。例如:


Subject:Change of plans

Subject:Re:that last e-mail

Subject:The dog died last night

Subject:I am seriously ill

Subject:I love you


当邮件到达时,收信人看到发件人是朋友或同事,就不会怀疑有问题。而一旦邮件被打开就太晚了。“I LOVE YOU”病毒在2000年6月就是通过这种方法在世界范围内传播的,并导致了数十亿美元的损失。

与病毒的传播相联系的是病毒技术的传播。在Internet上有多个病毒制造小组积极地交流,相互帮助开发新的技术、工具和病毒。他们中的大多数人可能是对病毒有癖好的人而不是职业罪犯,但带来的后果却是灾难性的。另一类病毒制造者是军人,他们把病毒作为潜在的战争武器来破坏敌人的计算机系统。

与病毒传播相关的另一个话题是逃避检测。监狱的计算设施非常差,所以Virgil宁愿避开他们。如果Virgil将最初的病毒从家里的计算机张贴到网上,就会产生危险。一旦攻击成功,警察就能通过最近病毒出现过的时间信息跟踪查找,因为这些信息最有可能接近病毒来源。

为了减少暴露,Virgil可能会通过一个偏远城市的网吧登录到Internet上。他既可以把病毒带到软盘上自己打开,也可以在没有软磁盘驱动器的情况下利用隔壁女士的计算机读取book.doc文件以便打印。一旦文件到了Virgil的硬盘,他就将文件名改为Virus.exe并运行,从而感染整个局域网,并且让病毒在两周后激活,以防警察列出一周内进出该城市机场的可疑人员名单。

另一个方法是不使用软盘驱动器,而通过远程FTP站点放置病毒。或者带一台笔记本电脑连接在网吧的Ethenet或USB端口上,而网吧里确实有这些服务设备供携带笔记本电脑的游客每天查阅自己的电子邮件。

关于病毒还有很多需要讨论的内容,尤其是他们如何隐藏自己以及杀毒软件如何将之发现。在本章后面讨论恶意软件防护的时候我们会回到这个话题。