Space Traveling across VM: Automatically Bridging the Semantic Gap in Virtual Machine Introspection via Online Kernel Data Redirection
今天分享的文章来自Yangchun Fu和Zhiqiang Lin在UTDallas时期的工作。这是一篇较早期(Oakland 12)解决VMI的语义问题的文章。
Intro
VMI将客户操作系统的状态提取到VMM中,提供了额外的隔离层并开启了安全性、可靠性和管理的新机会。然而,在执行自省时,经常需要在外部VMM层解释客户硬件层的状态,如处理器、物理内存和设备。这种解释通常需要对内部操作系统内核的工作有详细、最新的了解。例如,要检查Linux内核中运行进程的pid,就需要遍历对应的task_struct来获取其pid字段。获取这些信息通常是费时且乏味的工作,对于闭源操作系统,可能还需要逆向工程内核工作。
解释低级的比特和字节为高级的语义状态的难度被称为语义鸿沟。
早期解决方法依赖于Linux crash,但这需要内核被重新编译以加入调试符号。另一种方法涉及定位、遍历和解释已知的客户内存结构。尽管后一种方法已被广泛采用,它依赖于手动定位内核数据和开发相应的自省代码。此外,这种手动过程需要针对不同内核重复进行,而内核可能会因新版本或补丁而频繁变更,也可能为攻击者逃避这些自制工具提供机会。为减轻手动过程的负担并开发更安全的VMI工具。Virtuoso系统,用于自动生成最少人力介入的自省程序。Virtuoso的核心思想是从客户可信程序的追踪中创建自省程序。具体来说,给定一个自省功能(例如,列出所有进程),Virtuoso将训练并追踪客户程序的系统范围执行,自动识别完成此功能所需的指令,最终生成复现客户程序行为的自省代码。虽然Virtuoso在缩小语义鸿沟方面已经取得了重大进展,但作者也承认它的一个基本局限性是它只能复现已经执行和训练过的自省代码,并且仍然需要人类专家的干预。因此,在本文中提出了VM-Space Traveler(简称VMST),自动桥接语义鸿沟并生成VMI工具。
关键观点与技术: 核心在于,程序P(x)通常由代码P和数据x组成;对于同一个程序,P在不同机器上通常是相同的,唯一的区别在于运行时消耗的数据x。这一方法是让机器A中的程序P透明地消费机器B中的数据y,这样就能自动生成自省程序P'(x)=P(y)。但这一过程面临挑战,因为内核数据不易重定向,且可能引发内核崩溃或控制流中断。本文开发了一些操作系统无关的技术来实现数据重定向。
使用场景与新特性: VMST考虑到对客户操作系统的透明性,支持多个最新发布的Linux内核而无需修改。VMST允许用户使用常见的操作系统工具(如ps, lsmod)检查客户操作系统的状态。此外,VMST还能自动生成安全的自省工具,这些工具比手动创建的工具更为安全和可靠。VMST还允许用户级程序员和内核级程序员开发新的程序和设备驱动来监视系统状态。
实验结果: 在多种Linux发行版上实施了VMST,并测试了20种不同的内核版本来评估方法的通用性。实验结果表明,自动化生成VMI工具的方法是可行的,能直接支持客户操作系统中的多种检查工具,允许最终用户通过重用原生API或设备驱动开发新的VMI工具,并引入了合理的性能开销(平均增加了9.3倍)。
Design
Overall
VMST系统在图2中展示。对于运行在产品VM中的不受信任操作系统,如果最终用户想要进行自省,他们只需在提供的安全VM上安装相应的受信操作系统版本,并调用常用的标准工具程序,无需任何修改。无需手动理解或逆向工程操作系统内核,也无需编写自省程序。如果他们想自定义自省程序,可以不用担心操作系统内核的内部细节,直接使用原生API/系统调用进行开发。值得注意的是,图2中的产品VM和安全VM可以完全不同,VMST仅与安全VM绑定,并对客户产品VM透明,这些产品VM可以运行在XEN/KVM/VMWare/VirtualBox/VirtualPC/QEMU上,甚至是物理机器,只要可以访问其内存。
关键技术: VMST包含三项关键技术:
-
(1)系统调用执行上下文识别,
-
(2)可重定向数据识别,
-
(3)内核数据重定向。
系统调用执行上下文识别用于仅识别与自省相关的系统调用执行上下文,并确保内核数据重定向只重定向感兴趣上下文中的数据x。可重定向数据识别准确指出在系统调用执行上下文识别提供的上下文中需要重定向的数据x及其闭包。内核数据重定向则执行x的最终重定向。如果x上有任何数据写入操作,将执行写时复制(COW)。
SYSCALL CONTEXT
由于内核控制流程的复杂性,首先需要确定精确的系统调用执行上下文,在该上下文中执行必要的系统调用重定向。当自省程序运行时,存在用户空间和内核空间两个区域。在x86架构中,每个进程(包括本质上是进程的内核线程)都有一个独特的CR3值(用于定位页目录),因此可以通过检查CR3值来隔离相应的内核和用户空间。
获取被监控进程正确的CR3值是一个挑战,因为VM需要对客户操作系统保持透明,应避免遍历任何特定的task_struct来获取进程名称字段。尽管如此,还是可以在VMM层执行一些操作。识别系统调用入口点相对简单。在Linux中,用户级程序通过执行int 0x80或sysenter指令进入内核空间。因此,通过在VMM层截获这两个指令,就足以标识系统调用执行上下文的开始。然而,真正的难题在于识别系统调用的退出点。一种简单的方法可能直接截获sysexit或iret指令来确定退出点。然而,由于中断和异常处理以及系统调用执行期间可能发生的上下文切换,这种方法并不可行。
如图3所示,在处理系统调用时,可能发生中断,控制流可能转移到中断处理程序。例如,当系统调用例程访问进程的某个未映射的内存区域时,也可能发生页面错误等异常。此外,在系统调用退出点或在服务系统调用期间,也可能发生上下文切换(例如,进程使用超过其时间片)。中断和异常处理程序中也可能发生上下文切换。
幸运的是,由于VM虚拟化了所有硬件资源(例如通过仿真),在VMM层可以轻松观察和控制这些硬件状态,包括中断和计时器,只要能保持自己的自省过程和内核正确运行。
REDIRECTABLE DATA
这一方法旨在识别可以安全转移到客户内存中的可重定向内核数据x。通常,自省程序会手动从特定的全局内存位置(如system.map文件中导出的位置)遍历内核内存,并通过指针跟踪到其他位置,包括内核堆。一个直观的方法是跟踪和重定向从内核全局变量及通过指针解引用和算术操作得到的数据。在指令级别可以轻易识别这些全局变量,因为它们在编译后在相同的操作系统版本中保持不变。
作者开发了一个比污点分析更简单的方法,该方法可以节省用于数据流跟踪的影子内存空间。不是跟踪所有可重定向的数据,而是识别不可重定向的数据,特别是那些从堆栈变量解引用或派生的数据。这些堆栈变量至关重要,因为它们管理着内核控制路径,并且即使在相同的操作系统上,在不同时间也会有所不同。
KERNEL DATA REDIRECTION
System Call Redirection Policy
系统调用重定向策略需要非常细致,即需要根据每个系统调用的语义决定是否在执行期间重定向数据访问。
系统调用通常可以分为几类:文件访问(如 open、read、write)、网络访问(如 send/recv)、消息队列(如 msgctl)、共享内存(如 shmat)、文件描述符操作(如 dup、fcntl)、时间相关(如 getitimer/setitimer)、进程控制相关(如 execve、brk、getpid)以及其他系统级功能(如账户和配额管理 acct)。
自省中特别关注与检索系统状态(即获取 get 功能)和文件访问相关的系统调用。具体而言,因为 Linux/UNIX 中的 proc 文件访问类系统调用特别感兴趣。proc 文件系统是一种特殊的文件系统,为动态访问内核状态提供了更标准化的方式。实际上,像 ps、lsmod、netstat 这样的重要工具程序都会读取 proc 文件以检查内核状态。对于磁盘文件,由于虚拟机内省(VMI)主要处理内存,不涉及重定向,需要通过跟踪文件描述符来区分它们。为此,每当自省过程打开文件时,都会维护一个文件描述符映射,并通过检查参数来区分打开的文件是否为 proc 文件。
值得注意的是,VMST 中几乎所有关键技术都是操作系统无关的(这是设计目标)。然而,系统调用重定向策略则需要特定操作系统的系统调用转换和语义知识。因此,为了支持其他系统如 Microsoft Windows,需要仔细检查每个 Windows 系统调用以确定它们是否可重定向。一旦拥有这些知识,就很容易进行自省了。例如,作为概念验证,通过启用 VMST 重定向系统调用 NtQueryInformationProcess(系统调用编号 0x9a)并禁用堆栈重定向的方式,同时使用替代方法跟踪自省过程的新 CR3 值,成功自省了一个 Windows-XP (SP2) 进程 ID。
Virtual to Physical Address Translation
当动态地检测每个内核指令时,只能观察到虚拟地址。如果某个给定地址是可重定向的,必须识别其在客户机内的物理地址。即必须执行MMU级别的虚拟地址到物理地址(V2P)转换。
为此,在VM中设计了一个影子TLB(STLB)和影子CR3(SCR3),在地址转换过程中使用,如果需要重定向某个地址α。SCR3在自省时刻用客户CR3值初始化,仅当α需要重定向时用于内核内存地址,STLB也是如此。
同时,如先前在第II部分讨论的,如果重定向数据上发生数据写入,必须在页面级执行写时复制(COW)以避免对客户操作系统产生任何副作用。这一设计是扩展页面表项中的一个保留位以指示页面是否已被复制(变脏),并在软件STLB项中也添加一个位。这是在VMM中进行检测的优势之一,因为可以在如STLB这样的模拟软件中添加任何想要的东西。此外,对于页面表项,只需扩展一个保留位就能达到目的。当然,如果不存在任何保留位,也可以制作一个影子页面表,并为页面项扩展一个脏位。
更具体地说,在自省过程开始前,STLB初始化为零。当内核地址α需要重定向且为读取(即内存加载)操作时,首先检查STLB是否未命中,如果未未命中,直接从STLB得到物理地址PA(α)。否则,通过查询SCR3并执行三层地址转换来获取在客户机物理内存中的PA(α)。同时,为地址α在STLB中填充PA(α)的物理地址,以便将来引用与α在同一页面的地址可以快速定位(TLB的基本思想)。此外,仅当STLB的条目满了且必须替换时,STLB条目才会被刷新,因为只有一个SCR3值。
如果在α上有内存写操作,类似于读操作检查STLB是否命中,还通过查询STLB项中的脏位检查目标页面是否已变脏。如果未变脏或STLB未命中,通过查询SCR3和页面表执行三层地址转换,并进一步检查目标页面是否已变脏。如果未变脏(这个页面的第一次数据写入),将设置目标页面表项和STLB项的脏位,并执行目标页面的复制,并将原始页面的未来访问重定向到新页面。否则,只需在STLB项中设置脏位。
Directing the Access
最终数据重定向过程的详细描述:具体来说,对于每条内核指令i,将检查其执行是否处于系统调用执行上下文中。如果是,将检查当前系统调用上下文中的数据访问是否需要重定向。如果不需要,则不对i进行任何检测。
接下来,对i执行可重定向数据跟踪,根据指令语义更新影子状态。之后,对于i中使用的每个内存地址,如果是数据读取,将调用虚拟地址到物理地址(V2P)转换函数来获取相应的地址,并加载数据。否则,将检查目标页面是否已变脏。如果页面未变脏,将执行写时复制(COW)操作,并更新页面条目的脏位,必要时复制页面。之后,获取其物理地址并进行写操作。
还可以看到,数据重定向引擎可以在任何其他内核执行上下文中工作。例如,可以检查并重定向特定内核函数中的内核数据访问,如常规内核模块例程或用户开发的设备驱动程序例程。这实际上是VMST的另一个好处。即它允许最终用户习惯性地检查特定的内核代码块。
LIMITATIONS
尽管VMST已经自动地在虚拟机自省(VMI)中桥接了语义鸿沟,但其当前实现还存在一些局限性。
首先,VMST并不完全透明于任意操作系统内核。它仍然依赖于某些特定的操作系统内核知识,如系统调用接口、中断处理和上下文切换,尽管这些知识是通用的。例如,实验显示,VMST可以直接支持多种Linux内核而无需任何修改。然而,完全不同的操作系统可能具有不同的系统调用接口和语义。显然,不能直接在任意操作系统上运行VMST。换言之,对于非UNIX的其他操作系统,必须检查其内核如何处理系统调用、中断、上下文切换及其特定的系统调用语义等。例如,通过对VMST中的“系统调用重定向策略”稍作修改,可以直接启用Windows中的getpid进程来直接自省客户机Windows操作系统,这在实验中证明中断处理、上下文切换控制、可重定向数据识别和内核数据重定向确实是操作系统无关的。
其次,尽管UNIX平台上的大多数系统调用都是同步的,即在系统调用执行期间它会阻塞其他执行(除了上下文切换)直到系统调用完成,但可能有一些系统调用是异步的(“非阻塞”)。除非能够检测到内核通知调用者系统调用已完成的确切执行上下文,否则VMST不支持异步系统调用。幸运的是,VMI工具生成中还没有遇到这样的异步系统调用。
第三,目前仅支持自省工具检查内存数据。如果VMI工具需要打开客户机磁盘文件,它当前的实现中不会被重定向,最终用户可以直接复制这些文件并直接检查它们。由于Linux/UNIX中的文件打开是通过VFS(虚拟文件系统)进行的,已经拦截了打开proc文件时的open系统调用。能够确定磁盘文件的打开。唯一的问题是必须在操作系统内核中定位相应的实际磁盘加载数据。
第四,VMST无法读取已被换出到磁盘的客户机数据。在实验中没有遇到这种情况,解释是内核倾向于交换用户空间内存而非内核,因为内核空间是共享的,而换入和换出内核页面可能需要更新所有进程的页面表。同时,对于Linux/UNIX内核,已经确认内核页面从未被换出过。
原文始发于微信公众号(COMPASS Lab):[论文分享]Space Traveling across VM