2. 四川大学 网络空间安全研究院, 四川 成都 610207;
3. 四川大学 计算机学院, 四川 成都 610065
2. Cyber Science Research Institute, Sichuan University, Chengdu 610207, China;
3. College of Computer Science, Sichuan University, Chengdu 610065, China
虚拟机自省(virtual machine introspection, 简称VMI)机制最早于2003年由Tal Garfinkel和Mendel Rosenblum在Livewire[1]中提出, VMI通过在目标虚拟机(target virtual machine, 简称TVM)外部获取TVM的底层状态, 如CPU寄存器、内存、存储设备等, 可有效地监控或干预TVM的运行.随着软硬件技术的高速发展, 以KVM、XEN、VMWare等为代表的系统虚拟化技术极大地推动了由多任务计算到多操作系统计算的发展, 已成为云计算架构中关键的抽象层次和重要的支撑技术[2].传统的安全软件置于TVM内部, 因与恶意软件运行于同一特权级, 易被其绕过或攻击而失效[3].VMI基于虚拟化技术, 利用虚拟机监视器(virtual machine monitor, 简称VMM)的高特权级与强隔离性, 能够实现安全软件与恶意软件的分离, 具有隔离性、自省性与干预性[4], 在恶意软件分析[5]、入侵检测系统[1]、内核完整性检测[6]等安全方面得到广泛应用.VMI在TVM外部获取的底层状态为二进制数据, 其代表的操作系统级语义是无法直接知晓的, 这种语义差异被称为VMI的语义鸿沟(sematic gap)[7]问题.将得到的二进制底层数据转换为可理解的高层语义的过程, 即为语义重构.
语义重构是VMI技术的关键步骤.现有工作从不同层次、不同角度进行了研究与尝试, 以期准确、高效地进行语义重构, 得到TVM中的信息, 实现对TVM的安全监控.根据语义重构的方法及依赖条件, 可将现有解决方案分为以下几类.
(1) 基于目标虚拟机.
根据进行语义重构的位置, 又可将此种方案分为两类.
1) 直接在TVM中获取语义信息.如X-TIER[8]在TVM中添加内核模块, 借助TVM本身获取丰富的语义信息, 但其自身容易受到攻击;
2) 借助TVM内部信息在TVM外部进行语义重构.如: 开源内存取证框架Volatility[9]通过在TVM内部编译文件获取内核的相关信息, 基于这些信息对TVM的内存镜像进行离线解析得到高层语义; Libvmi[10]基于XenAccess[11]可实现对TVM内存及寄存器信息的实时读写, 但其需要在TVM中加载内核模块得到进程结构体关键成员相对于结构体首地址的偏移量(下文中均简称为进程偏移量)来定位解析TVM内存中的关键信息; vDetector[3]基于AntFarm[12], 通过硬件架构知识在VMM层获取TVM中真实的进程视图, 其同样需要在TVM中加载内核模块得到进程偏移量, 以便在TVM外部实现进程语义的重构.
(2) 基于安全虚拟机(secure virtual machine, 简称SVM).
其中, 文献[13]在SVM中同步模拟TVM中待监控进程的执行, 完成对TVM进程信息的获取与监控. Virtuoso[14]将如ps等的待检测指令在SVM内执行多次, 抽取与运算相关的指令生成指令路径, 通过路径片段抽取、融合、转译, 形成可在TVM外执行语义重构的代码.该方法需反复训练, 性能损耗较大.VMST[15]对Virtuoso进行改进, 利用内核重定向技术将数据访问重定向到TVM内存中生成自省程序.文献[16]结合了Virtuoso[13]和VMST[14], 在性能方面有了较大的提升, 但其需要两个与TVM内核版本相同的SVM进行自省操作, 消耗了宿主机资源.
(3) 基于内核源码或内存转储文件.
如KOP[17]基于points-to[18]对内核的源代码进行分析构造拓展类型图, 可在内存快照中映射内核数据结构. 改进的InSight[19]基于内核源码建立指针类型之间的used-as关系, 实现内核数据结构中指针声明类型至其实际用途及目标地址的转换, 以处理TVM内核的动态指针操作简化VMI程序的开发.VMwatcher[20]从源码中获取内核数据结构定义作为模板, 得到进程偏移量等信息重构外部语义视图.Min-C[21]基于内核源码自动生成语义重建库, 并结合其构造的C解释器定位解析TVM的相关数据结构获取高层语义.Volatilinux[22]则从内存转储文件中得到内核结构偏移, 并基于此重构出TVM的进程级语义信息.
上述语义重构方案均存在对TVM内核版本敏感的问题, 对于不同内核版本的TVM, 上述(1)类方法需要再次访问TVM并手动配置进程偏移量信息; (2)类方法需要安装与TVM内核版本一致的SVM完成自省; (3)类方法则需要重新分析内核源码, 导致系统的通用性及可移植性差, 自动化程度低.此外, 基于TVM的VMI方法可能因不具备权限或源码缺失等情况无法加载内核模块, 导致自省无法实施.基于SVM的VMI方法需配置SVM, 对自省环境的要求高, 且性能损耗较大.基于内核源码的VMI方法需分析及搜索的信息较多, 自省效率不高, 且分析内存转储文件无法获取运行状态TVM的信息.针对这些不足, 本文基于KVM-QEMU虚拟化平台, 提出了一种虚拟机自省中的语义重构改进方法VMOffset(virtual machine offset), 主要贡献如下:
(1) VMOffset解决了上述VMI方法实现过程与TVM内核版本相关的问题.在不需要知道TVM内核版本的情况下, VMOffset可基于结构体成员自身属性制定约束条件获取TVM的进程偏移量信息.该方法屏蔽了TVM内核版本的差异性, 无需访问TVM, 对TVM无侵入性, 且无需额外引入SVM, 具有通用性及可移植性;
(2) VMOffset在TVM启动阶段完成进程偏移量的获取, 性能损耗小, 且启动阶段不会出现内核Rootkit等攻击, 保障了所得信息的安全性.此外, VMOffset的实现过程不依赖特定内核源码, TVM内核源码的修改不会影响结果的正确性与可靠性;
(3) VMOffset可将所得进程偏移量提供给自研或开源VMI工具完成语义重构过程, 无需手动进行信息配置, 自动化程度高, 且可扩展VMI开源工具以实现虚拟机隐藏进程检测, 进程代码段完整性度量功能, 增强虚拟化环境下的安全能力.
本文第1节介绍VMOffset系统的设计及架构.第2节阐述关键技术.第3节对VMOffset进行测试评估.第4节总结全文.
1 VMOffset系统设计将在TVM外部提取的底层状态数据重构为高层语义信息的关键是获取两类信息: (1) 计算机组织、存储待重构数据元素的方式, 如链表、树等; (2) 待重构数据元素的三元组信息(数据元素类型, 起始地址, 偏移量)[2].同样地, VMOffset通过获取这两类信息实现语义重构过程, 其设计基于以下前提: (1) Linux操作系统通常使用链表组织关键信息, 如进程、文件、内核模块等; (2) 虽然内核数据结构成员数目及布局会因内核版本的不同而不同, 但部分关键属性成员是一直存在的, 且其类型不变.以进程为例, 如图 1所示, Linux内核使用task_struct结构体描述单个进程的详细信息, 并使用双向循环链表将各个进程的task_struct组织起来.该结构体中部分关键成员是一直存在的, 如成员pid为进程号, 用于标识各个进程; 成员comm可描述进程名称; 成员tasks用于链接整个进程链表.可知: 若得到某个进程task_struct结构体的起始地址, 根据pid及comm的偏移量, 就可重构出该进程的进程号及进程名; 同理, 根据tasks成员的偏移量, 即可通过其链表指针得到下一个进程tasks成员的地址, 依次遍历可得到TVM中的所有进程信息.
|
Fig. 1 Structure of process linked list in Linux 图 1 Linux进程链表结构 |
在该种语义重构方法中, 不同于多数VMI工具需依赖TVM本身或内核源码等得到进程偏移量, VMOffset可屏蔽TVM内核版本的差异性, 实现在TVM外部对TVM中进程偏移量的自动获取, 并基于此完成TVM进程级语义的重构.VMOffset基于公式(1)及公式(2)设计实现:
| $ MemAddr_{t s A}- TaskAddr _{{ts } A}= MemAddr _{{ts } B} - TaskAddr _{{ts } B}, \forall A, B \in Prolist $ | (1) |
| $ Member \vDash Condition Set, \forall Member \in Task\_Struct $ | (2) |
如公式(1)所示, 其中, Prolist为进程链表, A、B表示进程链表中的任意进程实例, MemAddrtsA与MemAddrtsB分别表示A和B进程描述符中某相同成员的地址, TaskAddrtsA、TaskAddrtsB则分别为A、B进程描述符的起始地址.由公式(1)可知: 对TVM进程链表中的任意不同进程实例而言, 进程描述符task_struct结构体某一成员的地址相对于进程描述符首地址的偏移量是不变的, 即, 进程偏移量对各个进程实例而言是相同的.
如公式(2)所示, 其中, Task_Struct为进程描述符, Member表示进程描述符中的某个成员, ConditionSet为特定约束条件的集合.因进程描述符中各个成员均有其特定于自身的意义, 因此会满足基于其自身属性的相关约束条件.如pid作为各个进程的进程号, 具有标识进程的作用, 因此各个进程的pid均唯一, 即: pid成员需满足对不同进程实例而言, 其值必须不同的约束条件.
VMOffset基于上述思想完成TVM进程偏移量的自动获取, 其总体架构如图 2所示.
|
Fig. 2 Overall architecture of VMOffset 图 2 VMOffset总体架构 |
图 2中VMOffset各个模块的功能与总体流程如下.
(1) 陷入点初始化模块: 该模块在VMM中透明修改TVM中特定内核函数的指令代码, 拦截TVM中特定内核函数调用, 以监控TVM中进程/线程的创建及消亡事件, 使其能够产生VM-EXIT陷入至VMM中, 以此触发后续获取进程偏移量的相关流程;
(2) 进程偏移量获取模块: 该模块基于陷入点, 在VMM中获取指定监控事件发生后TVM的寄存器状态及内存状态等信息, 依照公式(2), 结合基于成员自身属性制定的约束条件获取进程偏移量.由公式(1)可知: 对不同进程实例而言, 其相同成员的偏移量是不变的.因此, TVM中不同进程实例因产生特定监控事件触发VM-EXIT陷入VMM时, 均可执行相同的流程以获取进程偏移量.该模块获取偏移量的进程结构体成员包括: 可表示进程基本信息的pid、comm, 用于遍历进程链表的tasks, 用于管理进程内存信息的mm, 用于查找进程页表访问进程指定地址内容的mm.pgd以及可用于获取进程代码段内容的mm.start_code及mm.end_code;
(3) 恢复模块: 因获取偏移量的各个成员属性不同, 各自依赖的约束条件也不尽相同, 因此, 并非某一次操作即可获取全部所需进程偏移量.故对于每次VM-EXIT, 完成进程偏移量获取模块的相关操作后, 恢复模块判断是否已获取全部所需进程偏移量: 若尚未获取完毕, 则进行相关模拟操作后执行VM-ENTRY进入TVM完成内核函数的正常执行流程; 否则恢复对TVM中相关内核函数的修改, 使进程/线程的创建及消亡动作不再产生VM-EXIT陷入至VMM, 减少了对TVM的影响, 并将所得进程偏移量提供给开源或自研的VMI应用程序, 完成TVM的自省过程.
2 VMOffset关键技术 2.1 拦截内核函数调用的事件监控机制VMOffset通过监控TVM中进程创建及消亡的内核函数调用事件, 触发VMM中进程偏移量的获取流程, 并基于此触发点得到TVM的状态信息, 作为后续获取进程偏移量的条件与依据.Linux用户进程的创建通过系统调用fork(·)、clone(·)和vfork(·)实现, 进程的消亡通过系统调用exit(·)、wait(·)实现.
可通过修改SYSENTER_CS_MSR寄存器[23]或系统调用表[24]等方式拦截上述系统调用, 以监控进程的创建及消亡.但该种方式存在如下缺陷.
1) 系统调用是用户进程访问内核服务的一个接口, 内核线程直接通过调用内核函数使用内核提供的服务.因此在TVM系统启动阶段, 产生用户进程之前, 并不会发生系统调用事件, 故该种方式无法监控TVM整个时期的进程创建及消亡动作;
2) 在虚拟化环境下, 为了在VMM层捕获到TVM中的系统调用事件, 通常需要设置虚拟机控制结构体的相关字段[24], 用于在VMM中捕获缺页异常或一般保护异常.这样会引入大量与系统调用无关的异常陷入, 对TVM系统性能带来较大影响.
针对上述不足, VMOffset通过监控特定内核函数的调用来感知TVM中进程创建及消亡的动作, 这种方式可监控TVM各个阶段的进程创建及消亡动作, 且不会引入无关的异常陷入.系统调用fork(·)、clone(·)和vfork(·)的服务例程分别为sys_fork(·)、sys_clone(·)和sys_vfork(·), 这3个函数最终都会调用do_fork(·)函数实现具体创建工作.系统调用exit(·)和wait(·)的服务例程分别为sys_exit(·)、sys_wait(·), 分别调用内核函数do_exit(·)及do_ wait(·), 这两个内核函数最终都会调用函数release_task(·)释放待消亡进程的进程描述符.因此, VMOffset通过监控内核函数do_fork(·)及release_task(·)的调用, 实时感知TVM中进程创建及消亡事件, 其实现原理如图 3所示.
|
Fig. 3 Principle of event monitoring 图 3 事件监控原理 |
函数之间的调用通过栈来实现, 函数执行时所用栈被称为函数栈帧, EBP寄存器作为帧指针, 指向函数栈帧的开始位置; ESP寄存器作为栈指针, 则指向栈顶位置.当调用函数以call方式调用被调用函数时, call指令首先将调用函数call指令的下一条指令地址压入栈中, 然后将被调用函数地址保存至EIP寄存器.执行流程从EIP寄存器指定地址开始到达被调用函数, 被调用函数首先通过“pushl%ebp; movl%esp, %ebp”保存调用函数的栈帧信息, 便于被调用函数执行完毕后还原调用函数栈帧信息.因此, 为维护函数栈帧, 以call方式调用的函数的开头位置均为三字节的“pushl%ebp; movl%esp, %ebp”指令.由Intel手册可知: 可陷入敏感指令VMCALL机器码为“.byte 0x0f, 0x01, 0xc1”, 长度也为三字节.基于此, VMOffset通过指令替换来感知TVM的内核函数调用行为.为了让不同的内核功能单元更好地协同工作, Linux内核编译过程中会生成System.map文件存放内核符号表, 其中包含所有的全局内核项(函数及变量)的地址, 可在表中查找符号名得到对应的符号地址.VMOffset参考开源项目OpenCIT[25], 在TVM外部挂载TVM镜像得到其内核符号表, 随后从内核符号表中读取内核函数do_fork(·)及release_task(·)的入口地址, 传至陷入点初始化模块.如图 3所示, 该模块在TVM启动之前, 在VMM层将上述内核函数起始三字节的函数栈帧维护指令替换为VMCALL指令, 该操作对TVM透明, 无需在TVM中添加代理模块.此后, TVM中发生进程/线程创建及消亡的事件时, 就会因VMCALL指令陷入至VMM中, VMM中完成获取进程偏移量的处理流程之后, 模拟TVM中函数栈帧维护行为, 之后返回TVM继续执行.待所需进程偏移量全部获取完毕后, VMOffset恢复对内核函数的修改, 使其不再陷入, 减少了对目标虚拟机的影响.
2.2 基于迭代的进程偏移量获取方法VMOffset拦截TVM中内核函数do_fork(·)及release_task(·)的调用事件后, 以TVM的寄存器状态、内存状态信息及相关约束条件为依据, 基于迭代的方法进行进程偏移量的获取.首先定义相关实体如下.
● XFCandi_init={xf|xf为初始状态下成员X的偏移量候选项};
● XFCandi_prev={xf|xf为执行偏移量获取流程前成员X的偏移量候选项};
● XFCandi_not={xf|xf为陷入时刻内存状态不满足成员X约束条件的偏移量候选项};
● XFCandi_after={xf|xf为执行偏移量获取流程后成员X的偏移量候选项}.
根据上述定义, 可知执行一次偏移量获取流程后, 有: XFCandi_after=XFCandi_prev-XFCandi_not.
基于上述定义及VMOffset设计思想, 图 4以伪代码的方式给出了VMOffset获取进程结构体某特定成员偏移量的算法过程, 其中部分变量及函数的说明见表 1.
|
Fig. 4 Process of acquiring process offset 图 4 进程偏移量获取过程 |
| Table 1 Related description of process offset acquisition algorithm 表 1 进程偏移量获取算法相关说明 |
图 4中:
● 第1行~第4行首先依据待获取偏移量的成员类型得到其偏移量的所有候选项, 即XFCandi_init.以pid为例, Linux使用4字节的int类型存储进程的pid, 根据结构体的字节对齐原则可知, 成员pid相对于task_struct结构体起始地址的偏移量必须是4的倍数, 故设置其specific_size为4.需注意的是: TVM内核源码可能会被修改, 因此从0开始到task_struct结构体大小的范围内, 每增加specific_size的值均可能为pid成员的偏移量, 将这些值均作为成员pid的偏移量候选项添加至集合中;
● 第5行~第13行则对所有候选项进行筛选, 得到最终偏移量.其中, 第6行~第11行为一次偏移量获取的流程.
➢ 第6行根据陷入时刻TVM的寄存器信息获取当前进程结构体的首地址;
➢ 第7行~第11行则对集合中的每个偏移量候选项, 首先获取该地址处的内存内容, 然后以基于该成员属性制定的约束条件为依据, 判断其内容是否满足约束条件: 若不满足, 则将该候选项从集合中删除.
第1次执行偏移量获取流程时, XFCandi_prev即为XFCandi_init, 依照条件删除XFCandi_not后, 可得XFCandi_after.由于对不同进程实例而言, 其相同成员的偏移量是不变的, 因此, 当TVM中不同进程实例因产生指定监控事件陷入到VMM中时, VMOffset均可以上一次得到的XFCandi_after为此次获取流程的XFCandi_prev, 执行第6行~第11行的偏移量获取流程, 删除此次内存状态不满足约束条件的XFCandi_not集合, 得到新的XFCandi_after.迭代地执行此过程, 直到对应集合中仅有一个成员, 此时说明已成功获取该结构体成员的偏移量, 将其输出.
在TVM启动过程中, 对某特定成员而言, TVM中各个进程实例在该成员的各个候选偏移量处的内存内容是动态变化的, 其内存内容包括满足和不满足所制定约束条件两种可能.将候选偏移量处内存内容满足约束条件的事件表示为T, 以PT表示事件T发生的概率.可知: 对某成员唯一正确的偏移量而言, 其内存内容会始终满足基于其自身制定的约束条件, 故其PT始终为1;而对该成员的其他候选偏移量而言, 其内存内容则会逐渐表现出该处成员自身特性, 而非约束条件所属成员特性, 因此, 其PT小于1.若事件T发生的概率PT等于1, 则n次迭代过程中事件T一直发生的概率(PT)n也始终为1, 即正确的偏移量在迭代过程中会始终保留不被删除.由公式(3)可知: 当事件T发生的概率PT小于1时, n次迭代过程中事件T一直发生的概率(PT)n会逐渐减小至接近0:
| $\mathop {\lim }\limits_{n \to \infty } {({P_T})^n} = 0, 0 < {P_T} < 1$ | (3) |
一般而言, TVM启动过程中调用do_fork(·)及release_task(·)的次数超过1 000次.因此, 随着上述过程迭代次数的增加, 不符合约束条件的候选偏移量会被逐渐删除, 最终得到唯一正确的偏移量.
2.2.1 获取进程描述符起始地址如图 5所示, Linux内核中, 默认情况下内核栈占据两个连续的物理页面, 即8KB.内核栈由高地址向低地址增长, 8KB地址区间的起始位置保存着线程描述符thread_info.当TVM中进程因调用do_fork(·)及release_task(·)陷入至VMM时, TVM ESP寄存器指向内核栈的栈顶单元, 通过屏蔽栈顶地址的低13位, 可获取线程描述符thread_info结构体的首地址, 其第1个成员task指向当前进程的进程描述符task_struct, 故可基于此获取进程描述符task_struct结构体的起始地址.
|
Fig. 5 Relationship between kernel stack and process descriptor 图 5 内核栈与进程描述符的关系 |
2.2.2 获取目标虚拟机内存内容
为了让虚拟机使用一段隔离的、从0开始且连续的内存空间, 内存虚拟化机制引入了一层新的地址空间, 即虚拟机物理地址空间.如图 6所示, 虚拟机内存地址访问时存在两次地址转换: 虚拟机页表完成虚拟机虚拟地址GVA(guest virtual address)到虚拟机物理地址GPA(guest physical address)的转换, 该转换过程中得到的虚拟机各级页目录项地址及最终的GPA均不能直接发送到真实物理机的系统总线, 需通过VMM的扩展页表EPT (extended page table)将其转换为宿主机物理地址HPA(host physical address).因此, VMOffset通过在VMM中实现上述地址转换流程, 完成对TVM中指定GVA的内存访问, 获取其对应的内容.
|
Fig. 6 Virtual machine address translation process 图 6 虚拟机地址转换流程 |
2.2.3 基于成员属性的约束条件制定
VMOffset基于结构体成员自身属性制定相关约束条件, 并依据约束条件对各个成员的偏移量候选项进行筛选.具体地, 如公式(4)所示: 定义XFcandi为成员X所有偏移量候选项的集合, xf为集合中的每个候选偏移量, Addrts为TVM发生VM-EXIT时当前进程task_struct结构体的起始地址, Con(Addrts+xf)为其xf偏移处的内存内容, ConditionSet为成员X的约束条件.VMOffset基于第2.2节所述迭代方法, 依据ConditionSet逐步缩小XFcandi集合的成员数目得到成员X的最终xf:
| $ X F_{ {candi }}=\left\{x f \mid {Con}\left(A d d r_{t s}+x f\right) \in { Condition Set }\right\} $ | (4) |
基于公式(4), 针对不同的成员, 具体化约束条件与筛选方法如下.
1) 成员tasks.
tasks成员为进程链表节点, 在未遭受内核Rootkit攻击的情况下, tasks所在链表的成员数目应与虚拟机中真实进程总数一致.基于此, 制定其约束条件如下:
| $T{F_{candi}} = \left\{ {tf\left| {\begin{array}{*{20}{c}} {Nu{m_{real}} = Nu{m_{tf}}, } \\ {Nu{m_{real}} = Coun{t_{fork}} - Coun{t_{exit}}, Nu{m_{tf}} = Traverselinklist(Con(Add{r_{ts}} + tf))} \end{array}} \right.} \right\}$ | (5) |
公式(5)中, Numreal为TVM中真实进程数目, 通过VMM拦截的do_fork(·)的调用次数Countfork及release_task(·)的调用次数Countexit得到.将Con(Addrts+tf)视为一个进程链表节点, 依次遍历即可得到该节点所在链表的成员总数Numtf.基于公式(5)筛选tf的过程中需注意:
(1) Linux内核的第1个进程swapper由内核自身由无到有创建, 其使用静态分配的数据结构, 不通过调用内核函数do_fork(·)得到.该进程作为Linux内核的祖先进程, 负责创建Linux中的其他进程完成内核的初始化;
(2) 当VMM中拦截到do_fork(·)及release_task(·)的调用事件时, 函数的具体内容尚未执行, 因此当前时刻, TVM中进程的数目并未改变; 待函数执行完毕后, TVM中进程总数才会增加或者减少.如VMM中第1次拦截到do_fork(·)时, 可知TVM中当前进程为swapper进程, 且该时刻TVM中仅有1个进程, 即Numreal为1.
2) 成员pid.
pid成员作为进程的标识, 其约束条件可用公式(6)表示:
| $P{F_{candi}} = \left\{ {pf\left| {\begin{array}{*{20}{c}} {Co{n_i}(Addr_{ts}^i + pf) \ne Co{n_j}(Addr_{ts}^j + pf), } \\ {0 \leqslant Co{n_i}(Addr_{ts}^i + pf) < PidLimit, PidLimit < 32768, \forall i, j \in Prolist, i \ne j} \end{array}} \right.} \right\}$ | (6) |
其中, Prolist为TVM中进程链表, i、j分别为链表中某个进程.公式(6)表示对进程链表中的任意不同进程, 其pid的值都是不同的, 且pid均处于0~PidLimit的范围内.Linux系统一般从0开始, 依次连续分配pid直到可分配的pid上限, 之后则重新从某个值开始依次连续查找未被使用的pid进行分配.默认情况下, 不同系统的pid上限是不同的, 且可被修改.但是由于内核初始化阶段分配的pid均较小, 且部分系统的pid上限默认为32 768, 因此VMOffset将PidLimit设为32 768.基于此, 对pf进行筛选.
3) 成员comm.
comm成员用于描述进程名称.如前所述, Linux内核静态初始化其第1个进程的task_struct结构体内容, 其中, 进程名通过宏定义为“swapper”, 可通过匹配“swapper”字符串筛选comm的偏移量候选项.但若内核源码被修改, “swapper”被改为其他任意字符串, 则此种匹配方式得到的偏移量将是错误的, 进而会导致自省结果发生错误.因此, VMOffset基于其自身属性制定约束条件.如公式(7)所示: 成员comm表示进程名称, 故其每个字符的ASCII码均在可显示字符的ASCII码范围内, 且该字符数组的内容不可为空, 并以字符‘\0’结尾:
| $C{F_{candi}} = \left\{ {cf\left| {\begin{array}{*{20}{c}} {Co{n_i}(Addr_{ts}^i + cf)[j] \in ShowCharSet, } \\ {Co{n_i}(Addr_{ts}^i + cf)[end] = '\backslash 0', \forall i \in Prolist, \forall j \in [0, commlen), commlen > 0} \end{array}} \right.} \right\}$ | (7) |
公式(7)所描述的约束条件不会因内核源码的修改而发生变化, VMOffset基于此筛选cf.
4) 成员mm.
mm成员用于描述进程虚拟地址空间信息.由内核源码可知: 一般而言, mm成员的下一个成员即为active_ mm.而swapper进程的active_mm成员由内核静态初始化为init_mm的地址, 该地址可由TVM内核符号表得到, 可基于此获取mm的偏移量.但是为了保证内核源码被修改后所得偏移量的正确性, VMOffset不以成员位置关系为依据, 而是基于成员自身属性进行约束条件的制定.成员mm的特点如公式(8)所示: Linux中, 内核线程的mm成员为0, 用户进程mm成员的值则与active_mm成员的内容一致:
| $M{F_{candi}} = \{ mf|Co{n_i}(Addr_{ts}^i + mf) = 0||Co{n_i}(Addr_{ts}^i + mf) = Co{n_i}(Addr_{ts}^i + amf), \forall i \in Prolist\} $ | (8) |
公式(8)中, amf为成员active_mm的偏移量, Prolist的含义同公式(6).
5) 成员pgd.
mm_struct结构体中成员pgd用于存储进程的页目录基地址, 该值对应的物理地址会被载入CR3寄存器, 用于遍历进程页表实现地址转换功能.pgd的值与CR3寄存器的值相差page_offset, 32位下, page_offset为0xc0000000;64位下, 则为0xffffffff80000000.因此, 其约束条件可由公式(9)描述:
| $PG{F_{candi}} = \{ pgf|Co{n_i}(Addr_{ms}^i + pgf) = Vcpu.cr3 + page\_offset, \forall i \in UserPro\} $ | (9) |
其中, i为某个用户进程;
6) 成员start_code及end_code.
进程代码段在内存中的起始地址与结束地址分别存储在mm_struct结构体的start_code及end_code域中. IA-32体系下, 用户进程虚拟地址空间布局如图 7所示.
|
Fig. 7 Process virtual address space layout of IA-32 architecture 图 7 IA-32进程虚拟地址空间布局 |
进程代码段为图 7中所示的text segment, 每个体系结构为text段制定了一个特定的起始地址: x86架构下始于0x08048000, x86_64下则为0x400000.基于此, 可得到成员start_code的偏移量.一般而言, 内核源码中成员start_code与end_code处于相邻位置.同样地, 为保证所得偏移量的可靠性, VMOffset以end_code成员属性为依据获取对应偏移量, 其约束条件可由公式(10)描述:
| $EC{F_{candi}} = \left\{ {ecf\left| {\begin{array}{*{20}{c}} {{\rm{Min}}Limit \leqslant Co{n_i}(Addr_{ms}^i + ecf) < {\rm{Max}}Limit, PageAccess(Co{n_i}(Addr_{ms}^i + ecf)) = RO\& X} \\ {{\rm{Min}}Limit \geqslant Co{n_i}(Addr_{ms}^i + scf), {\rm{Max}}Limit < TASK\_SIZE, \forall i \in UserPro} \end{array}} \right.} \right\}$ | (10) |
其中, i、UserPro及
公式(10)中,
|
Fig. 8 Algorithm of filtrating end_code offset 图 8 end_code offset筛选算法 |
图 8中: 第1行~第9行基于进程虚拟地址空间布局及代码段所在页面权限更新MinLimit和MaxLimit, 选取具有可读写或不可执行权限的最小offset_content作为MaxLimit, 同时选取具有只读且可执行权限的最大offset_content作为MinLimit; 第10行~第14行则基于已更新的MinLimit与MaxLimit, 删除offset_content不在此范围内的ecf.图 8为筛选1次ecf的过程, 多次迭代可得到最终ecf.
由上述基于成员属性制定约束条件, 依照约束条件筛选各个成员偏移量候选项的过程可知, 不同成员自身的约束条件与其所依赖条件均存在差异.如筛选tasks成员偏移量候选项的过程仅依赖TVM中真实进程数目; 而获取pid成员的偏移量则还依赖tasks offset遍历进程链表, 得到所有进程pf偏移处的内容进行对比.因此, VMOffset需基于成员自身属性与各个成员之间的相互联系, 依次获取所需成员对应的偏移量.
3 实验及结果分析本节从功能及性能两个方面对VMOffset进行评测.功能测试用于验证VMOffset的有效性, 即: 能否屏蔽TVM内核版本的差异性, 自动获取其进程偏移量, 并基于此实现语义重构.性能测试用于验证VMOffset对TVM的性能影响.VMOffset基于KVM-QEMU虚拟化平台实现, 实验环境如下: 宿主机为Intel(R) Core(TM) i3-4160双核CPU, 主频为3.60GHz, 物理内存为4GB, 支持硬件辅助虚拟化.操作系统为64位CentOS7, 内核版本3.10.1, KVM版本kvm-kmod-3.10.1, QEMU版本Qemu-2.3.0.
3.1 功能测试为验证VMOffset的内核版本无关性, 部署了不同内核版本操作系统的TVM, 见表 2.
| Table 2 Environment configuration of virtual machine 表 2 虚拟机环境配置 |
实验中, TVM的内核版本不同, 对应的进程偏移量也不同.以成员pid的偏移量为例, CentOS6.5中为1 192, Ubuntu12.04中为516, CentOS7中为1 188, Ubuntu14.04中为1 064.在上述TVM内核版本未知的前提下, VMOffset均能自动获取其进程偏移量, 所得偏移量可提供给自研或开源VMI应用程序完成语义重构.为说明VMOffset对开源VMI工具的支持与兼容, 本节以VM2为例, 将VMOffset所得进程偏移量提供给Libvmi, 完成虚拟机自省过程.Libvmi是一款具有代表性的开源VMI工具, 专注于读写虚拟机内存, 但其需要在TVM中加载内核模块获取进程偏移量, 并将其手动写入配置文件中.本节将VMOffset所得进程偏移量提供给Libvmi, 使其无需进行手动获取及配置的操作.图 9所示为VMOffset结合Libvmi对TVM中进程视图的自省结果, 限于篇幅, 此处仅截取部分进程信息.图 9(a)为某一时刻TVM中进程视图, 图 9(c)为同一时刻VMOffset在TVM外部重构所得进程视图, 可见两者的结果是一致的.进程视图的重构可用于检测隐藏进程, 因此本节在TVM中编译安装Adore-ng工具, 隐藏pid为28的进程.隐藏后, TVM中进程视图如图 9(b)所示, 其中, 28号进程已被隐藏.而VMOffset重构视图仍与图 9(c)一致, 其中显示了被隐藏的进程crypto.可见: VMOffset可自动实现TVM中进程信息的语义重构, 且通过对比重构所得进程视图与TVM内部进程视图, 可以有效地发现TVM中的隐藏进程.
|
Fig. 9 Process view introspection results of VMOffset 图 9 VMOffset进程视图自省结果 |
除了重构TVM中的进程视图外, VMOffset还可重构TVM中指定进程的代码段, 代码段的获取可用于入侵检测, 如通过验证代码段的完整性可判断该进程是否被恶意篡改.本节参考VMPMS[26]提出的进程代码段分页式度量方法, 基于VMOffset所得进程偏移量, 在TVM外部开发用户层工具实现对TVM中指定进程的代码段完整性度量.以Apache服务进程httpd为例, 图 10为其度量结果.图 10(a)为httpd进程正常运行状态下的度量结果, 基值通过对SVM中httpd进程代码段各页进行SHA256运算得到.图 10(b)为模拟攻击后httpd进程度量结果, 对比可见: 模拟攻击后进程名称和代码页地址等语义信息并无变化, 但代码页的度量值发生了改变, 如126~128等页.可见: VMOffset可有效重构TVM中指定进程的代码段, 且可实现其代码段的完整性度量, 判断该进程是否被恶意篡改.
|
Fig. 10 VMOffset measurement result of process code segment in TVM 图 10 VMOffset对TVM中进程代码段的度量结果 |
3.2 性能测试
本节通过相关测试评估VMOffset对TVM的性能影响, 并考察VMOffset重构TVM进程视图及对TVM中进程代码段进行完整性度量的效率.最后对比了VMOffset与现有其他虚拟机自省工具, 评估了VMOffset的有效性与通用性.
VMOffset需在TVM启动阶段拦截其特定内核函数调用, 以触发进程偏移量的获取流程.这一操作会导致本不会发生的VM-EXIT, 对TVM的启动带来影响.由于VMOffset在获取所需进程偏移量后会还原修改, 因此本节通过测试VMOffset对TVM启动时间的影响, 及VMOffset导致TVM发生VM-EXIT的次数与TVM启动阶段调用do_fork(·)及release_task(·)总次数的占比, 来评估VMOffset对TVM启动阶段的性能影响.
表 3为对VM2及VM4分别使用VMOffset获取10次进程偏移量所得测试结果的平均值, VM2及VM4的配置信息见表 2.表 3中, TVM的启动时间通过开源工具软件BootChart测试得到, 总次数为TVM启动阶段相关内核函数调用的总次数, 以出现用户登录界面表示TVM启动阶段的结束.可见: 对于VM2, VMOffset运行阶段导致的VM-EXIT次数仅占总次数的3.74%, 引入的时间损耗为0.042%;对VM4的VM-EXIT占比则为4.62%, 引入的时间损耗仅为0.041%.测试结果表明: VMOffset在TVM启动完成之前即可获取全部所需进程偏移量, 对TVM启动阶段引入的时间损耗在0.05%以内.实验同时测试了VMOffset运行过程中, TVM中出现的最大pid值, 其中VM2出现的最大pid值为63, VM4中则为120, 均小于公式(6)中设定的32 768, 因此设定该上限值是可行的.此外, 测试了VMOffset运行过程中部分成员偏移量候选项满足其约束条件的事件发生的次数, 如task_ struct中, tasks、children、sibling均为链表节点.在90次VM-EXIT中, children及sibling满足基于tasks制定的约束条件的次数仅为2次, 其内存内容满足约束条件的事件发生的概率小于1, 因此多次迭代后均会被删除, 从而得到tasks成员的真正偏移量.类似可知, 多次迭代之后, VMOffset可得到全部所需进程偏移量.
| Table 3 Performance test result of TVM in start-up stage 表 3 TVM启动阶段性能测试结果 |
本节同时采用UnixBench微基准测试工具评估VMOffset对TVM运行时的性能影响, 图 11为部署与未部署VMOffset环境下, VM2的系统性能测试结果.UnixBench各测试项中, 评分越高, 表明性能越好.
|
Fig. 11 Performance test result of TVM in running stage 图 11 TVM运行阶段性能测试结果 |
由图 11可知, VM2的进程创建与切换(process creation, pipe-based context switching)、系统调用(system call overhead, excel throughput)、shell脚本测试(shell scripts)、文件传输(file copy)等方面的性能在部署与未部署VMOffset环境下均无明显波动.因VMOffset对TVM的性能影响主要源于拦截其内核函数调用导致的VM- EXIT损耗, 且在获取所需进程偏移量后会还原修改使其不再陷入, 故此后TVM的运行与未部署VMOffset无异.由实验结果可知: VMOffset仅对TVM的启动阶段引入0.05%之内的时间损耗, 对其运行阶段无明显影响.
除了评估VMOffset对TVM启动阶段及运行阶段的性能影响, 本节还测试了VMOffset重构TVM进程视图及对TVM中进程代码段进行完整性度量的效率.其中, 图 12展示了不同VMI方法重构TVM中进程视图所用时间的结果, 表 4则为VMOffset对TVM中不同进程代码段度量10次所用时间的均值.
|
Fig. 12 Process view reconstruction time 图 12 进程视图重构时间 |
| Table 4 Time for VMOffset measuring the process code segment 表 4 VMOffset度量进程代码段时间 |
图 12中, VMOffset+Libvmi为修改Libvmi使其直接使用VMOffset所得进程偏移量进行视图重构, 第2列的Libvmi则为初始通过读取配置文件中的进程偏移量以重构TVM进程视图.实验时, TVM进程链表中共有97个进程.由图可知: VMOffset+Libvmi重构进程视图所用时间为2.48s, Libvmi为2.92s.可见: 前者无需手动配置进程偏移量, 且重构进程链表所用时间相比原始Libvmi要少, 效率更高.图 12还列出了两种具有代表性的VMI方法的进程视图重构时间, 其中, HyperLink[27]基于内存搜索完成语义重构, 其重构进程链表需1分钟左右; ModSG[28]则通过在后端离线解析不同版本内核结构生成语义静态库, 前端模块基于语义静态库完成视图的重构.ModSG生成Ubuntu12.04操作系统对应的语义静态库所用时间约为110s, 当TVM中进程数量为100个时, 其前端重构时间约为0.1s, 因此, 其重构进程视图时间为110.1s.对比可知, VMOffset重构TVM中进程视图的效率是较高的.
表 4中, 进程位置用于说明待度量进程为TVM进程链表中的第几个进程, 代码段页数为待度量进程代码段总页数.进程代码段的度量需首先遍历进程链表判断待度量进程是否存在, 此后获取代码段各页内容进行度量, 因此其效率与进程位置及代码段页数两者相关.由表 4可知: 所度量进程在链表中的位置越靠后, 以及代码段页数越大, 所需度量时间也越长.多数进程的度量时间在3.5s之内, 但当进程所处位置较后, 且代码段页数较大时, 其度量时间也会相应增加, 如Xorg进程度量所用时间为10s左右.因代码段的重构与度量均在TVM外部实现, 对TVM本身不会产生影响, 结合需要进行的哈希运算与地址翻译的次数来看, 这样的时间效率是可以接受的.
为了进一步评估VMOffset的有效性, 本节将VMOffset与现有部分VMI方法进行了对比, 见表 5.
| Table 5 Comparison of VMOffset and other VMI methods 表 5 VMOffset与其他VMI方法的对比 |
表 5中, VMwatcher的自省过程依赖内核源码, Virtuoso及VMST需引入与TVM环境一致的SVM, Volatility, Libvmi及vDetector需在TVM中加载内核模块获取进程偏移量, ModSG需在后端离线解析不同版本内核结构生成语义静态库.上述方法对于不同内核版本的TVM, 均需要重新修改、配置或解析.其中, HyperLink的实现对TVM内核版本不敏感, 但其重构进程链表需1分钟左右, 且仅能重构进程号及进程名称.对比之下, VMOffset具有内核版本无关性, 无需额外引入SVM或访问TVM, 其可重构的进程级语义较为丰富, 且重构效率高、性能损耗小; 此外, VMOffset所得进程偏移量可作为一个信息段随TVM的迁移而迁移, 具有较强的通用性与可移植性.
4 结语本文针对现有虚拟机自省方法中存在的实现过程与TVM内核版本相关、移植性差、系统执行效率不高的问题, 提出了一种虚拟机自省中的语义重构改进方法VMOffset.VMOffset基于KVM-QEMU虚拟化平台实现, 可在未知TVM内核版本的前提下, 自动获取TVM中进程偏移量并提供给自研或开源VMI工具完成语义重构, 具有较好的可移植性与安全性.VMOffset无需修改TVM, 在TVM启动阶段完成进程偏移量的获取, 执行效率较高, 且引入的性能损耗在0.05%以内.同时, VMOffset也存在一些局限性, 如要求硬件虚拟化技术为Intel VT-x, 且目前仅支持Linux操作系统的TVM.在下一步的工作中, 将探索VMOffset对Linux以外操作系统的支持, 并丰富VMOffset在重构文件、网络连接等信息上的功能, 实现更强的通用性及更细粒度的虚拟机自省.
| [1] |
Garfinkel T, Rosenblum M. A virtual machine introspection based architecture for intrusion detection. In: Neuman C, ed. Proc. of the Network and Distributed Systems Security Symp. Washington: Internet Society, 2003.191-206.
|
| [2] |
Li BH, Xu KF, Zhang P, Guo L, Hu Y, Fang BX. Research and application progress of virtual machine introspection technology. Ruan Jian Xue Bao/Journal of Software, 2016, 27(6): 1384-1401(in Chinese with English abstract).
http://www.jos.org.cn/jos/article/abstract/5006?st=search [doi:10.13328/j.cnki.jos.005006] |
| [3] |
Li B, Wo TY, Hu CM, Li JX, Wang Y, Huai JP. Hidden OS objects correlated detection technology based on VMM. Ruan Jian Xue Bao/Journal of Software, 2013, 24(2): 405-420(in Chinese with English abstract). http://www.jos.org.cn/1000-9825/24/405.htm[doi: cnki:sun:rjxb.0.2013-02-017]
|
| [4] |
Pfoh J, Schneider C, Eckert C. A formal model for virtual machine introspection. In: Nieh J, Stavrou A, eds. Proc. of the 1st ACM Workshop on Virtual Machine Security. New York: ACM, 2009.1-10. [doi: 10.1145/1655148.1655150]
|
| [5] |
Dinaburg A, Royal P, Sharif M, Lee W. Ether: Malware analysis via hardware virtualization extensions. In: Ning P, ed. Proc. of the 15th ACM Conf. on Computer and Communications Security. New York: ACM, 2008.51-62. [doi: 10.1145/1455770.1455779]
|
| [6] |
Xiong X, Tian D, Liu P. Practical protection of kernel integrity for commodity OS from untrusted extensions. In: Proc. of the 18th Annual Network and Distributed System Security Symp. Washington: Internet Society, 2011.1-17.
|
| [7] |
Jain B, Baig MB, Zhang DL, Porter DE, Sion R. SOK: Introspections on trust and the semantic GAP. In: Proc. of the 2014 IEEE Symp. on Security and Privacy (SP). Washington: IEEE Computer Society, 2014.605-620. [doi: 10.1109/SP.2014,45]
|
| [8] |
Vogl S, Kilic F, Schneider C, Eckert C. X-TIER: Kernel module injection. In: Lopez J, Huang XY, Sandhu R, eds. Proc. of the Network and System Security. LNCS 7873, 2013.192-205. [doi: 10.1007/978-3-642-38631-2_15]
|
| [9] |
Volatility. Volatile memory analysis framework. https://github.com/volatilityfoundation/volatility
|
| [10] |
Xiong H, Liu Z, Xu W, Jiao S. Libvmi: A library for bridging the semantic gap between guest OS and VMM. In: Proc. of the 12th IEEE Int'l Conf. on Computer and Information Technology. Washington: IEEE Computer Society, 2012.549-556. [doi: 10.1109/CIT.2012.119]
|
| [11] |
Payne BD. Xen access library. https://code.google.com/p/xenaccess/
|
| [12] |
Jones ST, Arpaci-Dusseau AC, Arpaci-Dusseau RH. Antfarm: Tracking processes in a virtual machine environment. In: Proc. of the USENIX Annual Technical Conf. New York: ACM, 2006.1-14.
|
| [13] |
Srinivasan D, Wang Z, Jiang XX, Xu DY. Process out-grafting: An efficient "out-of-VM" approach for fine-grained process execution monitoring. In: Proc. of the 18th ACM Conf. on Computer and Communications Security. New York: ACM, 2011.363-374. [doi: 10.1145/2046707,2046751]
|
| [14] |
Dolan-Gavitt B, Leek T, Zhivich M, Giffin J, Lee W. Virtuoso: Narrowing the semantic GAP in virtual machine introspection. In: Proc. of the 32nd IEEE Symp. on Security and Privacy. Washington: IEEE Computer Society, 2011.297-312. [doi: 10.1109/SP.2011,11]
|
| [15] |
Fu YC, Lin ZQ. Space traveling across VM: Automatically bridging the semantic GAP in virtual machine introspection via online kernel data redirection. In: Proc. of the IEEE Symp. on Security and Privacy. Washington: IEEE Computer Society, 2012.586-600. [doi: 10.1109/SP.2012,40]
|
| [16] |
Saberi A, Fu YC, Lin Z. Hybrid-bridge: Efficiently bridging the semantic GAP in virtual machine introspection via decoupled execution and training memorization. In: Proc. of the 21st Annual Network and Distributed System Security Symp. Washington: Internet Society, 2014.1-15. [doi: 10.14722/ndss.2014.23226]
|
| [17] |
Carbone M, Cui WD, Lu L, Lee W, Peinado M, Jiang XX. Mapping kernel objects to enable systematic integrity checking. In: Proc. of the 16th ACM Conf. on Computer and Communications Security (CCS 2009). New York: ACM, 2009.555-565. [doi: 10.1145/1653662,1653729]
|
| [18] |
Andersen LO. Program analysis and specialization for the C programming language[Ph. D. Thesis]. Copenhagen: University of Copenhagen, 1994.
|
| [19] |
Schneider C, Pfoh J, Echert C. Bridging the semantic GAP through static code analysis. In: Proc. of the 2012 European Workshop on System Security (EuroSec 2012). New York: ACM, 2012.1-6.
|
| [20] |
Jiang XX, Wang XY, Xu DY. Stealthy Malware detection through VMM-based "out-of-the-box" semantic view reconstruction. In: Ning P, ed. Proc. of the 14th ACM Conf. on Computer and Communications Security. New York: ACM, 2007.128-138. [doi: 10.1145/1315245,1315262]
|
| [21] |
Inoue H, Adelstein F, Donovan M, Brueckner S. Automatically bridging the semantic GAP using c interpreter. In: Proc. of the 2011 Annual Symp. on Information Assurance. 2011.51-58.
|
| [22] |
Volatilitux: Physical memory analysis of linux systems. http://code.google.com/p/volatilitux
|
| [23] |
Pfoh J, Schneider C, Eckert C. Nitro: Hardware-based system call tracing for virtual machines. In: Tetsu I, Nishigaki M, eds. Proc. of the 6th Int'l Conf. on Advances in Information and Computer Security. Washington: IEEE Computer Society, 2011.96-112. [doi: 10.1007/978-3-642-25141-2_7]
|
| [24] |
Chen XS, Chen MM, Jin X. Shadow memory-based agentless virtual machine process protection. Journal of University of Electronic Science and Technology of China, 2018, 47(1): 80-87(in Chinese with English abstract). [doi: cnki:sun:dkdx.0.2018-01-012]
|
| [25] |
OpenCIT: Open continuous integration and test. http://opencit.openengsb.org
|
| [26] |
Cai MJ, Chen XS, Jin X, Zhao C, Yin MY. Paging-measurement method for virtual machine process code based on hardware virtualization. Journal of Computer Applications, 2018, 38(2): 305-309(in Chinese with English abstract). [doi: 10.11772/j.issn.1001-9081.2017082167]
|
| [27] |
Xiao JD, Lu L, Wang HN, Zhu XY. HyperLink: Virtual machine introspection and memory forensic analysis without kernel source code. In: Proc. of the IEEE Int'l Conf. on Autonomic Computing. Washington: IEEE Computer Society, 2016.127-136. [doi: 10.1109/icac.2016.46]
|
| [28] |
Cui CY, Wu Y, Li P, Zhang XM. Narrowing the semantic GAP in virtual machine introspection. Journal on Communications, 2015, 36(8): 31-37(in Chinese with English abstract). [doi: cnki:sun:txxb.0.2015-08-005]
|
| [2] |
李保珲, 徐克付, 张鹏, 郭莉, 胡玥, 方滨兴. 虚拟机自省技术研究与应用进展. 软件学报, 2016, 27(6): 1384-1401.
http://www.jos.org.cn/jos/article/abstract/5006?st=search [doi:10.13328/j.cnki.jos.005006] |
| [3] |
李博, 沃天宇, 胡春明, 李建欣, 王颖, 怀进鹏. 基于VMM的操作系统隐藏对象关联检测技术. 软件学报, 2013, 24(2): 405-420. http://www.jos.org.cn/1000-9825/24/405.htm[doi: cnki:sun:rjxb.0.2013-02-017]
|
| [24] |
陈兴蜀, 陈蒙蒙, 金鑫. 基于影子内存的无代理虚拟机进程防护. 电子科技大学学报, 2018, 47(1): 80-87. [doi: cnki:sun:dkdx.0.2018-01-012]
|
| [26] |
蔡梦娟, 陈兴蜀, 金鑫, 等. 基于硬件虚拟化的虚拟机进程代码分页式度量方法. 计算机应用, 2018, 38(2): 305-309. [doi: 10.11772/j.issn.1001-9081.2017082167]
|
| [28] |
崔超远, 乌云, 李平, 等. 虚拟机自省中一种消除语义鸿沟的方法. 通信学报, 2015, 36(8): 31-37. [doi: cnki:sun:txxb.0.2015-08-005]
|
2021, Vol. 32


