本文作者:方弦

计算无处不在。

走进一个机房,在服务器排成的一道道墙之间,听着风扇的鼓噪,似乎能嗅出0和1在CPU和内存之间不间断的流动。从算筹算盘,到今天的计算机,我们用作计算的工具终于开始量到质的飞跃。计算机能做的事情越来越多,甚至超越了它们的制造者。上个世纪末,深蓝凭借前所未有的搜索和判断棋局的能力,成为第一台战胜人类国际象棋世界冠军的计算机,但它的胜利仍然仰仗于人类大师赋予的丰富国际象棋知识;而仅仅十余年后,Watson却已经能凭借自己的算法,先“理解”问题,然后有的放矢地在海量的数据库中寻找关联的答案。长此以往,工具将必在更多的方面超越它的制造者。而这一切,都来源于越来越精巧的计算。

计算似乎无所不能,宛如新的上帝。但即使是这位“上帝”,也逃不脱逻辑设定的界限。

第一位发现这一点的,便是图灵。

当汤玛斯(Robert Tomas)在1971年写下他的“小小实验”所用的代码时,肯定不会想到,他写下的Creeper代码会开启数十年后的一门“黑色产业”。现在,面前的计算机上,掌心的智能机中,广阔的互联网内,有着无数幽灵飘来荡去,凯觎一切有价值的信息,操纵一切能被操纵的机器。这是一个庞大的产业,而同样庞大的是为了防御这些幽灵而生的守护者。计算机病毒和杀毒软件,两者你追我赶,永无止尽。注定这两者命运的,正是一条数学定理。

到处乱窜的代码

在计算机发展的早期,计算资源非常珍贵,只有军队或者大学才拥有计算机,还要排队才能用上。但如此宝贵的计算资源有时候却会被白白浪费。计算机不需要休息,但是人却必须睡眠。人不在的时候,计算机往往就空下来了,这就造成了需求的不平均。有某些计算需要特定的数据,但如果只为了一次计算去传输未免太浪费,这就是数据的不平均。要想将计算资源压榨到最后一滴,就需要消除这些不平均的因素。用现代术语来说,就是负载平衡的问题。

1969年见证了一样新奇事物的诞生。为了共享资源,美国军方开发了阿帕网(ARPANET),旨在将散落在美国各地的军用计算机连结起来。那就是网络的石器时代。我们现在习以为常的各种概念,比如说电子邮件、网络协议等等,当时还不存在。程序员手中所掌握的,就是几台大型计算机,以及连结它们的简陋而原始的网络。

但条件匮乏并不能阻碍程序员的脚步,他们是开辟新天地的先锋。既然信息可以在网络中流动,那么运行的程序本身可不可以在计算机之间转移呢?可行的话,某个程序在一台计算机繁忙的时候,可以自动跳到空闲的计算机上,而如果这台计算机上没有需要的数据,它也可以自动跳到有相应数据的计算机上执行。“程序自动转移”的这个想法,一下子就能同时解决需求和数据不平均的问题,岂不美哉?

当时汤玛斯在一间名为BBN的公司里,与同事为阿帕网内的计算机编写操作系统。这个操作系统叫TENEX,其中内置了网络功能,汤玛斯琢磨着,能不能利用相应的功能,写出一个能自动转移的程序呢?

他成功了。

他写了一个叫Creeper的程序,功能很简单:首先在当前计算机上读取一个文件,然后寻找网络上的另一台计算机,将程序本身和附带的文件都打包传送过去,在当前计算机上删除自身,最后留下一句话:

I’M THE CREEPER : CATCH ME IF YOU CAN.
(我是CREEPER:有本事就来抓我吧。)

-说我? -走开,不是说你! -不开心,要炸了……(图片来自gamepedia)

-说我? -走开,不是说你! -不开心,要炸了……(图片来自gamepedia)

Creeper说明“程序自动转移”确实可行。但问题是,放出来的Creeper不太好收拾,一时间它在阿帕网上跳来跳去,令人烦心。

但糟糕的还在后头。没几天,新版本Creeper出现了,它在传送的时候不会在原来的计算机上删除自身,也就是说它将自己复制到了另一台计算机上。据说始作俑者是汤玛斯的同事雷·汤姆林森(Ray Tomlinson)。可以说,新版本的Creeper满足了广义上病毒的定义:在用户不知情的情况下自我复制的程序

跟所有病毒一样,新版本Creeper也惹来了大麻烦:它很快就感染了网络上的所有计算机,而且即使人们在一台计算机上将它清除,也很快会受到来自网络上另一台计算机的感染。也许是为了人道主义的补锅,汤姆林森很快写出了另一个名为Reaper的程序,它基本上就是Creeper的变种,会以同样的方式感染网络上的计算机,但在感染完成后,会尝试移除计算机上的Creeper,最后自行关闭。

这就是第一个计算机病毒。

从玩笑到犯罪

在计算机发展的早期,因为用户群体相对小,宵小之徒还没来得及盯上这个领域。当时大部分病毒被编写出来的目的,其实是为了炫耀技术,或者搞搞无伤大雅的恶作剧。所以,当时的计算机病毒通常不会造成什么大损失,而且经常带着一丝幽默。比如说1982年出现的Elk Clone病毒,“危害”就是会自动显示一首诗。还有1990年前后肆虐的Cascade病毒,就会使终端命令行的字符“掉下来”(当时的界面也差不多只有命令行)。这些病毒都是通过软盘传播的,现在已经成为了时代的眼泪。

另一些病毒则是阴差阳错的结果。比如说第一个木马病毒ANIMAL,它在1975年出现,本体是一个猜动物的小游戏。为了进行自动更新,作者另外编写了另一个小程序PERVADE,执行时会将自身和ANIMAL复制到当前用户能访问的所有文件夹之中,包括共享文件夹,与其他用户共享。每当用户运行ANIMAL游戏时,它会自动执行PERVADE程序来完成复制,通过共享文件夹,整套程序就完成了从用户到用户的感染。要放到现在,这应该就叫P2P自动更新推送。另一个例子就是1986年出现的Brain病毒,本来是防盗版措施,但在作者加入自我复制的功能之后,它就成了名副其实的病毒。

随着计算机的普及,奸诈之徒也开始出现,病毒也越来越有破坏性。强行关闭计算机和删除文件自然不在话下,加密文件以勒索赎金也早就有人干过。甚至直接破坏计算机硬件的病毒也出现了,比如臭名远扬的CIH病毒,是经历过上世纪90年代洗礼的人们心中难以拂去的梦魇。

进入21世纪之后,犯罪分子编写病毒的目的也逐渐改变,从单纯的破坏转为牟利。将感染转化为利益有很多方法,最直白的就是勒索软件,它们通过强大的加密“劫持”重要数据和操作系统这些“人质”,只有受害者支付一笔“赎金”之后,才能“赎回”计算机的正常使用。最近的WannaCry就是一个例子。另一种方法就是搜索和截获信用卡的账号和密码等有价值的信息,然后直接通过这些重要信息牟利。还有一种方法就是操纵被感染的机器,把它们用在其他恶意用途上,比如DDOS[link]或者发送垃圾邮件,而黑客可以出售这些“服务”来牟利。无论哪种方式,都是一本万利的生意。

重利之下,病毒的始作俑者也变得团队化和全球化,对信息安全造成了重大威胁。只要是可以运行程序的机器,他们都不放过,比如新近出现的物联网,由家用电器组成的网络。因为物联网上的设备通常疏于更新而且留有后门,入侵起来实在易如反掌。目前最大的僵尸网络Mirai(日语中意即“未来”),大部分就是由物联网设备组成的。就规模而言,似乎这个模式的确是恶意软件的未来。

盯上病毒的除了贼还有兵。为了进行情报活动,各大国对计算机安全的研究都进行了巨大的投入,编写出前所未有精巧的新病毒,危险性远远超出了犯罪团伙。最近美国国家安全局“囤积”的大量漏洞和黑客工具被泄露,更是说明了国家力量可以造成多大的损失。其中仅仅一个漏洞“永恒之蓝”(Eternal Blue),就给我们带来了WannaCry这个勒索软件,使世界各地的许多机构陷于瘫痪。

有矛必有盾

有无相生,先后相随。既然有病毒,用户就有清除病毒的需要,也就有了杀毒软件。可以说,杀毒软件追随着计算机病毒而生。杀毒软件的任务就是识别并清除病毒。

面对如此麻烦的病毒,人们自然想追求一劳永逸的解法。是否能写出一个完美的杀毒软件,它不仅能查杀一切已知的病毒,还能查杀以后可能出现的任何病毒呢?

然而,牵涉到判断计算机程序具体行为的问题,基本上都是不可能完美解决的。我们之前已经看到[link:12],不存在一个程序能够判别任何一段代码在执行时是否会出错。同样,不存在这样的杀毒软件,能完美无缺地辨别任何计算机病毒,不论是已知的还是未知的。这实际上是Frederick B. Cohen在1987年证明的定理,他又被誉为计算机病毒防御技术的创始人。对于本系列的读者来说,这个定理其实是停机定理[link:2]的推论,这也许并非意料之外。

所以,对于任何杀毒软件来说,下面两种情形至少有一种会发生:要么把正常的程序当作病毒消灭了,要么在眼皮底下放过病毒,前者叫误杀,后者叫漏杀,两者此消彼长。当然,不存在完美的杀毒软件,并不阻碍我们精益求精,做出越来越精密的杀毒软件。再加上逐利不竭的犯罪分子,可以说,杀毒软件这个行业永远不会停止发展。

那么,人们是如何一步一步改进杀毒软件的呢?杀毒软件的目标,在于区分正常程序和计算机病毒。不同的病毒查杀技术,实际上就是各种将病毒从正常程序中区分出来的方法。

最早的杀毒软件可以追溯到1987年,当时的病毒很简单,数量也很少,要将它们同正常程序区分开来也很简单。当时的病毒通常有一些特定的程序代码,用以执行感染和发作的步骤。安全研究人员可以先定位并提取不同病毒中这些有特征性的代码(又称为“特征码”),放到杀毒软件的数据库里。杀毒软件的工作,就是检查每个文件是否存在相应病毒的特征码,如果存在,这个文件十有八九就包含了病毒。

幻化万端难辨识

但道高一尺,魔高一丈。病毒的制作者当然也观察到了这样的现象,他们开始想尽办法制作不同的变种,改变病毒的代码,比如在病毒体内加入没有实际效用的所谓“花指令”,或者将不同的指令变换为功能相同的指令,以此躲避杀毒软件的检测。但这样的手段,即使一时骗过了杀毒软件,在新病毒被研究人员捕获并分析之后,杀毒软件很快就能更新到能查杀新的变体。

病毒制作者自然不会善罢甘休。既然特征码针对的是病毒代码之中的固定部分,那么如果病毒在每次传播时,具体代码都会改变,那不就能逃脱特征码查杀的套路了?沿着这种思路开发的病毒,又叫自修改病毒。

最早的自修改病毒叫1260,早在1990年就出现了。它由两个模块组成:代码模块和解密模块。代码模块平时是被加密压缩的,而病毒在被激活时,会先运行解密模块,把包含真正的感染和破坏指令的代码模块解密,然后再去执行代码模块。而当病毒感染其他文件时,会随机打乱解密模块的部分内容,形成新的解密模块,然后用它来将代码模块重新加密。这种复杂的构造,就能保证每次感染时出现的病毒体都与以往完全不同,大大增加了查杀的难度。这种相对简单的自修改病毒,又叫多态病毒(polymorphic virus)。

然而这种小技巧并没有难倒安全研究人员。就像自然界中真正的病毒那样,多态病毒即使变异很快,仍然有一些至关重要的部分保持相对稳定,那就是解密模块。研究人员很快就学会了利用解密模块的特征来识别多态病毒。迫使多态病毒采用更多措施来打乱自身代码,比如说自动加插花指令和进行等价指令的替换,又或者是自动重新排列自身的代码。但安全研究人员很快就想出了反制的办法,毕竟这些改动都不算大,检测也不算困难。

最后病毒制作者只剩下一个希望:写出一个病毒,在每次感染时,病毒的几乎每一段代码都会产生重大改变。这样的话,就不可能在代码的层面上探测出这个病毒了。

说得轻巧,但具体怎么做呢?

黑客们想出了一条可谓破釜沉舟的计策:每次要感染新文件时,先把病毒本身转换为某种“中间代码”,然后将中间代码用另一种完全随机的方式编译,得到的就是全新的病毒代码。这种中间代码可以有无数种编译方式,但得到的代码执行时的行为都完全一样。以这种方式编写出来的病毒,又叫变形病毒(metamorphic virus)。通过先转换为中间代码再编译的方式,它们“变形”得到的具体代码千变万化,但具体的行为却完全一致。

最有名的变形病毒叫Simile,在2002年第一次出现,当时给安全研究人员带来了很大的麻烦。正因为它可以“变形”为与之前完全不同的代码,这使得特征码提取的方法基本失效。幸而变形病毒的研发非常复杂,尤其是“变形”所需的代码非常复杂(Simile中有90%的代码专门负责变形),所以变形病毒数量相当少。但即使变形病毒的门槛很高,随着技术的进步,也终会有普及的一天。即使是为了未雨绸缪,也应该想办法开发新技术去对付这种新型病毒。

听其言,观其行

不久之后,安全研究人员想到了两个办法,它们的核心思想都很简单。变形病毒改变的是它们“外在”的代码,但“内在”的行为,也就是病毒具体会做什么,却是不会改变的。如果有办法拨去具体代码的面纱,探测它们的“实际行为”,也就是语义,岂不是就可以成功查杀这些病毒了么?

第一个方法,就是尝试将变形病毒的“变形”过程还原,尝试将它们固定到同一个形式中。研究人员首先将变形病毒的代码拆解出来,然后用程序分析它的逻辑结构。因为变形病毒无论怎么变形,它的功能是不变的,所以具体的逻辑结构也不会有太大的变化。只要能识别它逻辑结构中不变的部分,就能识别同一类的病毒,无论它们怎么变形,代码有多不同。当然,分析一段代码的逻辑结构并不容易,研究人员借鉴了当时编译器的某些优化编译功能,可以将没有用处的指令剔除,也可以分辨出那些本质上等价的代码。最终结果相当成功,通过分析逻辑结构本身,就能有效查杀那些变形病毒。

研究人员想到的第二个办法更加直接。既然我们想要探测病毒的“实际行为”,那么何不让它实际“运行”一下,看它会做出什么举动?当然,我们不能在用户的计算机上实际运行可疑文件,否则如果可疑文件的确是病毒,那么麻烦就大了。研究人员做的,就是模拟一个病毒可以运行的环境,又叫“沙盒”,然后在这个环境中模拟病毒的运行,观察它的行为并记录下来。病毒通常会有一些特征行为,比如多态病毒就会访问并改写自己的代码,而变形病毒则会执行“变形”步骤。对于杀毒软件来说,对于可疑的程序,它可以先在沙盒中模拟这个程序,如果观察到的行为符合某个病毒的特征行为,那么这个程序很有可能就包含病毒。这种技术又叫沙盒查杀技术。但因为在沙盒内运行程序需要消耗不少的计算资源,所以只能用在一些特别重要的位置或者特别可疑的文件上,比如系统文件和内存。

“沙盒”原本是供小朋友游玩的设备,无论弄得再乱七八糟,也只是沙盒内部的事

“沙盒”原本是供小朋友游玩的设备,无论弄得再乱七八糟,也只是沙盒内部的事

实际上,沙盒查杀技术的用途不止于此。因为它检测的不是具体的代码,而是程序的行为,所以从理论上来说,这种技术能够用于检测未知病毒,走在病毒编写者的前面。不同的病毒会有不同的特征行为。有的病毒会检查操作系统的语言和当前时间,然后决定是否进行感染;有的病毒会尝试将自身注册为操作系统的底层驱动,用以隐藏自身;有的病毒会尝试枚举所有程序,准备感染它们;有的病毒会枚举特定文件然后进行加密,这是勒索的必备步骤。不同的行为有不同的风险,面对可疑文件,杀毒软件可以先在沙盒中运行它们,记录它们的行为。如果这些行为大体类似某个已知病毒,那么这个文件就可能包含病毒。即使没有检测到相应的病毒,如果记录到的行为过于危险可疑,杀毒软件也可以对它进行标记和隔离,以供进一步研究。这就是所谓的“启发式查杀技术”。确切地说,使用沙盒的是动态启发式技术,但也有所谓的静态启发式技术,只需要观察文件的代码,而不需要具体执行。利用最近兴起的机器学习技术,安全研究人员还能进一步提高启发式技术的可靠性。

启发式技术虽好,但并非无懈可击,因为它不能完全确定检测结果是对是错,有可能会误杀正常程序,毕竟很多正常程序,尤其是系统底层的驱动程序,也会作出与病毒相似的某些行为,但它们并不是病毒,而且对于计算机的正常运行至关重要。误伤这些文件会导致很多问题,而偏偏病毒倾向于感染这种贴近底层的文件来逃避侦测。我们有时会听到这样的消息,某款杀毒软件在更新后突然干掉关键系统文件,而且是成片发生。比较极端的,甚至还有解放军在演习中武器系统被杀毒软件咔嚓了的事件。但随着技术的发展,目前这些意外已经越来越少,杀毒软件的功能也越来越强大,查杀技术也越来越精确。

在杀毒软件和病毒的斗争之中,沙盒技术和启发式查杀技术有着重大的意义,因为它第一次让安全研究人员能部分走在病毒制作者的前面。在它出现之前,杀毒软件只能跟在病毒后面亦步亦趋,但有了这项技术,病毒制作者也就不能随心所欲编写新病毒,而要对市面上的杀毒软件进行一番研究,才能保证自己的新病毒不会在沙盒中被启发式查杀干掉。事实上,现在相当一部分的病毒被查杀时,仅仅会被识别为“一般病毒”(generic virus),因为阻止它们的是启发式技术,不需要具体识别它们的类型。

现在摆在病毒制作者面前的就是这样一个问题:怎么样才能躲过沙盒技术呢?也就是说,有没有办法在执行的时候,让病毒“意识”到自己身处沙盒而不是一台真正的电脑之中呢?

办法不是没有。既然沙盒是一种需要大量资源的模拟,为了减少资源的使用,杀毒软件的沙盒必定会有某种破绽。比如说,如果沙盒对于模拟的系统时间没有特殊处理的话,因为沙盒的模拟比真正的执行要慢,所以病毒可以通过测量执行某段代码所需的时间来得知自己是否处于沙盒之中。一旦安全研究人员发现有利用这种缺陷的病毒,他们就可以修正缺陷,重新取得先机。在这个战场上,亦步亦趋的不再是安全研究人员,而是那些病毒制作者。

但沙盒毕竟是模拟环境,总会存在破绽,病毒也可以等待一段时间再发作,以此逃避杀毒软件的检测。有些安全研究人员就有了一个大胆的想法:何不让要检测的程序自由运行,等到出现了可疑的行为再查杀不迟?毕竟病毒要复制和传播,必然要进行某些特定的操作,比如说读取其他程序或者调用某些特殊的系统函数。只要在病毒作出这些操作的瞬间进行拦截,那就没有问题了。这就是行为检测技术,要做到这一点,杀毒软件需要利用特殊的技术(Rootkit)监测所有应用程序的一举一动,在它们请求操作系统进行任何任务时,拦截并分析具体的请求。这种技术一开始是病毒用以隐藏自身痕迹而开发的,现在反而成了杀毒软件的武器。

永无休止的斗争

当然,杀毒软件的技术再好,也不能保证绝对的安全。越好的查杀技术,通常使用的资源也越多。如果对每个文件都采取最高级的查杀技术,那肯定会大大拖慢系统的运行速度,所以在具体编写软件时,安全研究人员需要进行适当的取舍。既然有取舍,那就会有漏洞可供利用。另外,病毒制造者也可以先下手为强,感染时想办法先把杀毒软件干掉,或者利用操作系统本身的漏洞取得整个系统的主导权,直接架空杀毒软件。互联网技术发达之后,杀毒软件厂商可以让杀毒软件自动上报可疑文件,尽早获得新病毒的资料;病毒制作者也可以让病毒自动更新,不停逃避杀毒软件的追杀。病毒和杀毒软件的攻防战线广阔异常,从磁盘到内存甚至显卡,都是它们的战场,而攻防策略之多,无论如何列举都只能是挂一漏万。

但尽管杀毒软件现在已经成为非常成熟的技术,病毒要感染计算机,也还有别的路可以走。操作计算机的毕竟是人,跟杀毒软件相比,人的“漏洞”更多,更容易被利用。有多少人嫌麻烦不去更新系统填补漏洞,给病毒以可乘之机?又有多少人看到“请查收文件”的邮件,就急匆匆点开附件,丝毫不检查文件的来源和性质?还有多少人用的密码就是123456,或者上网看到什么链接,无论多么可疑都点进去看看?一个系统的安全,取决于最薄弱的环节。如果没有信息安全的意识,杀毒软件再好,也挡不住人类作死的脚步。

分享到