如何利用Windows预览机制实现持久化

robots

 

0x00 前言

用户对文件在Explorer中的布局有各自独特的偏好。有些人喜欢紧凑的详细信息视图,有些人喜欢带有详细信息窗格的内容视图,还有些人甚至会喜欢使用小图标。Explorer提供了各种自定义选项,允许Windows用户以各种方式查看文件系统的内容,然而我们特别感兴趣其中的一个功能:预览窗格(Preview Pane)。

预览窗格允许用户快速浏览所选文件的内容,不需要实际打开该文件。该功能在Windows 10中默认禁用,但可以通过Explorer的查看->预览窗格菜单中启用。

这个功能表面上看起来比较简单,但事实并非如此。比如,Windows如何知道怎么显示某些文件类型的内容,不显示其他文件类型呢?预览视图是由Explorer来控制,还是在另一个进程中完成呢?这些处理器(handler)是否可以被滥用呢?我们花了几天事件来探索资源管理器预览处理器,想深入了解这些处理器的工作原理,回答上述问题。

 

0x01 工作原理

我们研究的第一步,是想澄清Explorer在将文件的预览信息呈现给用户时到底执行了哪些操作。首先我们启用预览窗格,导航到某个文件夹,该文件夹包含能够展示预览视图的一些文件类型(我们使用的是.CONTACT文件类型,该类型默认已安装在Windows上,大家还可以使用其他类型),然后运行Procmon以及Process Hacker,观察系统的行为。虽然我们并没有得到完整的信息,但总结出了以下几点:

1、Explorer首先会在HKCU中查找{8895b1c6-b41f-4c1c-a562–0d564250836f}子键,获取默认值,以便查询关联文件类型的预览处理器(如果未找到则继续寻找HKCR)。

2、Explorer查询在已注册的预览处理器列表中与该扩展(.CONTACT文件对应的是{13D3C4B8-B179–4ebb-BF62-F704173E7448})关联的CLSID 值。预览处理器列表位于HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers\中,根据微软官方说明,该列表主要用于系统优化。

3、随后Explorer会查询CLSID的InProcServer32值。

4、Explorer会将后续事务交给DCOM Server Process Launcher服务(DcomLaunch)来处理,该服务会收集与CLSID关联的AppID。

5、DcomLaunch会引用与AppID(位于HKCR\AppID\)对应的DllSurrogate值。需要注意的是,{6d2b5079–2f0b-48dd-ab7f-97cec514d30b}是系统原生x64预览处理器的默认值。WOW64处理器使用的是{534A1E02-D58F-44f0-B58B-36CBED287C7C}

6、随后DcomLaunch会启动代理进程PREVHOST.EXE,传入命令行参数:{HANDLER-INPROCSERVER32-CLSID} -Embedding

7、PREVHOST.EXE加载CLSID引用的进程内COM服务器。

8、PREVHOST.EXE打开待预览的文件。

此时,预览处理器DLL会被映射到代理进程PREVHOST.EXE中,文件会被处理,传回Explorer的预览窗格。如前文所述,在句柄加载期间以及加载之后,我们有很多小细节没有覆盖到,但此时我们已经知道如何滥用这个过程了。

 

0x02 构造处理器

现在我们已经了解了大致的处理流程,我们可以着手构造自己的预览处理器。值得庆幸的是,微软发布了一些非常有用的文档示例代码,可以作为参考。虽然官方提供的资源非常有用,但主要是面向开发者在生产环境中的指南,包含大量冗余代码(比如根据预览窗格大小自适应地调整预览大小)。

我们只需要最小的一个示例来测试我们的理论,因此我们写了一个基本的进程内COM服务端,根据微软文档,实现了IPreviewHandler以及IInitializeWithStream接口。虽然微软表示还需要实现IObjectWithSiteIOleWindow以及IPreviewHandlerVisuals接口,我们发现如果只关心处理器内代码执行逻辑的实现,不关心在面板中渲染完整预览视图时,那么就不需要实现这些接口。为了测试构造的处理器,当IPreviewHandler::DoPreview()调用我们的渲染函数时,我们只是简单地弹出一个消息框。

由于这些逻辑都与COM有关,我们修改了注册表,构造所需的所有键值,以便处理器能在主机上正常运行。这里微软的文档也提供了很多帮助,但我们并不确定实际需求与最佳实践之间的区别。我们发现需要如下注册表键值,才能弹出我们的测试消息框:

此时,我们已经创造了能工作的一个最简单POC,这个预览处理器关联的是.SPECTEROPS文件

 

0x03 提升权限

这种技术最大的障碍在于,默认情况下,预览处理器会在PREVHOST.EXE低完整性级别的实例中运行。这意味着即使我们可以执行代码,我们的令牌完整性级别(IL)也会限制我们访问在后续活动中访问操作系统的重要信息。

值得庆幸的是,微软自己也意识到在很多情况下,在低IL下运行对某些开发者来说是不可行的(比如需要将文件保存到带有中等完整性标签的目录中)。为了支持这些用例,开发者允许选择性退出低IL的隔离域,托管在PREVHOST.EXE代理进程的中等IL实例中。要实现这一点,微软指示开发者要在HKCR\CLSID\{PREVIEW-HANDLER-CLSID}中创建一个新的值DisableLowILProcessIsolation,并将值设置为1

由于HKCR实际上只是HKCU\Software\Classes以及HKLM\Software\Classes的组合,因此理论上开发者应该能够在当前用户上下文中,在HKCU创建所需的注册表键值、注册预览处理器。当用户预览所选择的文件类型时,对应的处理器就可以在中等IL级别中运行。为了测试这个理论,我们在先前注册的预览处理器中添加了DisableLowILProcessIsolation值,设置对应的值并刷新预览窗格后,我们发现自己仍运行在低IL中。

我们在原始的消息对话框中显示令牌的完整性级别。

为了澄清这个问题,我们运行Procmon,设置注册表操作过滤器,过滤以DisableLowILProcessIsolation结尾的路径。我们再次刷新预览窗格,但没有看到任何信息。在尝试其他几种文件类型后,Procmon终于捕捉到EXPLORER.EXE查询该键值的行为。这个事件对应的调用栈如下所示:

Procmon的符号解析在这里有点问题。对我们来说,第7帧(SHELL32!SHBrowseForFolder+0x63b)最为有趣,因为该条目对应的是函数SHELL32!DoesExtensionOptOutOfLowIL内的一个地址。该函数的反汇编代码如下所示:

观察这段代码,我们马上就知道哪里出了问题:系统只检查了HKLM下的注册表键值。这不但宣告了我们先前针对每个用户的持久性策略的失败,同时因为只有管理员能够写入HKLM,我们也无法以普通用户身份绕过低IL隔离机制。我们探索了微软提供的一个独立代理进程来托管我们的处理器,但由于这些进程也是以低IL来运行,因此这条路也行不通。

尽管这并不理想的结果,我们仍然得到了一种持久化机制,可以将自己的代码托管在微软签名的可执行文件中。由于需要一定权限,这种方式很少在初始化攻击中使用,而是会在获取更多权限后才涉及。此外,由于我们针对的是HKLM,系统中的所有用户都会受到影响,不单单是当前的用户。

 

0x04 具体操作

为了充分利用这种技术,我们的工具会分解成3个不同的组件:载荷、目标文件以及释放器。

1、载荷:处理器DLL,在代理进程中加载,开始执行我们的恶意代码。载荷会被释放到磁盘中用户定义的位置。

2、目标文件:扩展名与我们设置的处理器相匹配的文件。该文件同样会被释放到磁盘的任意位置,但最好释放到用户很有可能通过Explorer浏览到的位置。

3、释放器:负责释放载荷和目标文件(可选),设置所需的注册表键值,强制EXPLORER.EXE预览目标文件,运行我们的代码。释放器通过C2运行在内存中。

处理器内运行的函数不再使用测试消息框,而是换成shellcode运行程序。这部分内容留给大家完成,但还是需要考虑到一些问题:

1、通过程序操作的方式启用预览窗格后,Explorer必须重启,不然处理器不会被运行。

2、由于可能会运行多个处理器实例,因此需要某种类型的互斥锁,避免出现非预期行为。

3、可以无缝劫持现有的处理器,但由实现上的差异(比如Word会根据文件扩展名来使用不同的ProgID,而不是CLSID),撤销劫持并不是想象中的那么简单。

 

0x05 PoC

整个攻击的PoC可参考此处视频

 

0x06 检测方式

这种技术的检测方式很大程度上需要依赖对注册表改动操作。在开发这项技术的过程中,我们澄清了实现这种持久化技术的基本条件。虽然我们可以监控大量的特征常量,但攻击者有各种方式可以破坏检测逻辑(比如使用ProgID,而不是CLSID)。这里我们首先介绍一下最基本的检测条件,然后再讨论能让检测更加稳健的一些限定条件。

这里很重要的一点是:攻击者不需要为新文件类型实现一个预览处理器,可以按照大致相同的路线,简单地劫持已有的处理器即可。

基本条件

为了让这种技术能够发挥作用,又具备足够权限(即不在低IL下执行),我们在构造基本检测能力时,可以将关注点集中在几个特定的注册表项上。

需要监控的第一个也是最重要的一个事件:在HKLM\Software\Classes\CLSID\*的任意注册表项中将DisableLowILProcessIsolation值设置为1。攻击者必须在HKLM中设置这个值,以便在中等IL中启动代理进程,以便作为普通用户与主机进行交互。虽然这个事件覆盖的范围比较大,但经过测试,我们发现对这个值的操作实际上相当罕见。

第二个注册表键值为HKCR\Software\Classes\*\ShellEx\{8895b1c6-b41f-4c1c-a562–0d564250836f},创建这个键值是在系统上安装预览处理器的基本条件。这个键值可以用于任何文件类型,包括现有的或者新的扩展名,但必须被设置。要注意的是,我们必须使用通配符过滤器,而不是将范围限定为.*,避免将检测限制在文件扩展名上。这是因为注册表中的文件类型(比如.foo)可以与ProgID关联(比如foo),这些ProgID仍然可以发挥相同的作用。

最后一个基本条件是将预览处理器的CLSID作为值,添加到HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers键(用于特定用户)或者HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers(用于所有用户)。这个值用来指定预览处理器的友好名称,但不一定需要设置。

分类条件

虽然基本条件提供了少量的事件,可以识别预览处理器的安装操作,但还有其他一些条件,可以提供关联的上下文信息。

第一个条件是必须启用Explorer的预览窗格,这是这种攻击技术的必备条件。我们需要将这个条件当成一个可选事件,因为用户可能已经启用了该功能,这样就不会出现注册表操作事件。预览窗格默认处于禁用状态,对窗格的启用操作可以协同判断攻击者的行为。我们可以监控HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Modules\GlobalSettings\DetailsContainer,判断DetailsContainer的值是否被设置为02 00 00 00 01 00 00 00,同时监控HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Modules\GlobalSettings\Sizer,判断DetailsContainerSizer是否被设置为15 01 00 00 01 00 00 00 00 00 00 00 6d 02 00 00。正常情况下,只有EXPLORER.EXE才会设置这些键值。

我们还可以监控正在设置的代理进程。代理进程正常情况下是PREVHOST.EXE,但攻击者可能会在系统中注册自己的进程,以便自定义应用能处理预览,(比如某个托管处理程序需要加载特定版本的公共语言运行时(CLR))。代理进程能够帮助攻击者规避基于进程或者文件的检测机制。AppID应该在HKLM\Software\Classes\CLSID\*中设置,如果使用的是默认的PREVHOST.EXE,那么x64处理器对应的应该为{6d2b5079–2f0b-48dd-ab7f-97cec514d30b},在x64主机上运行的x86处理器应该为{534a1e02-d58f-44f0-b58b-36cbed287c7c}。如果攻击者选择使用自己的代理进程,那么必须创建HKLM\Software\Classes\AppId\*,并且DllSurrogate的值应该设置为自定义应用的路径。无论使用哪个代理进程,该进程始终会使用命令行参数{MALICOUS-HANDLER-CLSID} -Embedded来启动,其中CLSID为已注册的处理器DLL对应的值。

最后一点,要关注预览处理器DLL本身的注册行为。攻击者需要设置HKLM\Software\Classes\CLSID\*\InProcServer32的默认值,只想磁盘上的路径。需要注意的是,这个事件可能相对而言会比较多一些。此外,DLL文件在安装时不一定需要存在,可以在攻击者准备好后随时释放。

SQL查询示例

index=azure_mdatp sourcetype="mdatp:DeviceRegistry" AND registry_path="HKLM\Software\Classes\CLSID\*"
[search index=azure_mdatp sourcetype="mdatp:DeviceRegistry" registry_path="HKLM\Software\Classes\CLSID\*" 
AND registry_value_name="DisableLowILProcessIsolation" action_type=RegistryValueSet]
AND [search index=azure_mdatp sourcetype="mdatp:DeviceRegistry" registry_path="HKLM\Software\Classes\CLSID\*" 
AND action_type=RegistryKeyCreated registry_key_name="HKCR\Software\Classes\*\ShellEx\{8895b1c6-b41f-4c1c-a562–0d564250836f}"]
AND [search index=azure_mdatp sourcetype="mdatp:DeviceRegistry" registry_path="HKLM\Software\Classes\CLSID\*" 
AND action_type=RegistryKeyCreated registry_key_name="HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers"]
| stats count values(host) as host dc(host) as host_count by registry_key_name 
| sort - count 
| where host_count < 5
| fillnull value=0
| `HumanTime(_time)`
| table HumanTime, _time, host, device_id, ContainerName, registry_key_name, process_exec, clean_path, registry_value_data, account_domain
(完)