应用程序虚拟化
copy from http://blog.csdn.net/yujiankk/article/details/6911089
虚拟机技术研究
1.背景
虚拟机技术是在一个物理主机上创建一个或多个可执行环境的技术。每个虚拟机代表了一个潜在的物理主机的实例,并且互不干扰。这种隔离的属性使虚拟机可以成为安全系统和错误容忍应用程序的基石。
我们将介绍一个基于Windows的操作系统层虚拟机体系结构,我们称其为AVM。AVM虚拟机的主要思想是命名空间虚拟化(Namespace Virtualization),即在系统调用接口通过AVM的虚拟层重命名系统资源。当一个进程通过系统调用访问资源时,AVM层可以操纵这些资源的名字。通过重命名资源,处于不同虚拟机中的程序看到的资源名字完全不一样,这样,两个虚拟机绝不会共享任何资源,相互之间也没有直接影响。
为了达到轻量级的效果,该体系结构让虚拟机之间共享大部分主机资源,并且通过特殊的写时拷贝(Copy On Write)方案隔离虚拟机之间的状态变化。一个新创建的虚拟机在初始化时共享所有的主机资源。以后,如果AVM虚拟机中的程序只以只读的方式请求系统资源,那么它可以完全的共享访问该主机资源。虚拟机不会占用任何私有资源直到该程序试图修改主机资源。这样,在AVM体系结构下,虚拟机的资源需求得到了极大程度的减少。
虽然命名空间虚拟机的想法不是最新的而且已经被应用到某些系统中,比如FreeBSD Jails , Solaris Containers和Linux VServer,但是仍有一些WINDOWS平台上的技术难题有待改进。首先,WINDOWS系统有太多类型的命名空间。只处理文件和注册表资源的虚拟还远远达不到虚拟机完全隔离的要求。该虚拟机监控器需要识别这些对象并加以虚拟它们的命名空间。其次,WINDOWS系统将处理一系列的具有特殊管理机制的守护服务,其中一些服务与内核同等重要,很难复制到每个虚拟机中。所以,虚拟命名空间必须处理这些由共享进程创建的特殊资源。最后,WINDOWS系统有大量的特殊的进程间通信机制,其中某些通信机制不直接依赖于名字。这些通信机制也必须被限制以达到虚拟机之间完美的隔离。
2.本技术方案
2.1命名空间虚拟化
实现AVM的关键技术被称为“命名空间虚拟化”。由于WINDOWS为各种系统资源提供了多种类型的命名空间,例如文件,注册表,内核对象等,因此简单地来说命名空间虚拟化技术就是通过在系统调用层上截获调用,在用户进程请求访问各种资源时控制它们的命名(例如文件的路径)。最终通过重命名这些资源,使得这些资源对请求他们的进程来说是正常的,但是对于操作系统来说可能不存在(通过软件虚拟出来的资源)或者仅仅是原资源的一个拷贝。这样就到达了将被AVM运行的用户应用程序与操作系统隔绝开的目的。为了简化实现原理,本文采用了将原资源复制的方式来实现资源的“虚拟化”。
对于某些驻留在主机系统上的资源(例如文件,注册表项等),应用程序有可能仅仅是访问其内容,但是不会修改其内容,因此在这种情况下AVM没有必要将其复制,同时节省了运行时间和系统资源(因为文件往往是非常大的)。为了达到AVM既共享了主机系统的资源和环境,又能将虚拟机中的资源更改与主机系统隔绝开来的目的,写时拷贝机制“Copy On Write”被用在了本文的AVM实现中。当一个AVM被创建时,它共享主机系统中的所有资源,但是不占用。当运行在AVM中进程仅仅是请求读取某个资源时,该进程的读请求就被简单的发送给主机操作系统,完成读取操作。这时AVM并不占用任何系统资源,仅当进程请求更改某个资源时,AVM就会复制一个原资源到虚拟机中,该更改请求就会实施在这个资源拷贝上,而不会影响主机系统的资源。
本文的AVM在WINDOWS系统调用层利用命名空间虚拟化和写时拷贝机制实现了资源访问重定向和隔绝,并且在此基础上构建了应用程序沙盒来实现对多个行为的捕获。由于在前面已经有了WINDOWS系统调用的截获技术,因此下面将直接分别介绍资源访问的重定向和几种必需的资源虚拟化的详细实现原理。
当运行在AVM中的进程仅读取某资源时,该访问将不会被重定向,而当进程发出了其它请求时,AVM会做出不同的处理,以文件为例的具体过程如下:
1) 当进程要创建一个新文件的时候。例如进程请求创建“C:\infector.exe”,则当AVM截获到该文件创建系统调用的时候,AVM会将该创建操作的目的重定向到另外一个位置“C:\VMStore\HardVolume1\infector.exe”,这样文件就被创建在了一个被AVM控制的目录下,但是上层应用程序并不知道该重定向过程的存在。
2) 当进程尝试打开一个文件的时候。例如进程请求打开“C:\infector.exe”,则当AVM截获到该文件打开系统调用的时候,AVM会首先去尝试打开“C:\VMStore\HardVolume1\infector.exe”,如果打开成功,则直接返回,不再做进一步的处理。如果该文件不存在,AVM则检查打开该文件所请求的访问权限。如果是读权限,则说明该文件在被打开后,只会被请求打开它的进程读取,所以直接打开原文件“C:\infector.exe”并返回句柄。如果有写权限请求,则说明该文件被打开后,很有可能被写入,所以这时就要先将原文件复制到虚拟存储目录中,路径为“C:\VMStore\HardVolume1\infector.exe”,然后在打开它并将句柄返回给发出请求的进程。
3) 当进程要删除一个文件的时候。例如进程请求删除“C:\victim.exe”,则当AVM截获到该文件删除系统调用的时候,AVM仅仅将该删除操作和对应的文件路径信息记录到一个表中,以便事后规则分析匹对时使用,不会去真正地删除该文件。
虽然这个重定向过程是以文件为例的,但是WINDOWS对注册表和内核对象等的命名方式与文件的是完全一致的,因此以上重定向算法可以直接地应用在注册表和内核对象的虚拟化上。
在一个AVM下可以运行任意多个进程,这些进程对于主机操作系统是隔离的,但是它们之间是可视和可交互的。为了更准确地综合分析行为,本文研究的AVM允许虚拟机中的进程创建新的进程,并可以相互进行交互,例如跨进程读写内存,命名管道(Named Pipe),邮槽(Mail slot)等。为了将新创建的进程控制在AVM环境下, AVM利用前面的系统调用截获技术截获并控制了进程创建函数(ZwCreateProcessEx),线程创建函数(ZwCreateThread),注册了进程创建/终止通知回调函数(PsSetCreateProcessNotifyRoutine),虚拟内存操作函数(ZwAllocateVirtualMemory,ZwReadVirtualMemory,ZwWriteVirtualMemory)等。当新的进程在AVM下被创建时,新创建进程的标识号(Process Id)将被记录,表明这个进程是运行在AVM中的,以后该进程产生的所有操作请求将被AVM在系统调用层特殊处理,例如命名空间虚拟化,跨进程访问对象控制等。下面将详细介绍文件系统,注册表,内核对象虚拟化的具体实现原理。
2.2 文件虚拟
在AVM中,文件系统的虚拟化实现了将虚拟机中的文件/目录和一些特殊的设备文件(命名管道,邮槽)与主机系统隔离。这主要是通过使用系统调用截获技术截获并控制了WINDOWS的文件(磁盘文件,目录,命名管道和邮槽)操作函数。
对于普通文件(文件,目录),AVM使用了前面介绍的写时拷贝机制,其不同于WINDOWS在内存管理中使用的以单个块(页面)为单位的写时拷贝机制。因为基于块方式的文件写时拷贝机制需要处理与底层文件系统密切相关的数据结构和各种复杂机制等,所以为了简化AVM的实现就采用了以单个对象(文件,目录)为单位的写时拷贝机制。当进程以写入的方式打开一个文件的时候,整个目标文件都会被复制到AVM的根目录下(例如“C:\VMStore”),并且该文件的文件属性和其所有的父目录都会被复制,例如当主机系统上的“C:\WINDOWS\infector.exe”文件以写入方式被打开时,其会被复制为“C:\VMStore\HardVolume1\WINDOWS\infector.exe”,然后再由WINDOWS去打开这个复制出来的“虚拟”文件并返回句柄。当进程要创建一个新的文件的时候,处理过程与以写入方式打开一个文件一样。
对于特殊的设备文件(命名管道,邮槽)的虚拟化,处理方式与普通文件类似。当进程创建一个命名管道或者邮槽的时候,AVM会为这个设备文件在虚拟机的根目录下创建一个文件,该文件用来虚拟命名管道和邮槽。为了实现隔离,该文件只能被同处于一个AVM的进程访问。也就是说,当一个进程请求打开该命名管道或者邮槽的时候,AVM首先检查该进程是否和创建目标命名管道或邮槽的进程属于同一个虚拟机。如果是,打开操作的目标会被直接重定向到新创建的文件上;否则就返回操作失败信息。例如AVM中的进程p1请求创建命名管道“\\.\pipe\ipc01”,接着该请求被AVM在系统调用层截获,这个创建命名管道的请求目标就会被重定向为创建“C:\VMStore\Device\Pipe\ipc01”文件。然后AVM中的另一个进程p2请求打开该命名管道时,AVM就将打开请求重定向为打开“C:\VMStore\Device\Pipe\ipc01”文件。这样这个ipc01文件就虚拟了ipc01这个命名管道。以后对该命名管道的读写操作实际上就是对该命名管道的虚拟文件的读写操作。当特殊设备文件被关闭,并且没有被任何进程占用时,其所对应的虚拟文件就被AVM删除。
整个文件系统虚拟化的实现使用了系统调用截获技术在WINDOWS的系统调用层截获并控制了几个相关的文件操作系统调用。当AVM截获到这些系统调用对应的请求时,会首先检查发起请求的进程是否为被虚拟化的进程。如果不是,则该系统调用被直接传递到WINDOWS的原始系统调用函数;否则会根据不同的请求进行不同的处理:
(1) ZwCreateFile和ZwOpenFile
使用重定向算法处理AVM中进程的文件创建和打开请求。获得目标文件/目录路径通过该系统调用的ObjectAttributes参数获得,这时需要做的就是将该路径根据需要进行修改,使其指向AVM的根目录下的对应虚拟文件/目录,然后用重定向后的路径向WINDOWS请求文件创建或打开操作。最后返回文件/目录的句柄。由于在以后的文件操作中,文件/目录都是由句柄来标识的,所以在这一步中就必须要将文件路径和其句柄对应地记录起来。
(2) ZwQueryInformationFile和ZwQueryDirectoryFile
系统调用函数ZwQueryInformationFile用来通过一个文件的句柄来获得对应文件的各种信息,例如文件属性,路径等。当一个文件被AVM因为写时拷贝重定向后,进程通过传入该文件被打开或者创建的时候返回的句柄,调用ZwQueryInformationFile请求文件路径的时候,会得到指向AVM根目录下的其对应的虚拟文件的路径,这显然不是发起请求的进程所期望的。因此AVM必须要截获该调用,并根据在处理文件创建,打开操作时所记录的句柄-文件路径对应信息,来返回请求进程期望的结果。而系统调用函数ZwQueryDirectoryFile则是用来通过一个目录句柄返回在这个目录下的所有文件的。因此为了让发起请求的进程获得期望的结果,AVM需要根据在处理目录创建,打开操作时所记录的句柄-目录路径对应信息,来返回请求进程期望的结果。
(3) NtSetInformationFile
该系统调用可能被用来删除或者重命名一个文件/目录,因此AVM必须要对其进行处理,防止对主机系统的破坏。当请求是指向一个已经被写时拷贝过的文件/目录时,该请求可以直接传递给WINDOWS的原始系统调用。然后在记录中根据请求类型修改文件名或者删除记录。否则如果是重命名操作,则进行写时拷贝和重定向并修改对应的记录,但如果是删除操作,则仅仅为目标文件/目录记录一个删除记录,并删除对应的记录。
2.3 注册表虚拟
在WINDOWS操作系统中,注册表几乎保存了与操作系统,应用程序等相关所有配置和相关信息。因此AVM通过注册表虚拟化来实现虚拟机中的产生的配置信息与主机系统中配置信息的隔离。这主要是通过使用前面章节介绍的系统调用截获技术截获并控制了WINDOWS的注册表(注册表键,项,值)操作函数。
WINDOWS的注册表系统与文件系统十分类似,数据的存放和操作方式与文件系统几乎完全一样,例如注册表中的项都有自己的路径,在创建和打开项的操作中,目标项是通过其路径来表示的,而在其他的操作中,项则是通过在创建,打开操作中返回的句柄来标识。因此在本文的AVM实现中,注册表的虚拟化使用了和文件系统虚拟化完全相同的方式,那就是命名空间虚拟化和写时拷贝。与文件系统虚拟化实现类似,AVM也为注册表的虚拟化创建了一个根注册表键“\HKEY_CURRENT_USER\VMStore”。所有的由于写时拷贝而被复制的注册表键,项等都会放到这个根下面。由于注册表的虚拟化和文件系统的虚拟化实现方式是相同的,所以这里将不再赘述具体重定向和写时拷贝过程,可以参考上一节的文件系统虚拟化实现。
整个注册表虚拟化的实现使用了系统调用截获技术,在WINDOWS的系统调用层截获并控制了几个相关的注册表操作系统调用。当AVM截获到这些系统调用对应的请求时,会首先检查发起请求的进程是否为被虚拟化的进程。如果不是,则该系统调用被直接传递到WINDOWS的原始系统调用函数;否则会根据不同的请求进行不同的处理。由于注册表的操作与文件的操作可以一一对应,并且处理方式都是一样的,所有在这里仅列出注册表操作的系统调用和文件操作的系统调用的对应关系,见下表。它们的具体处理过程可以参考上一节文件系统虚拟化实现中的对应实现,以及上一章中注册表访问控制实现中关于获取注册表键,项等的全路径和请求访问权限的实现。
表1 注册表与文件操作系统调用对应关系
注册表操作文件/目录操作
ZwCreateKeyZwCreateFile
ZwOpenKeyZwOpenFile
ZwDeleteKeyZwSetInformationFile-删除请求
ZwDeleteValueKeyZwSetInformationFile-删除请求
ZwSetValueKeyZwSetInformationFile-重命名请求
ZwEnumerateKeyZwQueryDirectoryFile
ZwEnumerateValueKeyZwQueryDirectoryFile
ZwQueryKeyZwQueryInformationFile
ZwQueryVauleKeyZwQueryInformationFile
在注册表中,可以将注册表键看作是文件系统中的目录,注册表项看作是文件系统中的文件,而注册表值看作是文件系统中的文件的内容。
2.4 对象虚拟
WINDOWS在其内核中提供了多种命名对象(Named Objects),包括互斥对象(Mutant/Mutex Objects),事件对象(Event Objects),信号量对象(Semaphore Objects),定时器对象(Timer Objects),区域对象(Section Objects),端口对象(Port Objects)等。当然文件,注册表等资源在内核中也是以对象的形式被对象管理器管理着。互斥对象,事件对象,信号量对象主要是用于线程同步;区域对象主要是用于文件映射(File Mapping)和进程间共享内存(Shared Memory);端口对象主要用于本地过程调用(Local Procedure Call,LPC)和远程过程调用(Remote Procedure Call, RPC)。从这些内核对象的用途来看,大多数的进程都会用到他们,例如应用程序常常使用这些全局命名对象来实现防止多重启动,甚至特别重要的线程同步也是由它们来实现的。同时这些对象都是全局的,也就是说任何进程都可能可以访问它们。因此为了防止AVM中的进程影响主机系统中的进程,AVM必须要对全局的内核对象进行虚拟化。由于这些内核对象的命名方式和文件,注册表等的命名方式完全一样,所以可以采用和前面介绍的命名空间虚拟化方法来隔离在AVM中创建的内核对象。
在WINDOWS中内核对象是以目录的方式层次管理的,与文件系统类似,所有内核对象都有一个共同的根目录,以“\”表示。每个内核对象都可以有其自己的子目录,例如一个事件对象就可以放在“\BaseNamedObjects\”目录下。一般来说每种内核对象都有其固定的目录,如事件对象,互斥对象,信号量对象,区域对象等都会放在“\BaseNamedObjects\”目录下,而端口对象根据其用途不同会被放在“\Windows\”和“\RPC Control\”目录下。这样内核对象的命名空间虚拟化方法与文件系统的方法完全一样,通过重定向内核对象的根目录来达到虚拟化的目的。例如有一个命名事件对象Event1,在其被创建时WINDOWS会默认将路径设置为“\BaseNamedObjects\Event1”,而被AVM在系统调用层重定向后,其路径就为“\VM\BaseNamedObjects\Event1”。这样这个事件对象仅在AVM中可以被OpenEvent等API函数打开,而对于主机系统,它对用户态的应用程序是透明的和不可访问的。
整个内核对象虚拟化的实现使用了系统调用截获技术,在WINDOWS的系统调用层截获并控制了几个相关的内核对象创建和打开操作系统调用。当AVM截获到这些系统调用对应的请求时,会首先检查发起请求的进程是否为被虚拟化的进程。如果不是,则该系统调用被直接传递到WINDOWS的原始系统调用函数;否则会根据不同的请求进行不同的处理。现以事件对象为例,对应的系统调用截获控制函数的实现如下:
(1) ZwCreateEvent
该系统调用函数用于创建一个新的事件对象或者打开一个已经存在的事件对象。该函数的第三个参数为指向OJECT_ATTRIBUTES结构的指针,获得目标事件对象路径的方式与在论述文件系统访问控制的实现方式完全一样。当目标事件对象不存在时,需要做的就是将该对象的路径进行修改,使其前面加入AVM的内核对象根目录,例如“\VM”,然后用重定向后的路径向WINDOWS请求创建事件对象。最后返回新创建事件对象的句柄。如果该对象已经存在,则有可能该事件对象是由系统关键进程(例如服务)创建的。而这些事件对象是进程运行所必需的资源。但是由于事件对象不能像文件和注册表项那样被复制,因此如果要打开的事件对象已经存在于主机系统中,则AVM将直接将请求传递到WINDOWS的原始ZwCreateEvent系统调用中。
(2) ZwOpenEvent
该系统调用函数用于打开一个已经存在的事件对象。该函数的第三个参数为指向OJECT_ATTRIBUTES结构的指针,获得目标事件对象路径的方式与在论述文件系统访问控制的实现方式完全一样。首先AVM在目标事件对象路径前加入AVM的内核对象根目录,例如“\VM”,然后用重定向后的路径向WINDOWS请求打开事件对象。如果成功则返回该事件对象的句柄。如果不成功,则AVM将直接尝试打开请求的事件对象。如果打开成功,该情况就与前面在ZwCreateEvent截获控制函数中介绍情况一致,AVM需要将打开得到的句柄直接返回。当然不成功就返回打开失败信息。
因为各种内核对象的创建打开方式都是一样的,所以这里不一一论述它们的实现方式。其它的内核对象的创建,打开操作系统调用在表2中列出,其对应的截获控制函数可以参照ZwCreateEvent和ZwOpenEvent的实现。
表2各种内核对象创建/打开操作系统调用
对象类型创建操作打开操作
互斥对象ZwCreateMutant
ZwOpenMutant
信号量对象ZwCreateSemaphore
ZwOpenSemaphore
定时器对象ZwCreateTimer
ZwOpenTimer
区域对象ZwCreateSection
ZwOpenSection
端口对象ZwCreatePort
ZwConnectPort
2.5 网络接口虚拟
网络服务程序的启动需要建立套接字并调用bind()函数绑定本地IP和本地端口号。为了成功在多个虚拟机中运行同一网络服务程序的不同实例,网络接口必须被虚拟,因为操作系统不允许不同进程绑定同一IP和端口号。AVM允许用户为每个虚拟机指定唯一的IP地址,并使用IP aliasing指派虚拟机IP到物理网络接口,当虚拟机启动,该IP地址作为主机网络接口的别名被添加到主机,而虚拟机停止时,该IP地址从物理接口上删除。
2.6 限制进程间通信
WINDOWS消息允许一个进程向同一桌面上任何窗体发送各种类型的消息。发送进程和接收窗体可以属于不同进程。当前AVM原型并没有分派每个虚拟机各自一个桌面,因此,AVM必须通过拦截消息转换相关的系统调用来识别穿越在多虚拟机的WINDOWS消息。除WINDOWS消息之外,AVM还对属于一个虚拟机的活动的顶层窗口进行重命名方法是在其名字前添加虚拟机名字和ID。最后,其它WINDOWS IPC机制,如粘贴板数据转换和Component Object Model (COM)程序的内部作用也需要被虚拟。
2.7 守护服务的虚拟
WINDOWS守护服务是命名的win32 service,由系统进程Service Control Manager(SCM)来管理。SCM是关键系统进程,与操作系统组件有着复杂的关联,不能被复制到每个虚拟机中。因此,虚拟机共享同一个SCM和相同的SCM数据库。思路是当服务安装时在服务镜像名和虚拟机ID之间做映射,在运行时转换到进程ID和虚拟机ID的映射。