9.8.6 封装移动代码

病毒和蠕虫不需要制造者有多大学问,而且往往会与用户意愿相反地侵入到计算机中。但有时人们也会不经意地在自己的机器上放入并执行外来代码。情况通常是这样发生的:在遥远的过去(在Internet世界里,代表去年),大多数网页是含有少量相关图片的静态文件,而现在越来越多的网页包含了叫做Applet的小程序。当人们下载包含Applet的网页时,Applet就会被调用并运行。例如,某个Applet也许包含了需要填充的表格以及交互式的帮助信息。当表格填好后会被送到网上的某处进行处理。税单、客户产品定单以及许多种类的表格都可以使用这种方法。

另一个让程序从一台计算机到另一台计算机上运行的例子是代理程序(agent)。代理程序指用户让程序在目标计算机上执行任务后再返回报告。例如,要求某个代理程序查看旅游网站,查找从阿姆斯特丹到旧金山的最便宜航线。代理程序会登录到每个站点上运行,找到所需的信息后,再前进到下一个站点。当所有的站点查询完毕后,它返回原处并报告结果。

第三个移动代码的例子是PostScript文件中的移动代码,这个文件将在PostScript打印机上打印出来。一个PostScript文件实际上是用PostScript语言编写,它可在打印机里执行的程序。它通常告诉打印机如何画某些特定的曲线并加以填充,它也可以做其他任何想做的事。Applet、代理和PostScript是移动代码(mobile code)的三个例子,当然还有许多其他的例子。

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

在前面大篇幅讨论了病毒和蠕虫之后,我们很清楚地意识到让外来代码运行在自己的计算机上多少有点冒险。然而,有些人的确想要运行外来代码,所以就会产生问题:“移动代码可以安全运行吗?”简而言之:可以,但并不容易。最基本的问题在于当进程把Applet或其他的移动代码插入地址空间并运行后,这些代码就成了合法的用户进程的一部分,并且掌握了用户所拥有的权限,包括对用户的磁盘文件进行读、写、删除或加密,把数据用E-mail发送到其他国家等。

很久以前,操作系统推出了进程的概念,为的是在用户之间建立隔离墙。在这一概念中,每个进程都有自己的保护地址空间和UID,允许获取自己的文件和资源,而不能获取他人的。而对于保护进程的一部分(指Applet)或者其他资源来说,进程概念也无能为力。线程允许在一个进程中控制多个线程,但是单个线程与其他线程之间却没有提供保护。

从理论上来说,将每个Applet作为独立的进程运行只能帮上一点忙,但缺乏可操作性。例如,某个Web网页包含了相互之间互相影响的两个或多个Applet,而数据在Web页里。Web浏览器也需要与Applet交互,启动或停止它们,为它们输入数据等。如果每个Applet被放在自己的进程里,就无法进行任何操作。而且,把每个Applet放在自己的地址空间里并不能保证Applet不窃取或损害数据。如果有Applet想这样做是很容易的,因为没有人在一旁监视。

人们还使用了许多新方法来对付Applet(通常是移动代码)。下面我们将看看其中的两种方法:沙盒法和解释法。另外,代码签名同样能够用于验证Applet代码。每一种方法都有自己的长处和短处。

1.沙盒法

第一种方法叫做沙盒法(sandboxing),这种方法将每个运行的Applet限制在一定范围的有效地址中(Wahbe等人,1993)。它的工作原理是把虚拟地址空间划分为相同大小的区域,每个区域叫做沙盒。每个沙盒必须保证所有的地址共享高位字节。对32位的地址来说,我们可以把它划分为256个沙盒,每个沙盒有16MB空间并共享相同的高8位。同样,我们也可以划分为512个8MB空间的沙盒,每个沙盒共享9位地址前缀。沙盒的尺寸可以选取到足够容纳最大的Applet而不浪费太多的地址空间。如果页面调用满足的话,物理内存不会成为问题。每个Applet拥有两个沙盒,一个放置代码,另一个放置数据,如图9-37a所示的16个16MB的沙盒。

阅读 ‧ 电子书库
图 9-37 a)内存被划分为16 MB的沙盒;b)检查指令有效性的一种方法

沙盒的用意在于保证每个Applet不能跳转到或引用其他的代码沙盒或数据沙盒。提供两个沙盒的目的是为了避免Applet在运行时超越限制修改代码。通过抑制把所有的Applet放入代码沙盒,我们减少了自我修改代码的危险。只要Applet通过这种方法受到限制,它就不能损害浏览器或其他的Applet,也不能在内存里培植病毒或者对内存造成损失。

只要Applet被装入,它就被重新分配到沙盒的开头,然后系统检查代码和数据的引用是否已被限制在相应的沙盒里。在下面的讨论中,我们将看一下代码引用(如JMP和CALL指令),数据引用也是如此。使用直接寻址的静态JMP指令很容易检查:目标地址是否仍旧在代码沙盒里?同样,相对JMP指令也很容易检查。如果Applet含有要试图离开代码沙盒的代码,它就会被拒绝并不予执行。同样,试图接触外界数据的Applet也会被拒绝。

最困难的是动态JMP。大多数计算机都有这样一条指令,该指令中要跳转的目标地址在运行的时候计算,该地址被存入一寄存器,然后间接跳转。例如,通过JMP(R1)跳转到寄存器1里存放的地址。这种指令的有效性必须在运行时检查。检查时,系统直接在间接跳转之前插入代码,以便测试目标地址。这样测试的一个例子如图9-37b所示。请记住,所有的有效地址都有同样的高k位地址,所以该地址前缀被存放在临时寄存器里,如说S2。这样的寄存器不能被Applet自身使用,因为Applet有可能要求重写寄存器以避免受该寄存器限制。

有关代码是按如下工作的:首先把被检查的目标地址复制到临时寄存器S1中。然后该寄存器向右移位正好将S1中的地址前缀隔离出来。第二步将隔离出的前缀同原先装入S2寄存器里的正确前缀进行比较。如果不匹配就激活陷阱程序杀死进程。这段代码序列需要四条指令和两个临时寄存器。

对运行中的二进制程序打补丁需要一些工作,但却是可行的。如果Applet是以源代码形式出现,工作就容易得多。随后在本地的编译器对Applet进行编译,自动查看静态地址并插入代码来校验运行中的动态地址。同样也需要一些运行时间的开销以便进行动态校验。Wahbe等人(1993)估计这方面的时间大约占4%,这一般是可接受的。

另一个要解决的问题是当Applet试图进行系统调用时会发生什么?解决方法是很直接的。系统调用的指令被一个叫做基准监视器的特殊模块所替代,这一模块采用了与动态地址校验相同的检查方式(或者,如果有源代码,可以链接一个调用基准监视器的库文件,而不是执行系统调用)。在这两个方法中,基准监视器检查每一个调用企图,并决定该调用是否可以安全执行。如果认为该调用是可接受的,如在指定的暂存目录中写临时文件,这种调用就可以执行。如果调用被认为是危险的或者基准监视器无法判断,Applet就被终止。若基准监视器可以判断是哪一个Applet执行的调用,内存里的一个基准监视器就能处理所有这样Applet的请求。基准监视器通常从配置文件中获知是否允许执行。

2.解释

第二种运行不安全Applet的方法是解释运行并阻止它们获得对硬件的控制。Web浏览器使用的就是这种方法。网页上的Applet通常是用Java写的,Java可以是一种普通的编程语言,也可以是高级脚本语言,如安全TCL语言或Javascript。Java Applet首先被编译成一种叫做JVM(Java虚拟机,Java Virtual Machine)的面向栈的机器语言。正是这些JVM Applet被放在网页上,当它们被下载时就插入到浏览器内置的JVM解释器中,如图9-38所示。

阅读 ‧ 电子书库
图 9-38 Applet可以被Web浏览器以解释方式执行

使用解释运行的代码比编译运行的代码好处在于,每一条指令在执行前都由解释器进行检查。这就给了解释器识别校验地址是否有效的机会。另外,系统调用也可以被捕捉并解释。这些调用的处理方式与安全策略有关。例如,如果Applet是可信任的(如来自本地磁盘的Applet),它的系统调用就可以毫无疑问会被执行。但是如果Applet不受信任(如来自Internet的Applet),它就会被放入沙盒来限制自身的行为。

高级脚本语言也能够被解释执行。这里,解释执行不需要机器地址,所以也就不存在脚本以不允许的方式访问内存所带来的危险。解释运行的缺点是:它与编译运行的代码相比十分缓慢。