Elastic Security Labs

WinVisor – 基于虚拟机管理程序的 Windows x64 用户模式可执行文件模拟器

基于虚拟机管理程序的概念验证型 Windows x64 二进制模拟器

阅读时间:25 分钟视角
WinVisor – 基于管理程序的模拟器,适用于 Windows x64 用户模式可执行文件

背景

在 Windows 10 (版本 RS4)中,Microsoft 引入了Windows Hypervisor Platform (WHP) API。该 API 向用户模式 Windows 应用程序公开了 Microsoft 内置的虚拟机管理程序功能。2024年,作者利用这个API创建了一个个人项目:一个名为DOSVisor的 16 位 MS-DOS 模拟器。正如发行说明中所提到的,我们一直计划进一步发展这一概念并用它来模拟 Windows 应用程序。Elastic 每年两次为员工提供研究周(ON Week)来开展个人项目,为开始从事该项目提供了绝佳的机会。该项目将被(毫无想象力地)命名为 WinVisor,灵感来自于其前身 DOSVisor。

虚拟机管理程序提供硬件级虚拟化,无需通过软件模拟 CPU。这可确保指令的执行与在物理 CPU 上完全一样,而基于软件的模拟器在边缘情况下通常表现不一致。

该项目旨在构建一个用于执行 Windows x64 二进制文件的虚拟环境,允许记录(或挂钩)系统调用并启用内存自省。该项目的目标不是构建一个全面而安全的沙箱——默认情况下,所有系统调用都将被简单记录并直接转发到主机。在其初始形式中,虚拟化客户机中运行的代码可以很容易地“逃逸”到主机。安全地保护沙箱是一项艰巨的任务,超出了本项目的范围。本文末尾将进一步详细描述这些局限性。

尽管已经推出 6 年了(在撰写本文时),但除了QEMUVirtualBox等复杂的代码库之外,WHP API 似乎尚未在许多公共项目中使用。另一个值得注意的项目是 Alex Ionescu 的Simpleator - 一个轻量级的 Windows 用户模式模拟器,它也利用了 WHP API。该项目与 WinVisor 有许多相同的目标,尽管实现的方法截然不同。WinVisor 项目旨在尽可能地实现自动化,并支持简单的可执行文件(例如ping.exe )普遍开箱即用。

本文将介绍该项目的总体设计、遇到的一些问题以及解决方法。由于开发时间的限制,某些功能将受到限制,但最终产品至少将是一个可用的概念验证。本文末尾将提供托管在 GitHub 上的源代码和二进制文件的链接。

虚拟机管理程序基础知识

虚拟机管理程序由 VT-x(英特尔)和 AMD-V(AMD)扩展提供支持。这些硬件辅助框架允许一个或多个虚拟机在单个物理 CPU 上运行,从而实现虚拟化。这些扩展使用不同的指令集,因此本质上彼此不兼容;必须为每个扩展编写单独的代码。

在内部,Hyper-V 使用hvix64.exe来支持 Intel,并使用hvax64.exe来支持 AMD。微软的 WHP API 抽象了这些硬件差异,允许应用程序创建和管理虚拟分区,而不管底层 CPU 类型如何。为简单起见,以下解释将仅关注 VT-x。

VT-x 添加了一组称为 VMX(虚拟机扩展)的附加指令,其中包含VMLAUNCH等指令(首次开始执行 VM)和VMRESUME指令(在 VM 退出后重新进入 VM)。当客户机触发某些条件时,会发生 VM 退出,例如特定指令、I/O 端口访问、页面错误和其他异常。

VMX 的核心是虚拟机控制结构 (VMCS),这是一个每个虚拟机的数据结构,用于存储客户机和主机上下文的状态以及有关执行环境的信息。VMCS 包含定义处理器状态、控制配置以及触发从客户机返回主机的转换的可选条件的字段。可以使用VMREADVMWRITE指令读取或写入 VMCS 字段。

在虚拟机退出期间,处理器将客户机状态保存在 VMCS 中并转换回主机状态以进行虚拟机管理程序干预。

WinVisor 概述

该项目利用了 WHP API 的高级特性。该 API 将虚拟机管理程序功能公开到用户模式,并允许应用程序将主机进程的虚拟内存直接映射到客户的物理内存中。

虚拟 CPU 几乎完全在 CPL3(用户模式)下运行,除了在 CPL0(内核模式)下运行以在执行前初始化 CPU 状态的小型引导加载程序。这将在虚拟 CPU 部分进一步详细描述。

为模拟客户环境构建内存空间涉及映射目标可执行文件和所有 DLL 依赖项,然后填充其他内部数据结构,例如进程环境块 (PEB)、线程环境块 (TEB)、 KUSER_SHARED_DATA等。

映射 EXE 和 DLL 依赖关系很简单,但准确维护内部结构(例如 PEB)是一项更复杂的任务。这些结构很大,大多数没有记录,并且它们的内容在不同的 Windows 版本中可能有所不同。填充一组最少的字段来执行一个简单的“Hello World”应用程序相对简单,但应该采取改进的方法来提供良好的兼容性。

WinVisor 无需手动构建虚拟环境,而是启动目标进程的暂停实例,并将整个地址空间克隆到客户机中。导入地址表 (IAT) 和线程本地存储 (TLS) 数据目录会暂时从内存中的 PE 头中删除,以停止 DLL 依赖项加载并防止在到达入口点之前执行 TLS 回调。然后恢复该进程,允许通常的进程初始化继续( LdrpInitializeProcess )直到到达目标可执行文件的入口点,此时虚拟机管理程序启动并取得控制权。这实际上意味着 Windows 已经为我们完成了所有艰苦的工作,并且我们现在拥有一个预先填充的用户模式地址空间,可供执行目标可执行文件使用。

然后创建一个处于挂起状态的新线程,其起始地址指向自定义加载函数的地址。该函数填充 IAT,执行 TLS 回调,最后执行目标应用程序的原始入口点。这实际上模拟了如果进程在本地执行时主线程会执行的操作。然后,该线程的上下文被“克隆”到虚拟 CPU 中,并在虚拟机管理程序的控制下开始执行。

内存根据需要分页到客户机中,并且系统调用被拦截、记录并转发到主机操作系统,直到虚拟化目标进程退出。

由于 WHP API 仅允许将当前进程的内存映射到客户机中,因此主虚拟机管理程序逻辑被封装在注入目标进程的 DLL 中。

虚拟 CPU

WHP API 为前面描述的 VMX 功能提供了一个“友好”的包装器,这意味着不再需要诸如在执行VMLAUNCH之前手动填充 VMCS 之类的常规步骤。它还向用户模式公开了该功能,这意味着不需要自定义驱动程序。但是,在执行目标代码之前,仍必须通过 WHP 适当地初始化虚拟 CPU。下面将描述其中的重要方面。

控制寄存器

只有CR0CR3CR4控制寄存器与该项目相关。CR0CR4用于启用 CPU 配置选项,例如保护模式、分页和 PAE。CR3包含PML4分页表的物理地址,这将在内存分页部分中进一步详细描述。

特定型号的寄存器

还必须初始化模型特定寄存器 (MSR) 以确保虚拟 CPU 正确运行。MSR_EFER包含扩展功能的标志,例如启用长模式(64 位)和SYSCALL指令。MSR_LSTAR包含系统调用处理程序的地址, MSR_STAR包含在系统调用期间转换到 CPL0(并转换回 CPL3)的段选择器。MSR_KERNEL_GS_BASE包含GS选择器的影子基地址。

全局描述符表

全局描述符表 (GDT) 定义段描述符,其主要描述在保护模式下使用的内存区域及其属性。

在长模式下,GDT 的用途有限,并且基本上是过去的遗物 - x64 始终在平面内存模式下运行,这意味着所有选择器都基于0 。唯一的例外是FSGS寄存器,它们用于特定于线程的用途。即使在这些情况下,它们的基地址也没有由 GDT 定义。相反,MSR(例如上面描述的MSR_KERNEL_GS_BASE )用于存储基地址。

尽管已经过时,GDT 仍然是 x64 模型的重要组成部分。例如,当前特权级别由CS (代码段)选择器定义。

任务状态段

在长模式下,任务状态段 (TSS) 仅用于从较低特权级别转换到较高特权级别时加载堆栈指针。由于该模拟器除了初始引导加载程序和中断处理程序外几乎完全在 CPL3 中运行,因此仅为 CPL0 堆栈分配了一个页面。TSS 作为特殊系统条目存储在 GDT 中,占用两个槽。

中断描述符表

中断描述符表 (IDT) 包含有关每种中断类型的信息,例如处理程序地址。这将在中断处理部分进一步详细描述。

引导加载程序

上面提到的大多数 CPU 字段都可以使用 WHP 包装函数进行初始化,但支持某些字段(例如XCR0 )仅在 WHP API 的更高版本(Windows 10 RS5)中出现。为了完整性,该项目包含一个小型的“引导加载程序”,它在启动时在 CPL0 上运行,并在执行目标代码之前手动初始化 CPU 的最后部分。与以 16 位实模式启动的物理 CPU 不同,虚拟 CPU 已经初始化为以长模式(64 位)运行,从而使启动过程更加简单。

引导加载程序执行以下步骤:

  1. 使用LGDT指令加载 GDT。该指令的源操作数指定一个 10 字节的内存块,其中包含先前填充的表的基地址和限制(大小)。

  2. 使用LIDT指令加载 IDT。该指令的源操作数使用与上面描述的LGDT相同的格式。

  3. 使用LTR指令将 TSS 选择器索引设置到任务寄存器中。如上所述,TSS 描述符作为 GDT 内的特殊条目存在(在本例中位于0x40 )。

  4. 可以使用XSETBV指令设置XCR0寄存器。这是一个附加的控制寄存器,用于 AVX 等可选功能。本机进程执行 XGETBV 来获取主机值,然后通过引导加载程序中的XSETBV将其复制到客户机中。

这是一个重要的步骤,因为已经加载的 DLL 依赖项可能在其初始化过程中设置了全局标志。例如, ucrtbase.dll在启动时通过CPUID指令检查 CPU 是否支持 AVX,如果支持,则设置一个全局标志以允许 CRT 使用 AVX 指令进行优化。如果虚拟 CPU 尝试执行这些 AVX 指令而没有先在XCR0中明确启用它们,则会引发未定义的指令异常。

  1. 手动将DSESGS数据段选择器更新为其 CPL3 等效项 ( 0x2B )。执行SWAPGS指令,从MSR_KERNEL_GS_BASE加载 TEB 基地址。

  2. 最后,使用SYSRET指令转换到CPL3。在SYSRET指令之前, RCX被设置为占位符地址(CPL3 入口点), R11被设置为初始 CPL3 RFLAGS 值( 0x202 )。SYSRET指令自动将CSSS段选择器从MSR_STAR切换为它们的 CPL3 等效项。

当执行SYSRET指令时,由于RIP中的占位符地址无效,将引发页面错误。模拟器将捕获此页面错误并将其识别为“特殊”地址。然后,初始 CPL3 寄存器值将被复制到虚拟 CPU 中, RIP将更新为指向自定义用户模式加载器函数,然后恢复执行。该函数加载目标可执行文件的所有 DLL 依赖项,填充 IAT 表,执行 TLS 回调,然后执行原始入口点。导入表和 TLS 回调在此阶段处理,而不是更早,以确保它们的代码在虚拟化环境中执行。

内存分页

所有客户机的内存管理都必须手动处理。这意味着必须填充并维护分页表,以允许虚拟 CPU 将虚拟地址转换为物理地址。

虚拟地址转换

对于那些不熟悉 x64 中的分页的人来说,分页表有四个级别: PML4PDPTPDPT 。对于任何给定的虚拟地址,CPU 都会遍历表的每一层,最终到达目标物理地址。现代 CPU 也支持 5 级分页(以防 4 级分页提供的 256TB 可寻址内存不够用!),但这与本项目的目的无关。

下图说明了示例虚拟地址的格式:

使用上面的例子,CPU 将通过下表条目计算虚拟地址0x7FFB7D030D10对应的物理页面: PML4[0xFF] -> PDPT[0x1ED] -> PD[0x1E8] -> PT[0x30] 。最后将偏移量( 0xD10 )添加到此物理页面以计算出准确的地址。

虚拟地址中的位48 - 63在 4 级分页中未使用,并且本质上经过符号扩展以匹配位47

CR3控制寄存器包含基PML4表的物理地址。当启用分页(长模式下强制)时,CPU 上下文中的所有其他地址都引用虚拟地址。

页面错误

当客户机尝试访问内存时,如果请求的页面尚未出现在分页表中,则虚拟 CPU 将引发页面错误异常。这将触发 VM Exit 事件并将控制权交还给主机。发生这种情况时, CR2控制寄存器包含请求的虚拟地址,尽管 WHP API 已在 VM 退出上下文数据中提供了该值。然后,主机可以将请求的页面映射到内存中(如果可能)并恢复执行,或者如果目标地址无效则抛出错误。

主机/客户机内存镜像

如前所述,模拟器会创建一个子进程,并且该进程内的所有虚拟内存将使用相同的地址布局直接映射到客户机中。Hypervisor Platform API 允许我们将主机用户模式进程的虚拟内存直接映射到客户机的物理内存中。然后,分页表会将虚拟地址映射到相应的物理页面。

无需预先映射进程的整个地址空间,而是为客户机分配固定数量的物理页面。该模拟器包含一个非常基本的内存管理器,并且页面“按需”映射。当发生页面错误时,所请求的页面将被调入并恢复执行。如果所有页面“槽位”都已满,则最旧的条目将被换出,为新条目腾出空间。

除了使用固定数量的当前映射页面之外,模拟器还使用固定大小的页表。页表的大小是通过计算映射页面条目数量的最大可能表数来确定的。该模型可以实现简单且一致的物理内存布局,但却是以效率为代价的。事实上,分页表比实际的页面条目占用更多的空间。

有一个单独的 PML4 表,在最坏的情况下,每个映射页面条目将引用唯一的 PDPT/PD/PT 表。由于每个表为4096字节,因此可以使用以下公式计算总页表大小:

PAGE_TABLE_SIZE = 4096 + (MAXIMUM_MAPPED_PAGES * 4096 * 3)

默认情况下,模拟器允许一次映射256页面(总共1024KB )。使用上面的公式,我们可以计算出这将需要分页表3076KB ,如下所示:

实际上,许多页表条目将被共享,并且分配给分页表的许多空间将保持未使用状态。但是,由于该模拟器即使使用少量页面也能正常运行,因此这种程度的开销并不是什么主要问题。

CPU 为分页表维护一个硬件级缓存,称为转换后备缓冲区 (TLB)。当将虚拟地址转换为物理地址时,CPU会首先检查TLB。如果在缓存中找不到匹配的条目(称为“TLB 未命中”),则将读取分页表。因此,每次重建分页表时刷新 TLB 缓存非常重要,以防止其不同步。刷新整个 TLB 的最简单方法是重置CR3寄存器值。

系统调用处理

当目标程序执行时,客户机中发生的任何系统调用都必须由主机处理。该模拟器可处理SYSCALL指令和传统(基于中断)系统调用。SYSENTER在长模式下不使用,因此不受 WinVisor 支持。

快速系统调用(SYSCALL)

当执行SYSCALL指令时,CPU 转换到 CPL0 并从MSR_LSTAR加载RIP 。在 Windows 内核中,这将指向KiSystemCall64SYSCALL指令本身不会触发 VM 退出事件,但模拟器将MSR_LSTAR设置为保留的占位符地址 - 在本例中为0xFFFF800000000000 。当执行SYSCALL指令时,如果将RIP设置为该地址,将引发页面错误,并且可以拦截该调用。这个占位符是Windows中的内核地址,不会与用户模式地址空间产生任何冲突。

与传统的系统调用不同, SYSCALL指令在转换到 CPL0 期间不会交换RSP值,因此可以直接从RSP检索用户模式堆栈指针。

旧式系统调用(INT 2E)

SYSCALL指令相比,传统的基于中断的系统调用速度较慢且开销较大,但尽管如此,Windows 仍然支持它们。由于模拟器已经包含处理中断的框架,因此添加对旧式系统调用的支持非常简单。当捕获到传统的系统调用中断时,经过一些小的转换后,它可以被转发到“公共”系统调用处理程序——具体来说,就是从 CPL0 堆栈中检索存储的用户模式RSP值。

系统调用转发

在模拟器创建其上下文被克隆到虚拟 CPU 中的“主线程”之后,该本机线程将被重用为代理,将系统调用转发到主机。重复使用相同的线程可以保持客户机和主机之间的 TEB 和任何内核状态的一致性。特别是 Win32k,依赖于许多线程特定状态,这些状态应该在模拟器中反映出来。

当发生系统调用时(无论是通过SYSCALL指令还是传统中断),模拟器都会拦截它并将其传输到通用处理程序函数。系统调用号存储在RAX寄存器中,前四个参数值分别存储在R10RDXR8R9中。R10用于第一个参数,而不是通常的RCX寄存器,因为SYSCALL指令用返回地址覆盖了RCX 。Windows 中的旧式系统调用处理程序 ( KiSystemService ) 也使用R10来实现兼容性,因此不需要在模拟器中进行不同的处理。其余参数从堆栈中检索。

我们不知道任何给定的系统调用号所需的确切参数数量,但幸运的是,这并不重要。我们可以简单地使用固定数量,只要提供的参数数量大于或等于实际数量,系统调用就会正常运行。将动态创建一个简单的程序集存根,填充所有参数,执行目标系统调用,并干净地返回。

测试表明,Windows 系统调用当前使用的最大参数数量为17 ( NtAccessCheckByTypeResultListAndAuditAlarmByHandleNtCreateTokenExNtUserCreateWindowEx )。WinVisor 使用32作为参数的最大数量,以允许未来潜在的扩展。

在主机上执行系统调用后,返回值被复制到客户机中的RAX 。然后将RIP转换为SYSRET指令(或对于旧式系统调用为IRETQ ),然后恢复虚拟 CPU,实现无缝转换回用户模式。

系统调用日志记录

默认情况下,模拟器只是将客户系统调用转发到主机并将其记录到控制台。但是,需要一些额外的步骤来将原始系统调用转换为可读格式。

第一步是将系统调用号转换为名称。系统调用编号由多个部分组成:位12 - 13包含系统服务表索引( 0ntoskrnl1win32k ),位0 - 11包含表内的系统调用索引。该信息使我们能够在相应的用户模式模块( ntdll / win32u )中执行反向查找,以解析原始系统调用名称。

下一步是确定每个系统调用要显示的参数值的数量。如上所述,模拟器将32参数值传递给每个系统调用,即使其中大多数都没有使用。但是,出于可读性原因,记录每个系统调用的所有32值并不理想。例如,简单的NtClose(0x100)调用将被打印为NtClose(0x100, xxx, xxx, xxx, xxx, xxx, xxx, xxx, xxx, ...) 。如前所述,没有简单的方法可以自动确定每个系统调用的确切参数数量,但我们可以使用一个技巧来高精度地估计它。

此技巧依赖于 WoW64 使用的 32 位系统库。这些库使用 stdcall 调用约定,这意味着调用者将所有参数推送到堆栈上,并在返回之前由被调用者在内部清理它们。相比之下,本机 x64 代码将第一个 4 参数放入寄存器中,并且调用者负责管理堆栈。

例如,WoW64 版本的ntdll.dll中的NtClose函数以RET 4指令结尾。这会在返回地址后从堆栈中弹出另外 4 个字节,这意味着该函数接受一个参数。如果函数使用RET 8 ,则表明它采用 2 参数,依此类推。

即使模拟器作为 64 位进程运行,我们仍然可以将ntdll.dllwin32u.dll的 32 位副本加载到内存中 - 手动加载或使用SEC_IMAGE映射。必须编写GetProcAddress的自定义版本来解析 WoW64 导出地址,但这是一项简单的任务。从这里,我们可以自动找到每个系统调用相应的 WoW64 导出,扫描RET指令来计算参数的数量,并将该值存储在查找表中。

这种方法并不完美,并且有多种可能导致失败的情况:

  • WoW64 中不存在少数本机系统调用,例如NtUserSetWindowLongPtr
  • 如果 32 位函数包含 64 位参数,则它将在内部拆分为 2x 32 位参数,而相应的 64 位函数只需要一个相同值的参数。
  • Windows 中的 WoW64 系统调用存根函数可能会发生改变,从而导致现有的RET指令搜索失败。

尽管存在这些缺陷,但对于绝大多数系统调用来说,结果仍然是准确的,而无需依赖硬编码值。此外,这些值仅用于记录目的,不会影响其他任何内容,因此在这种情况下,轻微的误差是可以接受的。如果检测到故障,它将恢复显示最大数量的参数值。

系统调用挂钩

如果该项目用于沙盒目的,那么出于显而易见的原因,盲目地将所有系统调用转发到主机是不可取的。该模拟器包含一个框架,允许在必要时轻松挂接特定的系统调用。

默认情况下,仅挂钩NtTerminateThreadNtTerminateProcess来捕获客户进程退出。

中断处理

中断由 IDT 定义,它在虚拟 CPU 执行开始之前填充。当发生中断时,当前 CPU 状态被推送到 CPL0 堆栈( SSRSPRFLAGSCSRIP ),并且RIP被设置为目标处理程序函数。

与 SYSCALL 处理程序的MSR_LSTAR一样,模拟器使用占位符值 ( 0xFFFFA00000000000 - 0xFFFFA000000000FF ) 填充所有中断处理程序地址。当发生中断时,这个范围内就会发生页面错误,我们可以捕获它。中断索引可以从目标地址的最低8位提取(例如, 0xFFFFA00000000003INT 3 ),并且主机可以根据需要进行处理。

目前,模拟器仅处理INT 1 (单步)、 INT 3 (断点) 和INT 2E (旧式系统调用)。如果捕获到任何其他中断,模拟器将因错误退出。

处理完中断后, RIP将被转移到IRETQ指令,该指令将干净地返回到用户模式。某些类型的中断会将额外的“错误代码”值推送到堆栈上 - 如果是这种情况,则必须在IRETQ指令之前弹出它以避免堆栈损坏。该模拟器内的中断处理程序框架包含一个可选标志,可以透明地处理此问题。

虚拟机管理程序共享页面错误

Windows 10 引入了一种新型共享页面,其位置靠近KUSER_SHARED_DATA 。该页面由与时间相关的功能(例如RtlQueryPerformanceCounterRtlGetMultiTimePrecise使用。

可以使用NtQuerySystemInformation SystemHypervisorSharedPageInformation信息类来检索此页面的确切地址。LdrpInitializeProcess函数在进程启动期间将该页面的地址存储在全局变量 ( RtlpHypervisorSharedUserVa ) 中。

WHP API 似乎包含一个错误,如果将此共享页面映射到客户机中并且虚拟 CPU 尝试从中读取,则会导致WHvRunVirtualProcessor函数陷入无限循环。

时间限制限制了全面调查此事的能力;不过,我们实施了一个简单的解决方法。模拟器修补目标进程中的NtQuerySystemInformation函数并强制其对SystemHypervisorSharedPageInformation请求返回STATUS_INVALID_INFO_CLASS 。这会导致ntdll代码回退到传统方法。

演示

以下是在该虚拟化环境下模拟的一些常见 Windows 可执行文件的示例:

限制

该模拟器有几个限制,使其在当前形式下用作安全沙箱并不安全。

安全问题

有几种方法可以“逃离”虚拟机,比如简单地创建一个新的进程/线程、调度异步过程调用(APC)等等。

Windows GUI 相关的系统调用还可以从内核直接嵌套调用到用户模式,这目前可以绕过虚拟机管理程序层。因此,在 WinVisor 下运行时,notepad.exe 等 GUI 可执行文件仅部分虚拟化。

为了演示这一点,WinVisor 在模拟器中添加了一个-nx命令行开关。这会在启动虚拟 CPU 之前强制将整个目标 EXE 映像标记为内存中不可执行,如果主机进程尝试以本机方式执行任何代码,就会导致进程崩溃。但是,这仍然是不安全的——目标应用程序可以使该区域再次可执行,或者只是在其他地方分配可执行内存。

由于 WinVisor DLL 被注入到目标进程中,它与目标可执行文件存在于相同的虚拟地址空间内。这意味着在虚拟 CPU 下运行的代码能够直接访问主机管理程序模块内的内存,这可能会损坏它。

不可执行客户内存

虽然虚拟 CPU 设置为支持 NX,但所有内存区域当前都镜像到具有完全 RWX 访问权限的客户机中。

仅限单线程

该模拟器目前仅支持虚拟化单个线程。如果目标可执行文件创建了额外的线程,那么这些线程将在本地执行。为了支持多线程,可以开发一个伪调度程序来处理这个问题。

禁用 Windows 并行加载器以确保所有模块依赖项都由单个线程加载。

软件异常

目前不支持虚拟化软件异常。如果发生异常,系统将照常本地调用KiUserExceptionDispatcher函数。

结论

如上所示,模拟器在当前形式下对各种可执行文件均表现良好。虽然它目前可以有效地记录系统调用和中断,但还需要进行大量进一步的工作才能使其安全地用于恶意软件分析目的。尽管如此,该项目为未来的发展提供了有效的框架。

项目链接

https://github.com/x86matthew/WinVisor

可以在 X 上的@x86matthew找到作者。