Life is Pane: Persistence via Preview Handlers

This post was written together with Emily Leidy

People can have some strong preferences about how their files are laid out in Explorer. Some like the compact Details view. Others like the descriptive Content view with the Details pane. Some insane people even use Small Icons . Explorer offers dozens of customizations to how Windows users can view the contents of the filesystem, but a feature which became particularly interesting to us was the Preview Pane.

The preview pane allows users to have a quick peek at the content of a selected file without actually having to open it. This feature is disabled on default Windows 10 builds, but can be enabled in the Explorer menu under View→Preview pane.

While this seems relatively simple at face value, it is anything but under the hood. For example, how does Windows know how to display the contents of certain filetypes but not others? Are the previews controlled by Explorer or is it done in another process? Are these handlers abusable? We spent a few days exploring preview handlers to gain a deeper understanding of how they work and answer these questions.

Behind the Pane

The first step in our research was to figure out exactly what was going on when Explorer wanted to present a preview of a file to the user. To start, we enabled the preview pane, navigated to a folder with filetypes which are known to display previews (we used .CONTACT filetypes as it’s installed on Windows by default, but there are many more that could be used), launched Procmon and Process Hacker, and observed the system’s behavior. While our findings aren’t as complete as they could be, the general gist is as follows:

  1. Explorer queries the preview handlers for the associated filetype identified by the subkey {8895b1c6-b41f-4c1c-a562–0d564250836f}, first in HKCU and then in HKCR, and takes its default value.

2. Explorer queries the value associated with the CLSID collected from the extension ({13D3C4B8-B179–4ebb-BF62-F704173E7448} for .CONTACT files) in the list of registered preview handlers. This list resides in HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers\ and is used as an optimization by the OS according to Microsoft.

3. Explorer then queries the InProcServer32 value of the CLSID.

4. Explorer finally hands things off to the DCOM Server Process Launcher service (DcomLaunch) which collects the AppID associated with the CLSID.

5. DcomLaunch references the DllSurrogate value of the associated AppID, located in HKCR\AppID\. Note that {6d2b5079–2f0b-48dd-ab7f-97cec514d30b} is the default for native x64 preview handlers. WOW64 handlers use {534A1E02-D58F-44f0-B58B-36CBED287C7C}.

6. DcomLaunch then launches the surrogate process, PREVHOST.EXE, passing the command line arguments {HANDLER-INPROCSERVER32-CLSID} -Embedding .

7. PREVHOST.EXE loads the in-process COM server referenced by the CLSID.

8. PREVHOST.EXE opens the file to be previewed.

At this point, the preview handler DLL is mapped into the surrogate process, PREVHOST.EXE, and the file can be processed and passed back to Explorer’s preview pane. As mentioned before, there are many minor details not covered both during and after loading the handle, but by this point we had a good idea how this could be abused.

Building Our Handler

Now that we had the general flow worked out, we could set out on building our own preview handler. Thankfully, Microsoft published some pretty robust documentation and sample code which we could reference. While the resources provided are extremely helpful, they are geared toward a developer writing production-ready preview handlers and contain a lot of bloat required to make them work properly (e.g. adaptively resizing the preview based on the preview pane’s size).

We really only needed a minimal example to test our theory, so we wrote a basic in-process COM server and implemented the IPreviewHandler and IInitializeWithStream interfaces as described in Microsoft’s documentation. While Microsoft states that the IObjectWithSite, IOleWindow, and IPreviewHandlerVisuals interfaces also need to be implemented, we found that this is not the case when only code execution inside of the handler is required and the author doesn’t care about rendering a full preview in the pane. To test our handler, the renderer function called by IPreviewHandler::DoPreview() simply spawns a message box.

As with all things COM-related, we were then off to the registry to build out all of the keys needed to get our handler running on the host. Again, Microsoft’s documentation helped quite a bit here, but we weren’t sure what was an actual requirement versus a best practice. What we found was that the following registry keys and values were required in order for our test message box to pop up:

At this point, we had a functioning minimal POC of the preview handler targeting .SPECTEROPS files from which we could build our capability.

Leaving Low IL

The biggest hurdle with this technique is that, by default, preview handlers are run in a low integrity instance of PREVHOST.EXE. This means that even though we can get code execution, our token’s integrity level (IL) will limit us from accessing important parts of the operating system in the context of post-exploitation activities.

Thankfully for us, Microsoft realized that there are plenty of cases where running in low IL just won’t work for some developers (e.g. needing to save a file to a directory marked with medium integrity label). To support these use cases, developers are allowed to opt out of the low IL isolation behavior and instead be hosted in a medium IL instance of the PREVHOST.EXE surrogate process. In order to opt out, Microsoft instructs developers to create a new value, DisableLowILProcessIsolation , under HKCR\CLSID\{PREVIEW-HANDLER-CLSID} and set the value to 1.

Because HKCR is really just a combination of HKCU\Software\Classes and HKLM\Software\Classes, the developer should theoretically be able to register the preview handler under the context of the current user by creating the required registry keys and setting the values under HKCU. Then their handler will be executed in a medium IL surrogate whenever the user previews their chosen file type. We tested this assumption by adding the DisableLowILProcessIsolation value to the preview handler we had previously registered. After setting the value and refreshing the preview pane, we found that we were still running in low IL.

We replaced the original message box with one which shows the token’s integrity level

To try to figure out what was going on, we opened up Procmon and set a filter for registry operations whose path ended with DisableLowILProcessIsolation. We refreshed the preview pane but didn’t see anything. After trying a few other file types, Procmon eventually caught EXPLORER.EXE querying the value of this key. The call stack for this event can be seen below.

Procmon’s symbol resolution is a little off here. Frame 7 (SHELL32!SHBrowseForFolder+0x63b) is the most interesting for us as it resolves to an address inside of the function SHELL32!DoesExtensionOptOutOfLowIL. The disassembly of this function can be seen below.

Looking at the disassembly, it immediately became clear what was going wrong — only values in keys under HKLM are checked. This means that not only are our hopes of dropping per-user persistence dead, but because HKLM is only writable by administrators, we can’t even get around the low IL isolation as a normal user. We explored Microsoft’s directions for providing a separate surrogate process to host our handler, but ultimately that effort failed as those processes also spawns as low IL.

Although this wasn’t the ideal outcome, we still had a persistence mechanism with which we could host our code inside of a Microsoft-signed executable. Because of the privileges required, this will rarely see any use on initial compromise and instead will be used later in the attack chain as we gain more privileged footholds in the environment. Additionally, because we’re targeting HKLM, all users of the system will be affected and not just the current user.


In order to take full advantage of this technique, our tooling is broken up into three distinct components — a payload, a target file, and a dropper.

  • Payload: The handler DLL which will be loaded in the surrogate and begin executing our malicious code. This is dropped to disk in a user-defined location.
  • Target File: Any file with an extension matching the one we’ve set up our handler for. This is also dropped to disk in an arbitrary location, but one where the user will likely browse with Explorer.
  • Dropper: Responsible for (optionally) dropping the payload and target file, setting up all of the required registry keys, and forcing EXPLORER.EXE to preview the target file, launching our code. This is run in memory over C2.

The runner function inside of the handler was swapped from the testing message box to a shellcode runner. While we’ll leave this as an exercise to the reader, there are a few hang-ups with this technique that are worth covering.

  1. Explorer must restart after programmatically enabling the preview pane or the handler won’t fire
  2. Some type of mutex is needed as multiple instances of the handler can spawn unexpectedly and you’ll be flooded with agents
  3. Existing handler can be hijacked relatively seamlessly, but reverting the change isn’t always as simple as it appears due to differences in implementations (e.g. Word uses different ProgIDs based on the file extension instead of CLSIDs)

Proof of Concept


Detection of this technique relies heavily on monitoring changes to the registry. During our development of this technique, we identified the base conditions for implementing a handler for persistence. While there are a good amount of constants that we can monitor, this technique provides the actor with many chances to subvert detection logic (e.g. using ProgIDs instead of CLSIDs). We’ll first highlight the conditions from which a basic detection can be built and then discuss some of the qualifying conditions that can be used to make the detection more robust.

An important observation identified is that actors are not required to implement a preview handler for new file extensions and can just as easily hijack existing handlers following roughly the same methodology.

Base Conditions

In order for this technique to both function and be useful for attackers (i.e. not executing in low IL), we can focus our attention on a few specific registry keys while building out the base detection.

The first and most important event to monitor for the base detection is the value DisableLowILProcessIsolation being set to 1 in any key in HKLM\Software\Classes\CLSID\* . This key must be set in HKLM in order for the surrogate process to be launched in medium IL, allowing the actor to interact with the compromised host as a normal user. While the scope of this event is relatively large, we found that instances of this value being set are exceedingly rare during our testing.

The second registry key to target as part of the detection is HKCR\Software\Classes\*\ShellEx\{8895b1c6-b41f-4c1c-a562–0d564250836f} . The creation of this key is a base condition of installing any preview handler on the system. This can be for any filetype, including both existing or new extensions, but it must be set. Note that it is important to use the wildcard filter as written and not to scope it as .* in an attempt to restrict detections to only file extensions. This because filetypes (e.g. .foo) in the registry can be associated with ProgIDs (e.g. foo) which function the same in the context of this technique.

The final base condition is adding the CLSID of the preview handler as a value to the HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers key for per-user installation or HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PreviewHandlers for all users. This value will specify the friendly name of the preview handler for debugging but it is not required to be set.

Classifying Conditions

While the base conditions provide a minimal amount of events which can be used to identify the installation of preview handlers, there are a number of others that can provide supplemental context to the base detection, are conditional, or may aid in an investigation.

The first of these conditions is that Explorer’s preview pane must be enabled. This is a requirement for this technique to work. This should be considered an optional event because the victim may already have enabled this feature, meaning that a registry event won’t occur. Normally disabled by default, enabling the preview pane can provide a consistent choke point where we can observe an actor enabling it manually. To do this, we can monitor HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Modules\GlobalSettings\DetailsContainer for the DetailsContainer value being set to 02 00 00 00 01 00 00 00 and HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Modules\GlobalSettings\Sizer for the value of DetailsContainerSizer being set to 15 01 00 00 01 00 00 00 00 00 00 00 6d 02 00 00 . These keys should only be set by EXPLORER.EXE under normal conditions.

Another event which can provide additional context is the surrogate process being set. This process is PREVHOST.EXE under normal conditions, but the actor may register their own on the system to allow for a custom application to handle the preview such as in the instance of a managed handler which needs to load a specific version of the Common Language Runtime (CLR). This provides a valuable way for an actor to evade process or image-based detections. This AppID should be set in HKLM\Software\Classes\CLSID\* as the value AppID. If the default PREVHOST.EXE is to be used, this should be {6d2b5079–2f0b-48dd-ab7f-97cec514d30b} for x64 handlers or {534a1e02-d58f-44f0-b58b-36cbed287c7c} for x86 handlers running on an x64 host. If the actor opts to use their own surrogate application, a key HKLM\Software\Classes\AppId\* must be created with the value DllSurrogate set to the path of their custom application. Regardless of the surrogate process, it will always be launched with the command line arguments {MALICOUS-HANDLER-CLSID} -Embedded where the CLSID is that of the registered handler DLL.

The final piece which could provide value, especially during an investigation, is the registration of the preview handler DLL itself. This will be set as the default value in HKLM\Software\Classes\CLSID\*\InProcServer32 and will point to a path on disk. It is worth noting that this event can be relatively noisy compared to the other events generated by usage of this persistence technique. Additionally, this file doesn’t have to exist at the time of installation and can be dropped whenever the actor is ready to operationalize the persistence mechanism.

Example SPL Query

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


Thank you to Casey Smith for giving us the tip to explore preview handlers in the first place. Thank you to Dwight Hohnstein for collaborating with us to turn the handler into something usable.

Life is Pane: Persistence via Preview Handlers was originally published in Posts By SpecterOps Team Members on Medium, where people are continuing the conversation by highlighting and responding to this story.

Article Link: