玩转(Windows)沙箱

  • 玩转(Windows)沙箱已关闭评论
  • 13 views
  • A+

备注

原文地址:Playing in the (Windows) Sandbox - Check Point Research

原文信息:March 11, 2021Research By: Alex Ilgayev

原文标题:Playing in the (Windows) Sandbox


简介

两年前,微软发布了一项新功能,作为Insiders build 18305的一部分--Windows沙箱。

这个沙箱有一些有用的规范:

  • 是Windows 10(专业版/企业版)的集成部分。
  • 在Hyper-V虚拟化的基础上运行。
  • 原始的和一次性的--每次运行都是干净的,没有持久的状态。
  • 可通过一个具有专用格式(WSB格式)的配置文件进行配置。你可以配置网络、vGPU、映射的文件夹、在用户登录时运行的自动脚本,以及许多其他选项。
  • 该部署是基于Windows容器技术。

从附带的技术博客来看,我们可以说微软实现了一个重大的技术里程碑。由此产生的沙箱呈现了两个世界的最佳:一方面,沙箱基于Hyper-V技术,这意味着它继承了Hyper-V严格的虚拟化安全性。另一方面,沙箱包含几个特性,允许与主机共享资源,以减少CPU和内存消耗。

其中一个有趣的特性尤为重要,我们将在这里详细阐述。

动态生成的图像

访客磁盘和文件系统是动态创建的,并且是使用主机文件系统中的文件实现的。

出于几个原因,我们决定更深入地研究这项技术。

  • 缺乏关于其内部技术性的文件,包括官方和社区的。虽然它结合了两种被广泛记录的技术(Windows Containers和Hyper-V),但我们仍然缺少关于它是如何一起工作的。例如,技术博客提到了Windows容器技术,但在官方文档中,Windows容器的创建和管理是通过Windows的Docker工具完成的,而Windows Sandbox中并没有使用。
  • 不幸的是,除了调整WSB文件外,微软不允许对沙箱进行任何定制。这意味着我们不能安装任何需要重启的程序,也不能为沙箱创建我们自己的基础镜像。

在这篇文章中,我们分解了几个组件、执行流程、驱动支持以及动态图像功能的实施设计。我们展示了几个内部技术,如NTFS自定义reparse标签、VHDx分层、用于适当隔离的容器配置、虚拟存储驱动器、通过VMBus的vSMB等等。我们还创建了一个用于恶意软件分析的定制FLARE VM沙箱,其启动时间仅为10秒。

常规组件

对Hyper-V及其模块的复杂生态系统已经进行了广泛的研究。发现了一些漏洞,比如接下来的VmSwitch RCE,它可以导致完全的从客人到主机的逃脱。几年前,微软推出了Windows Containers(主要用于服务器),这一功能允许在Windows上原生运行Docker,以简化软件部署。

这两项技术也以两个组件的形式被引入到Windows 10终端平台。WDAG(Windows Defender Application Guard),以及最近的Windows Sandbox。最近,WDAG和另一个令人兴奋的Office隔离功能被合并为MDAG--微软防御者应用卫士。

在POC2018会议上,张云海有一个演讲,他深入研究了WDAG架构和内部。正如我们所展示的那样,Windows沙箱在其底层实现上共享相同的技术。

沙箱可以分为三个部分:两个服务--CmService.dll和vmcompute.exe--以及创建的工作进程,vmwp.exe。

准备好沙箱

每个基于Hyper-V的虚拟机背后都有一个VHDx文件,这是一个虚拟磁盘,被机器使用。为了了解磁盘是如何创建的,我们查看了一个正在运行的沙箱的工作文件夹:%PROGRAMDATA%MicrosoftWindowsContainers。令人惊讶的是,我们发现了8个以上的VHDx文件。

我们可以通过其动态大小跟踪主VHDx文件,在下一个路径--Sandboxes29af2772-55f9-4540-970f-9a7a9a6387e4sandbox.vhdx,其中GUID是在每个沙箱运行中随机生成的。

当我们手动挂载VHDx文件时,我们看到它的大部分文件系统都不见了(这种现象在Zhang的WDAG研究中也可见,前面提到过)

我们可以立即观察到文件夹图标上的 "X "符号。如果我们打开文件资源管理器中的 "属性 "栏,我们可以看到两个不寻常的NTFS属性。这里对这两个属性进行了解释:

O - 脱机

L--重复点

Reparse Point是NTFS的一个扩展,允许它创建一个 "链接 "到另一个路径。它还在其他功能中起作用,如卷挂载。在我们的案例中,使用这一功能是有道理的,因为大多数文件并不 "实际 "存在于VHDx文件中。

为了了解reparse指向哪里以及那里有什么,我们深入研究NTFS结构。
准备沙箱

分析MFT记录

主文件表(MFT)存储了从NTFS分区检索文件所需的信息。一个文件可能有一条或多条MFT记录,并且可以包含一个或多个属性。我们可以用mftparser选项运行流行的取证工具Volatility,来解析底层文件系统中的所有MFT记录。这可以通过以下命令行来完成:

volatility.exe -f sandbox.vhdx mftparser --output=body -D output --output-file=sandbox.body

当我们在输出中搜索kernel32.dll(一个样本系统文件)记录时,我们遇到了以下文字:

0|[MFT FILE_NAME] WindowsSystem32kernel32.dll (Offset: 0x3538c00)|1251|---a---S--o----|0|0|764456|1604310972|1596874670|1603021550|1596874670
0|[MFT STD_INFO] WindowsSystem32kernel32.dll (Offset: 0x3538c00)|1251|---a---Sr-o----|0|0|764456|1606900209|1596874670|1603021550|1596874670

我们可以看到类似的reparse("S")和offline("o")属性,就像我们之前做的那样,但是Volatility并没有给我们任何额外的信息。我们可以使用MFT记录的偏移量,0x3538c00,来启动我们自己的手动解析。

我们在解析过程中使用了接下来的NTFS文档。我们没有提供MFT格式的完整规范,但简单地说,MFT记录包含数量不等的属性,每个属性都有自己的头和一个有效载荷。我们正在寻找$REPARSE_POINT属性,它由序号0xC0标识

我们对上述结构的分析工作产生了以下数据:

$REPARSE_POINT Attribute
--------------- Attribute Header ---------------
C0 00 00 00 - Type ($REPARSE_POINT)
78 00 00 00 - Length
00 - Non-resident flag
00 - Name length
00 00 - Offset to the name
00 00 - Flags
03 00 - Attribute Id (a)
5C 00 00 00 - Length of the attribute
18 00 - Offset to the attribute
00 - Indexed flag
00 - Padding
---------------- Attribute Data ----------------
18 10 00 90 - Reparse tag
54 00 - Reparse data length
00 00 - Padding
----------------- Reparse Data -----------------
01 00 00 00 - Version ?
00 00 00 00 - Reserved ?
77 F6 64 82 B0 40 A5 4C BF 9A 94 4A C2 DA 80 87 - Referenced GUID
3A 00 - Path string size
57 00 69 00 6E 00 64 00 6F 00 77 00 73 00 5C 00
53 00 79 00 73 00 74 00 65 00 6D 00 33 00 32 00
5C 00 6B 00 65 00 72 00 6E 00 65 00 6C 00 33 00
32 00 2E 00 64 00 6C 00 6C 00 - Path string

几个重要的注意事项:

  • 我们没有找到任何关于微软重新解析数据结构的公开文档,但是逆向工程并不太难。
  • 重解析标记0x90001018在这里被定义为IO_REPARSE_TAG_WCI_1,下面是描述:

"由Windows容器隔离筛选器使用。只是服务器端解释,在网络上没有意义。”
* 在本研究中对Windows模块进行逆向工程时,我们多次遇到引用的GUID 77 F6 64 82 B0 40 A5 4C BF 9A 94 4A C2 DA 80 87作为一个硬编码值。这个值表示对主机基础层的引用,我们后面会讲到它。
* Reparse数据中的路径显示了我们的样本文件的相对路径。WindowsSystem32kernel32.dll

根据上述信息,我们可以得出结论,文件是由底层文件系统 "链接 "的(可能是指定的FS过滤器),但许多问题仍然没有答案:VHDx是如何构建的,其他VHDx的目的是什么,以及什么组件负责链接到主机文件。

VHDx分层法

如果我们跟踪Procmon在沙箱创建期间的日志,我们注意到一系列的VHDx访问尝试:

虽然第一个是我们之前解析过的 "真正的 "VHDx,但它后面还有其他3个VHDx访问。我们怀疑微软对虚拟磁盘模板使用了某种分层方式。

通过使用二进制编辑器检查VHDx文件,我们的理论很容易得到验证:

VHDx格式的父定位符可以使用多种方法给出:绝对路径、相对路径和卷路径。文档可以在这里找到。

有了这些知识,我们就可以构建下一层:

  • Sandboxes<new_sandbox_guid>sandbox.vhdx – The “real” VHDx.
  • Sandboxes<constant_guid_per_installation>sandbox.vhdx – Created once per sandbox install.
  • BaseImages949cec7-8165-4167-8c7d-67cf14eeede0SnapshotSnapshotSandbox.vhdx – Probably relevant to the base layer snapshot.
  • PortableBaseLayerSystemTemplateBase.vhdx – Base template.

当我们浏览这些虚拟磁盘时,我们注意到仍然缺少文件; 一些系统文件夹是空的,用户/程序文件和各种其他文件的文件夹也是空的。

使用 Procmon 可以让我们理解另一个重要的层是缺失的: 操作系统基本层。

操作系统底层

操作系统的基础层主文件存在于沙箱工作文件夹下的路径中。BaseImages949cec7-8165-4167-8c7d-67cf14eeede0BaseLayer.vhdx. 通过Procmon查看安装过程,我们可以看到下一个.wim(Windows Imaging Format)文件,C:WindowsContainersservicedWindowsDefenderApplicationGuard.wim,被提取到同名的PortableBaseLayer文件夹,并被复制和重命名到上面的基础层文件。这显示了WDAG和Windows Sandbox之间的又一个相似之处。

当我们浏览BaseLayer.vhdx磁盘时,我们可以看到创建的沙箱的完整结构,但系统文件仍然 "物理 "上丢失。像我们之前做的那样解析kernel32.dll的MFT记录,结果是相同的$REPARSE_POINT属性,但有不同的标签。0xA0001027: IO_REPARSE_TAG_WCI_LINK_1. 请记住这个标签,以便以后使用

此外,当我们运行mountvol命令时,我们看到基础层VHDx被挂载到它存在的同一目录:

负责挂载该卷的服务,以及我们在这之前提到的所有功能,是容器管理器服务CmService.dll。

这个服务运行一个名为cmimageworker.exe的可执行文件,用接下来的一个命令行参数,expandpbl/deploy/clean,来执行这些操作。

我们可以观察到cmimageworker.exe中对computestorage!HcsSetupBaseOSLayer的调用,以及computestorage.dll中基础层的部分实际创建

Microsoft发布了有关沙箱的以下声明:

Windows的一部分 - 该功能所需的一切都随Windows 10专业版和企业版一起提供。不需要下载一个VHD!
到目前为止,我们了解了关于该功能的关键实施细节。让我们继续看看容器是如何执行的。

运行沙箱

运行Windows沙箱应用程序会触发一个执行流程,我们在此不作详述。我们只提到,该流程导致CmService通过RPC调用执行vmcompute!HcsRpc_CreateSystem。另一个关键服务,vmcompute.exe,运行并协调主机上的所有计算系统(容器)。

在我们的案例中,CreateSystem命令还收到了下一个配置JSON,它描述了所需的机器。

注意--为了便于阅读,JSON被剪掉了。您可以在附录A中访问完整的JSON。

{
"Owner": "Madrid",
...
"VirtualMachine": {
...
"Devices": {
"Scsi": {
"primary": {
"Attachments": {
"0": {
"Type": "VirtualDisk",
"Path": "C:\ProgramData\Microsoft\Windows\Containers\Sandboxes\025b00c8-849a-4e00-bcb2-c2b8ec698bab\sandbox.vhdx",
...
}
}
}
},
...
"VirtualSmb": {
"Shares": [{
"Name": "os",
"Path": "C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer\Files",
...
}],
` },
...
},
...
"RunInSilo": {
"SiloBaseOsPath": "C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer\Files",
"NotifySiloJobCreated": true,
"FileSystemLayers": [{
"Id": "8264f677-40b0-4ca5-bf9a-944ac2da8087",
"Path": "C:\",
"PathType": "AbsolutePath"
}]
},
...
},
...
}

这个JSON是在CmService!Container::Manager::Hcs::Details::GenerateCreateComputeSystemJson创建的。我们没有设法跟踪任何有助于建立该配置的文件。

在我们开始分析JSON中有趣的字段之前,我们要提到Palo Alto Networks的这篇文章。这篇文章解释了容器的内部结构,以及Job和Silo对象是如何关联的。

第一个有趣的配置标签是RunInSilo。这个标签在vmcompute中触发了一个代码流,使我们看到了下一个堆栈跟踪:

3: kd> k
# Child-SP RetAddr Call Site
00 ffff9a00`8da57648 fffff806`85d2b7fb wcifs!WcPortMessage
01 ffff9a00`8da57650 fffff806`85d63499 FLTMGR!FltpFilterMessage+0xdb
... (REDUCTED)
0b 0000004d`4218dbf0 00007ffa`08c5363d FLTLIB!FilterSendMessage+0x31
0c 0000004d`4218dc40 00007ffa`08c48686 wc_storage!WciSetupFilter+0x195
0d 0000004d`4218dcf0 00007ffa`22e06496 wc_storage!WcAttachFilterEx+0x156
0e 0000004d`4218dee0 00007ffa`22de5a66 container!container::FilesystemProvider::Setup+0x15e
0f 0000004d`4218dfc0 00007ffa`22ded4ad container!container_runtime::CreateContainerObject+0x106
10 0000004d`4218e010 00007ffa`22decf3c container!container::CreateContainer+0x10d
11 0000004d`4218e4a0 00007ff6`fcf0bc7f container!WcCreateContainer+0x1c
12 0000004d`4218e4d0 00007ff6`fcf0c5c4 vmcompute!ComputeService::JobUtilities::ConvertJobObjectToContainer+0xcb
13 0000004d`4218e590 00007ff6`fce8573f vmcompute!ComputeService::JobUtilities::CreateSiloForIsolatedWorkerProcess+0x4dc
14 0000004d`4218e8c0 00007ff6`fce875c5 vmcompute!ComputeService::Management::Details::PrepareJobForWorkerProcess+0x17b
15 0000004d`4218e9a0 00007ff6`fcee6cbb vmcompute!ComputeService::Management::Details::ConstructVmWorker+0xfd5
... (REDUCTED)

从堆栈中,我们可以了解到,每当计算系统收到筒仓配置时,它就会通过container!WcCreateContainer调用创建并配置一个容器。作为配置的一部分,它还通过FLTLIB!FilterSendMessage与wcifs.sys驱动进行通信。我们很快会解释这个驱动和它的用途。

第二个有趣的功能是VirtualSmb标签,用于为我们之前提到的挂载的基础层路径创建相应的共享。我们很快也会回到这个问题上。

容器隔离

正如我们在堆栈跟踪中看到的,容器的创建包括在端口WcifsPort上与wcifs.sys驱动(Windows容器隔离FS过滤器驱动)打开过滤器通信通道。这是一个用户模式代码与过滤器驱动进行通信的常用方法。

这个小型过滤器驱动在实现容器文件系统虚拟化方面有着重要的作用。这个驱动在客户和主机中都扮演着这个角色。

文件系统过滤器驱动程序通常相当复杂,这个也不例外。幸运的是,Google Project Zero的James Forshaw最近写了一篇很好的文章,解释了Windows FS过滤器驱动程序的底层设计,这有助于我们理解我们案例中的逻辑。

我们可以把驱动逻辑分为2个部分:

  • 驱动程序的配置--配置取决于驱动程序是在客户系统还是在主机系统上运行。
  • 处理操作回调,如WcPreCreate、WcPostCreate、WcPreRead和WcPostRead。这些回调包含主要的逻辑、数据操作和适当的重定向。

我们将解释这个驱动程序用来了解沙箱的生态系统的一些方法

初始配置

访客配置

正如我们前面所说的,主机和访客都使用这个驱动程序,但是方式不同

访客通过注册表接收一组参数,用于其初始配置。其中一些参数在HKLMSYSTEMCurrentControlSetControl和HKLMSYSTEMCurrentControlSetControlBootContainer,我们可以看到如下:

你可能会注意到IO_REPARSE_TAG_WCI_1(代码0x90001018),我们早些时候在 "真正的 "VHDx文件中看到过。这个标签,连同我们在BaseLayer.vhdx中看到的IO_REPARSE_TAG_WCI_LINK_1作为一个reparse标签,被硬编码到wcifs!WcSetBootConfiguration方法中:

访客配置的第二个更重要的部分是在wcifs!WcSetupVsmbUnionContext中,它在那里设置了一个被称为Union Context的虚拟化层。在幕后,驱动程序在几个上下文对象上存储自定义数据,并通过适当的NT API访问它们--FltGetInstanceContext、PsGetSiloContext和FltGetFileContext。这些自定义对象包含AVL树和哈希表,以有效地查询虚拟化层。

WcSetupVsmbUnionContext方法有两个更有趣的工件。一个是vSMB路径,它是层的一部分,另一个是HOST_LAYER_ID GUID,我们之前在解析的MFT和描述虚拟机的JSON中看到过:

随着我们的深入,我们看到有迹象表明,虚拟SMB方法被用来在客户和主机之间共享文件。很快我们就会看到,vSMB是基础层实现和映射文件夹共享的主要方法。

主机配置

对于主机系统来说,主要的配置发生在父计算进程vmcompute启动容器创建时,并向WcifsPort发送一个自定义消息。这就触发了wcifs!WcPortMessage,这是一个回调例程,用于发送任何消息到该特定端口。

下面是服务向过滤器驱动发送的消息的部分重构:

```
struct WcifsPortMsg
{
DWORD MsgCode;
DWORD MsgSize;
WcifsPortMsgSetUnion Msg;
};

struct WcifsPortMsgSetUnion
{
DWORD MsgVersionOrCode;
DWORD MsgSize;
DWORD NumUnions;
wchar_t InstanceName[50];
DWORD InstanceNameLen;
DWORD ReparseTag;
DWORD ReparseTagLink;
DWORD NotSure;
HANDLE Job;
BYTE ContextData[1];
};struct WcifsPortMsg
{
DWORD MsgCode;
DWORD MsgSize;
WcifsPortMsgSetUnion Msg;
};

struct WcifsPortMsgSetUnion
{
DWORD MsgVersionOrCode;
DWORD MsgSize;
DWORD NumUnions;
wchar_t InstanceName[50];
DWORD InstanceNameLen;
DWORD ReparseTag;
DWORD ReparseTagLink;
DWORD NotSure;
HANDLE Job;
BYTE ContextData[1];
};
```

ContextData字段还包含联合应映射的设备路径。

操作回调

在注册过程中,过滤器驱动为它想拦截的每个操作提供了一组回调。过滤器管理器在每个文件操作前/后调用这些回调,我们可以看到如下。

在不深入了解技术细节的情况下,该驱动定义并处理了两个自定义的reparse标签:

  • IO_REPARSE_TAG_WCI_1 - 这是一个主要的标签,表明磁盘上的文件实例是虚拟的,真正的路径可以在其内部结构中找到。这种 "转换 "的使用实例:
  • 访客将文件从其原始路径C:Windowssystem32kernel32.dll转换为vSMB路径DevicevmsmbVSMB-{dcc079a-60ba-4d07-847c-3493609c0870}osWindowsSystem32kernel32.dll。
    主机将文件从基础层设备路
  • C:ProgramDataMicrosoftWindowsContainersBaseImages949cec7-8165-4167-8c7d-67cf14eeede0BaseLayerFilesWindowsSystem32en-USapphelp.dll.mui转换为真实路径C:WindowsSystem32en-USapphelp.dll.mui。
    这种转换相当有趣,因为它主要发生在基础层中包含这种reparse标签的空系统文件夹中(比如en-US文件夹)。
  • IO_REPARSE_TAG_WCI_LINK_1 - 据我们所知,这个标签只在主机上使用,并将系统文件从基础层设备路径C:ProgramDataMicrosoftWindowsContainersBaseImages949cec7-8165-4167-8c7d-67cf14eeede0BaseLayerFilesWindowsSystem32kernel32. dll到真正的路径C:WindowsSystem32kernel32.dll。与上一点相比,这个例子的DLL文件条目确实存在于基础层中,并且有这个reparse标签。

发现vSMB是操作系统底层共享的主要方法是相当令人惊讶的。既然我们知道它是生态系统中的一个重要的通信方法,那么下一步自然就是进一步挖掘内部。

(v)SMB文件共享

在沙箱安装期间,我们注意到vmcompute通过向存储提供者设备调用CreateFileW来创建几个虚拟共享,并发送IOCTL 0x240328。这样一个调用的样本路径可能看起来像这样。??STORVSPVSMB??C:ProgramDataMicrosoftWindowsContainersBaseImages949cec7-8165-4167-8c7d-67cf14eeede0BaseLayerFiles.

创建这些共享的方法是vmcompute! ComputeService::Storage::OpenVsmbRootShare。我们可以在下面的堆栈跟踪中看到它的流程:

3: kd> k
# Child-SP RetAddr Call Site
00 ffff9a00`8d48a178 fffff806`85fd6af8 storvsp!VspFileCreate
01 (Inline Function) --------`-------- Wdf01000!FxFileObjectFileCreate::Invoke+0x29 [minkernelwdfframeworksharedincprivatecommonFxFileObjectCallbacks.hpp @ 58]
... (REDUCTED)
11 0000004d`4210d690 00007ff6`fcf33700 KERNELBASE!CreateFileW+0x66
12 0000004d`4210d6f0 00007ff6`fceb8180 vmcompute!ComputeService::Storage::OpenVsmbRootShare+0x3ac
13 0000004d`4210d850 00007ff6`fceba0fc vmcompute!ComputeService::VirtualMachine::Details::ConfigureVSMB+0x598
14 0000004d`4210da30 00007ff6`fceba908 vmcompute!ComputeService::VirtualMachine::Details::InitializeDeviceSettings+0x918
15 0000004d`4210eb90 00007ff6`fce86abd vmcompute!ComputeService::VirtualMachine::CreateVirtualMachineConfiguration+0x68
16 0000004d`4210ebe0 00007ff6`fcee6cbb vmcompute!ComputeService::Management::Details::ConstructVmWorker+0x4cd
... (REDUCTED)

此外,当我们使用WSB文件配置将主机文件夹映射到访客时,也会调用同样的方法。例如,映射Sysinternals文件夹的结果是对驱动程序的下一个调用。??STORVSPVSMB?C:Usershyperv-rootDesktopSysinternalsSuite。

通过(v)SMB访问文件

在创建这些共享后,我们可以通过创建的别名在客体中访问它们。我们可以使用type命令来打印主机的kernel32.dll,其路径为:.vmsmbVSMB-{dcc079a-60ba-4d07-847c-3493609c0870}osWindowsSystem32kernel32.dll。

为了服务于vSMB文件,作为VM工作者进程的一部分,vmusrv模块创建了一个工作者线程。这个模块是一个用户模式的vSMB服务器,它在vmusrv!VSmbpWorkerRecvLoop例程中直接从VMBus请求数据包,然后继续处理这些数据包。

为创建文件操作提供服务

每当vmusrv收到一个Create SMB请求时,它就会向存储提供者驱动程序发起一个新的请求。这样的一个调用可能看起来像这样:

2: kd> k
# Child-SP RetAddr Call Site
... (REDUCTED)
0c ffff9a00`8d9522e0 fffff806`892c4741 storvsp!VspVsmbCommonRelativeCreate+0x369
0d ffff9a00`8d952510 fffff806`892c3b7e storvsp!VspVsmbHandleRelativeCreateFileRequest+0x321
0e ffff9a00`8d952790 fffff806`892c0f85 storvsp!VspVsmbDispatchIoControlForProcess+0x11e
0f ffff9a00`8d9527e0 fffff806`8100e522 storvsp!VspFastIoDeviceControl+0x175
... (REDUCTED)
13 000000ae`9c0ff298 00007ffa`110c0c0a ntdll!NtDeviceIoControlFile+0x14
14 000000ae`9c0ff2a0 00007ffa`110c0456 vmusrv!CShare::OpenFileRelativeToShareRootInternal+0x306
15 000000ae`9c0ff3e0 00007ffa`110b9381 vmusrv!CShare::OpenFileRelativeToShareRoot+0x356
16 000000ae`9c0ff510 00007ffa`110b4451 vmusrv!CFSObject::CreateFileW+0x185
17 000000ae`9c0ff690 00007ffa`1109a568 vmusrv!CShare::Create+0x91
18 000000ae`9c0ff740 00007ffa`1109d74d vmusrv!ProviderCallback_Create+0x30
19 000000ae`9c0ff780 00007ffa`1109c299 vmusrv!SrvCreateFile+0x331
1a 000000ae`9c0ff860 00007ffa`1109c6f0 vmusrv!Smb2ExecuteCreateReal+0x111
1b 000000ae`9c0ff940 00007ffa`110a08da vmusrv!Smb2ExecuteCreate+0x30
1c 000000ae`9c0ff970 00007ffa`11098907 vmusrv!Smb2ExecuteProviderCallback+0x7e
1d 000000ae`9c0ff9d0 00007ffa`11088311 vmusrv!Smb2PacketProcessing+0x97
1e 000000ae`9c0ffa40 00007ffa`11087225 vmusrv!Smb2PacketProcessingCallback+0x11
... (REDUCTED)

与存储提供者的通信是通过一个代码为0x240320的IOCTL完成的,而引用的句柄是在初始化阶段打开的vSMB路径:

如果我们仔细观察storvsp!VspVsmbCommonRelativeCreate,我们看到每次执行后都会调用nt!IoCreateFileEx。这个调用包含了所需文件的相对路径和一个额外的RootDirectory字段,它代表了安装的基础层VHDx中的 Files文件夹:

提供读/写操作

读/写操作是由vmusrv!CFSObject::Read/vmusrv!CFSObject::Write中的工作线程执行。如果文件足够小,线程只需在句柄上执行ReadFile/WriteFile。否则,它将文件映射到内存中,并通过VMBus上面的RDMA有效地传输它。这个传输是在vmusrv!SrvConnectionExecuteRdmaTransfer执行的,而RDMA通信是使用IOCTL 0x3EC0D3或0x3EC08C与RootVMBus设备(主机VMBus设备名称)进行的。

2: kd> k
... (REDUCTED)
06 ffffad0e`3bee7650 fffff800`36225b62 vmbusr!RootIoctlRdmaFileIoHandleMappingComplete+0x10f
07 ffffad0e`3bee7690 fffff800`361fee21 vmbusr!RootIoctlRdmaFileIo+0xf2
08 ffffad0e`3bee76f0 fffff800`339da977 vmbusr!RootIoctlDeviceControlPreprocess+0x191
... (REDUCTED)
12 00000009`ae27f7e8 00007ffe`281ce773 ntdll!NtDeviceIoControlFile+0x14
13 00000009`ae27f7f0 00007ffe`281dcbd2 vmusrv!SrvConnectionExecuteRdmaTransfer+0x24f
14 00000009`ae27f940 00007ffe`281d4874 vmusrv!CFile::ReadFileRdma+0xc2
15 00000009`ae27f9c0 00007ffe`281c218e vmusrv!CFSObject::Read+0x94
16 00000009`ae27fa00 00007ffe`281c08da vmusrv!Smb2ExecuteRead+0x1be
17 00000009`ae27fa60 00007ffe`281b8907 vmusrv!Smb2ExecuteProviderCallback+0x7e
18 00000009`ae27fac0 00007ffe`281a6a4e vmusrv!Smb2PacketProcessing+0x97
19 00000009`ae27fb30 00007ffe`3bba6fd4 vmusrv!SmbWorkerThread+0xce
... (REDUCTED)

访客对主机的流程

基于本文解释Storvsc.sys/Storvsp.sys关系的一些见解,我们可以将之前所有的技术块结合到下一个文件访问流程中。

  1. 我们使用命令类型来打开并打印kernel32.dll文件的内容。这是一个系统文件,因此沙箱并不拥有其副本,而是使用主机的副本。
  2. 访客不知道这个文件不存在,所以它通过文件系统驱动栈到存储驱动栈执行正常的文件访问。
  3. Hyper-V的存储消费者Storvsc.sys是一个miniport驱动,意味着它充当了客户的虚拟存储。它通过VMBus接收和转发SCSI请求。
  4. 存储提供者Storvsp.sys有一个工作线程,在storvsp!VspPvtKmclProcessingComplete监听VMBus上的新消息。
  5. 提供者解析VMBus请求,并将其传递给vhdparser!NVhdParserExecuteScsiRequestDisk,后者执行vhdmp.sys,VHD解析器驱动程序。
  6. 最终,vhdmp.sys 通过过滤器管理器访问 sandbox.vhdx 的物理实例,并执行读/写操作。在这种情况下,它读取客户文件系统过滤器管理器所要求的数据。该数据被返回给过滤器管理器进行进一步分析。
  7. 如上所述,返回的条目被标记为WCI reparse标签和主机层GUID。当wcifs.sys对文件执行创建后的操作时,它寻找该设备的联合上下文,并将文件对象替换为下一个对象。DevicevmsmbVSMB-{dcc079ae-60ba-4d07-847c-3493609c0870}osWindowsSystem32kernel32.dll
  8. 这个Devicevmsmb设备是作为SMB共享创建的,所以过滤管理器像其他普通共享一样访问它。在幕后,它通过VMBus向主机执行SMB请求。
  9. vSMB用户模式服务器vmusrv.dll在其工作线程方法vmusrv!SmbWorkerThread中轮询.VMbus设备的新消息。
  10. 正如我们之前所展示的,在创建操作中,服务器通过挂载的操作系统底层的句柄上的IOCTL与存储提供者进行通信。DeviceSTORVSPVSMB?C:ProgramDataMicrosoftWindowsContainersBaseImages949cec7-8165-4167-8c7d-67cf14eeede0BaseLayerFiles
  11. 存储提供者通过IoCreateFileEx执行文件请求。该请求是相对的,并包含挂载的操作系统层的RootDirectory。这就触发了过滤器管理器在挂载的操作系统层中打开文件。
  12. 与步骤(7)类似,返回的条目包含一个WCI reparse标签,这使得WCIFS.SYS在创建后的方法中改变文件对象。它将文件对象改为其物理路径。C:WindowsSystem32kernel32.dll
  13. 访问主机的kernel32.dll文件,并返回到客体。
  14. 对于ReadFile操作,wcifs.sys驱动在文件对象上面保存了一个上下文状态,以帮助它执行读写操作。此外,工作线程vmusrv通过直接访问文件或通过VMBus顶部的RDMA来执行读取请求。

实际过程要复杂得多,所以我们试图把重点放在对虚拟化至关重要的组件上。

沙箱还允许通过其配置将文件夹从主机映射到客户机。这样的文件夹会收到vSMB路径的唯一别名,其访问方式与操作系统层类似。唯一的区别是,路径是由bindflt.sys在客体过滤器管理器中改变的。

例如,如果我们将SysinternalsSuite文件夹映射到访客桌面文件夹,路径C:UsersWDAGUtilityAccountDesktopSysinternalsSuiteProcmon。 exe被改成了DevicevmsmbVSMB-{dcc079ae-60ba-4d07-847c-3493609c0870}db64085bcd96aab59430e21d1b386e1b37b53a7194240ce5e3c25a7636076b67Procmon.exe,其余进程保持不变。

玩转沙箱

在这项研究中,我们的目标之一是根据我们的需要来修改基础层的内容。现在我们了解了这个生态系统,它似乎很容易。

该修改有几个简单的步骤:

  1. 停止CmService,这个创建和维护基础层的服务。当该服务被卸载时,它也会移除基础层的挂载。
  2. 挂载基础层(它在C:ProgramDataMicrosoftWindowsContainersBaseImages949cec7-8165-4167-8c7d-67cf14eeede0BaseLayer.vhdx文件中)。这可以通过双击,或使用diskmgmt.msc工具完成。
  3. 对基础层进行修改。在我们的例子中,我们添加了所有FLARE的安装后文件。
  4. 解除基础层的挂载。
  5. 启动CmService。

在我们启动沙箱的那一刻,我们就有了我们令人敬畏的FLARE VM!

摘要

当我们开始研究Windows沙箱时,我们不知道这样一个 "简单 "的操作会归结为一个复杂的流程,其中有几个微软内部未记录的技术,如vSMB和容器隔离。

我们希望这篇文章能够帮助社区进一步收集信息和寻找错误。对我们来说,这是研究和理解虚拟化相关技术的第一步,意义重大。

如果有任何技术反馈,请随时在twitter上联系我们。

链接

Hyper-V VmSwitch RCE Vulnerability

https://www.youtube.com/watch?v=025r8_TrV8I

Windows Sandbox

https://techcommunity.microsoft.com/t5/windows-kernel-internals/windows-sandbox/ba-p/301849

Windows Sandbox WSB Configuration

https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-sandbox/windows-sandbox-configure-using-wsb-file

Windows Containers

NTFS Attributes

https://www.urtech.ca/2017/11/solved-all-ntfs-attributes-defined/

Reparse Point

https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points

NTFS Documentation

https://dubeyko.com/development/FileSystems/NTFS/ntfsdoc.pdf

NTFS Reparse Tags

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4

VHDx Parent Locator

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-vhdx/b6332a98-624d-46b8-bd0e-b77b573662f9

FS Filter Driver – Communication between User Mode and Kernel Mode

https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/communication-between-user-mode-and-kernel-mode

Hunting for Bugs in Windows Mini-Filter Drivers

https://googleprojectzero.blogspot.com/2021/01/hunting-for-bugs-in-windows-mini-filter.html

Hyper-V Storvsp.sys-Strovsc.sys Flow

https://www.linkedin.com/pulse/hyper-v-architecture-internals-pravin-gawale/

RDMA Explained by Microsoft

https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v-virtual-switch/rdma-and-switch-embedded-teaming

附录A

Vmwp的Windows沙箱JSON配置

{
"Owner": "Madrid",
"SchemaVersion": {
"Major": 2,
"Minor": 1
},
"VirtualMachine": {
"StopOnReset": true,
"Chipset": {
"Uefi": {
"BootThis": {
"DeviceType": "VmbFs",
"DevicePath": "\EFI\Microsoft\Boot\bootmgfw.efi"
}
}
},
"ComputeTopology": {
"Memory": {
"SizeInMB": 1024,
"Backing": "Virtual",
"BackingPageSize": "Small",
"FaultClusterSizeShift": 4,
"DirectMapFaultClusterSizeShift": 4,
"EnablePrivateCompressionStore": true,
"EnableHotHint": true,
"EnableColdHint": true,
"SharedMemoryMB": 2048,
"SharedMemoryAccessSids": ["S-1-5-21-2542268174-3140522643-1722854894-1001"],
"EnableEpf": true,
"EnableDeferredCommit": true
},
"Processor": {
"Count": 4,
"SynchronizeHostFeatures": true,
"EnableSchedulerAssist": true
}
},
"Devices": {
"Scsi": {
"primary": {
"Attachments": {
"0": {
"Type": "VirtualDisk",
"Path": "C:\ProgramData\Microsoft\Windows\Containers\Sandboxes\025b00c8-849a-4e00-bcb2-c2b8ec698bab\sandbox.vhdx",
"CachingMode": "ReadOnlyCached",
"NoWriteHardening": true,
"DisableExpansionOptimization": true,
"IgnoreRelativeLocator": true,
"CaptureIoAttributionContext": true
}
}
}
},
"HvSocket": {
"HvSocketConfig": {
"DefaultBindSecurityDescriptor": "D:P(A;;FA;;;SY)",
"DefaultConnectSecurityDescriptor": "D:P(A;;FA;;;SY)",
"ServiceTable": {
"befcbc10-1381-45ab-946e-b1a12d6bce94": {
"BindSecurityDescriptor": "D:P(D;;FA;;;WD)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)",
"AllowWildcardBinds": true
},
"7d2e0620-034a-4438-b0fd-ae27fc0172a1": {
"BindSecurityDescriptor": "D:P(A;;FA;;;SY)(A;;FA;;;S-1-5-83-0)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)"
},
"a715ac94-b745-4889-9a0f-772d85a3cfa4": {
"BindSecurityDescriptor": "D:P(A;;FA;;;LS)",
"ConnectSecurityDescriptor": "D:P(A;;FA;;;LS)",
"AllowWildcardBinds": true
},
"7b3014c3-284a-40d4-a97f-9d23a75c6a80": {
"BindSecurityDescriptor": "D:P(D;;FA;;;WD)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)",
"AllowWildcardBinds": true
},
"e97910d9-55bb-455e-9170-114fdfce763d": {
"BindSecurityDescriptor": "D:P(D;;FA;;;WD)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)",
"AllowWildcardBinds": true
},
"e5afd2e3-9b98-4913-b37c-09de98772940": {
"BindSecurityDescriptor": "D:P(D;;FA;;;WD)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)",
"AllowWildcardBinds": true
},
"abd802e8-ffcc-40d2-a5f1-f04b1d12cbc8": {
"BindSecurityDescriptor": "D:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;S-1-15-3-3)(A;;FA;;;S-1-5-21-2542268174-3140522643-1722854894-1001)",
"ConnectSecurityDescriptor": "D:P(D;;FA;;;WD)"
},
"f58797f6-c9f3-4d63-9bd4-e52ac020e586": {
"BindSecurityDescriptor": "D:P(A;;FA;;;SY)",
"ConnectSecurityDescriptor": "D:P(A;;FA;;;SY)",
"AllowWildcardBinds": true
}
}
}
},
"EnhancedModeVideo": {
"ConnectionOptions": {
"AccessSids": ["S-1-5-21-2542268174-3140522643-1722854894-1001"],
"NamedPipe": "\\.\pipe\025b00c8-849a-4e00-bcb2-c2b8ec698bab"
}
},
"GuestCrashReporting": {
"WindowsCrashSettings": {
"DumpFileName": "C:\ProgramData\Microsoft\Windows\Containers\Dumps\025b00c8-849a-4e00-bcb2-c2b8ec698bab.dmp",
"MaxDumpSize": 4362076160,
"DumpType": "Full"
}
},
"VirtualSmb": {
"Shares": [{
"Name": "os",
"Path": "C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer\Files",
"Options": {
"ReadOnly": true,
"TakeBackupPrivilege": true,
"NoLocks": true,
"ReparseBaseLayer": true,
"PseudoOplocks": true,
"PseudoDirnotify": true,
"SupportCloudFiles": true
}
}],
"DirectFileMappingInMB": 2048
},
"Licensing": {
"ContainerID": "00000000-0000-0000-0000-000000000000",
"PackageFamilyNames": []
},
"Battery": {},
"KernelIntegration": {}
},
"GuestState": {
"GuestStateFilePath": "C:\ProgramData\Microsoft\Windows\Containers\Sandboxes\025b00c8-849a-4e00-bcb2-c2b8ec698bab\sandbox.vmgs"
},
"RestoreState": {
"TemplateSystemId": "97d51d87-c49d-488f-bc29-33017f7703b9"
},
"RunInSilo": {
"SiloBaseOsPath": "C:\ProgramData\Microsoft\Windows\Containers\BaseImages\0949cec7-8165-4167-8c7d-67cf14eeede0\BaseLayer\Files",
"NotifySiloJobCreated": true,
"FileSystemLayers": [{
"Id": "8264f677-40b0-4ca5-bf9a-944ac2da8087",
"Path": "C:\",
"PathType": "AbsolutePath"
}]
},
"LaunchOptions": {
"Type": "None"
},
"GuestConnection": {}
},
"ShouldTerminateOnLastHandleClosed": true
}

相关推荐: 蚁剑流量改造学习

流量分析 挂个代理直接抓包就好了 UA头修改 修改蚁剑工作目录的/modules/request.js,这里面默认是antSword/v2.1 一般需要修改一下,不然极其容易被发现,可以利用平常爬虫的一些技巧,随机构造UA头 ``` let USER_AGEN…