A while ago at work I was trying to help IT debug an issue they believed was being caused by a new security baseline. A piece of software that involved user endpoints connecting to a remote storage server, to work collaboratively, had ceased to function properly. When I took a look at one of these machines and the software in question, it sort of blew my mind in that “how many random legacy features does windows actually have” sort of way.

The client side of this application was essentially something embedded into File Explorer. Maybe this is more well known than I realise, but I hadn’t come across it before. It turned out to be implemented as a feature known as a Shell Namespace Extension. I thought to myself “huh, I wonder if this has any offensive applications” and shelved it to look at on another day. It’s now 2 or 3 years later and I finally got around to revisiting it - with the added perk that I now have LLMs to help deal with COM!

As it turns out, Shell Namespace Extensions can be used as an execution and persistence mechanism, that:

  • Can be established as an unprivileged user.
  • Presents a few different placement options determining how/when the payload might be executed. The most straightforward of these causes the payload to execute once when the Explorer process is initialised (with no other interaction required).

Prior Work

Little has been written about Shell Namespace Extensions, and nothing obvious I could find that was security related. However, COM hijacks are very well known, and this technique basically falls in the same camp (though it’s registering a new COM object rather than hijacking an existing one). There are some specific persistence methods that are very similar, or related, because they use other parts (or extensions) of the Shell:


Shell Namespace Extensions Background

The Windows Shell and the Namespace

The Windows Shell is essentially the name given to the GUI of the OS. This encapsulates the Desktop, Taskbar, Start Menu, File Explorer, and other parts of the UI.

The Shell creates a single hierarchical tree of navigable objects (virtual and real) called the Namespace. This Namespace includes the filesystem itself, as well as encompassing virtual folders, other devices, network resources, cloud storage, and things like the Recycle Bin. Many of these should be recognisable as the tree items on the left-hand side of the File Explorer window. As succinctly put by Wikipedia, File Explorer “is a Windows component that can browse the shell namespace.”

The objects in the namespace are known as “Shell Items”, and as mentioned, they include file system objects and virtual objects, but they also can include custom objects that are provided by extensions. This is how something like Dropbox can display and render items within File Explorer - and it’s also how this piece of software at work I was debugging had incorporated what felt like an entire app into Explorer.

Extensions

Namespace extensions essentially allow us as developers to add our own information (branches) to the tree-structured namespace, meaning that Explorer can then be leveraged as a GUI to view our data in a way we deem fit. Our extension creates a namespace root, that is presented as a virtual folder in the Shell namespace.

As outlined in the Microsoft docs, the extension consists of two parts:

  • A data manager
  • An interface between the data manager and Explorer

The data manager component is completely within the developer’s control - you are responsible for storing/managing the data required by your extension. This is relevant to the topic of namespace extensions as a persistence mechanism insofar as it highlights a limitation of the technique - the extensions don’t provide any other means of storing additional payloads other than the DLL that will be registered for the Namespace extension itself (this will be covered more later). The second component is required and will form the basis of the persistence mechanism, as well as influence how it is triggered and presented (or hidden) in Explorer.

As I’ve already indicated, Shell Namespace Extensions are implemented using COM - but before getting into that, it will be useful to look at a couple of the core data structures used by Namespace Extensions, as it will add some context to the COM interfaces.

Shell Item Identifiers

Because the namespace is a hierarchical tree containing both real and virtual objects, there needs to be a way to address or identify things that are in the namespace that works for either type. This is achieved by a structure called the PIDL, but we need to cover two other structures before understanding what a PIDL is.

First, we have a structure that describes an individual Shell item (the Shell Item Identifier).

typedef struct _SHITEMID {
	USHORT cb;    // Size of this structure in bytes (including cb itself)
	BYTE abID[1]; // Variable-length opaque identifier data
} SHITEMID;

You may find it hard not to simply remember this as a “Shitem ID”

There can then be a list of SHITEMID, and this constitutes an ITEMIDLIST. This list is terminated by a 2-byte NULL.

typedef struct _ITEMIDLIST {
  SHITEMID mkid;
} ITEMIDLIST;

Finally, we have a “Pointer to an ID List”… which is the PIDL. PIDLs can be either absolute or relative, just like regular file paths.

COM and extension interfaces

Shell Namespace Extensions are implemented as COM in-process servers. Once your extension is registered as an extension (as in, has the required registry keys created), the Shell uses various interfaces exposed by your DLL in order to incorporate the extension into the GUI. There are some core interfaces, and some that are optional.

The core interfaces are:

  • IShellFolder: This is the fundamental interface of the extension. It represents the folder node, and its methods facilitate the enumeration of and movement into child folders, as well as retrieving both the display names of the folders, and the attributes that indicate what options the extension supports.
    • There is also an IShellFolder2 that extends this interface to offer column/detail support for your Shell items.
  • IPersistFolder: This interface enables the initialisation of our Shell folder object. In this initialisation, our folder also learns its absolute PIDL, and therefore where it is in the Shell namespace.
    • There is also an IPersistFolder2 that provides the GetCurFolder method, used to retrieve the ITEMIDLIST for the folder object.

If you then wanted to actually be able to present a Shell folder that could be interacted with, you would likely also need to implement:

  • IEnumIDList: Used to enumerate the PIDLs of the child items inside your folder.
  • IContextMenu: Provides the right-click context menu for the items in your folder.
  • IExtractIcon: Provides icons for the items in your folder.
  • IDataObject: Enables both drag-and-drop and clipboard operations.

Using Namespace Extensions for Persistence

There are a couple of different places we can register an extension in the namespace, and also different methods on the exposed interfaces where we could execute our payload for persistence. These different options will necessitate different levels of interaction from the user. The root-level Desktop option is by far the easiest to use and the most reliable, but I’ll list the other ones anyway.

Desktop Extensions

This option is the most straightforward. It requires the fewest methods to implement, and is the easiest to trigger. This is a “root-level” extension, meaning that the extension will sit directly under the Desktop in the namespace (at the same level as “This PC”, “Network”, and the “Recycle Bin”). When an extension is registered here, a payload in IPersistFolder::Initialize will be triggered:

  • When the Explorer process itself initialises (usually on start-up or if the Explorer process is restarted.)
  • When a new extension is registered. This triggers a Shell change notification, prompting Explorer to re-initialise the extensions.

This means that with a single registration you get both initial execution of the payload, as well as persistence when the system is rebooted. This is pretty likely to happen for most users during routine usage, without additional social engineering. Using the IPersistFolder::Initialize method results in the lowest number of repeated payload executions.

These extensions are registered by writing the key:

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{GUID}\

MyComputer Extensions

This option would require the user to navigate to the “This PC” folder in File Explorer.

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{GUID}\

NetworkNeighborhood Extensions

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\NetworkNeighborhood\NameSpace\{GUID}\

How the Shell uses the extension

Ok so we’ve briefly covered what a Shell Namespace Extension is, and the necessary COM interfaces at a high level, and a couple of different locations an extension can be registered in for persistence - let’s walk through the process and also take a look at the flow of how the Shell actually loads and interacts with a test extension. For our test, we’ll register a DLL named NsExPersist.dll as a Desktop extension - the simplest and most viable option.

To register the extension we need to write a few registry keys. This will require a GUID for the CLSID, as is always the case when working with COM.

The first key will be the CLSID key, along with 2 subkeys:

  • HKCU\Software\Classes\CLSID\{GUID}\

We will then write two subkeys:

  • InprocServer32 which will then have two values:
    • A default value pointing to the file path of our extension DLL
    • A value named ThreadingModel set to Apartment to make this single-threaded
  • ShellFolder which will be used to set the attributes of our root folder.
    • Attributes needs to have at least SFGAO_FOLDER (0x20000000) in order for our extension to be initialised when the Explorer process starts. Other attributes are in the Microsoft docs - some of these would likely be required to hide the persistence mechanism in the GUI (not required when registering in the Desktop space in the way we are, but would potentially be when registering in other locations).

With our COM object registered we now need to hook that up as a namespace extension by writing to the \Desktop\Namespace key. We will do this in the Current User hive so that it doesn’t require elevated privileges.

  • HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\<GUID>
    • We add the default value of this key to a string representing the name of our root-level folder. In this case we won’t actually see it, but you would want to write something innocuous to it.

Once these keys are written, our payload will automatically be executed. This is because the windows.storage.dll, the module containing a large amount of the Shell implementation, sets up registry watcher callbacks on keys relevant to the construction of various Shell namespace objects. In this case, the CDesktopFolder, which we are essentially targeting via our extension (specifically the CRegFolder that is owned by CDesktopFolder) , watches the \Desktop\NameSpace. When we write our extension key, the write triggers the callback, which ultimately leads the extensions in the registry to be enumerated and loaded.

This is also why it’s important that if you want to utilise the extension registration as an execution vector, the COM class has to be registered first, otherwise the CoCreateInstance call triggered by writing to the NameSpace location key will fail.

There are numerous change notification watchers related to the namespace extensions, so it’s worth noting that the IPersistFolder::Initialize method will likely be called multiple times upon reinitialization of the extensions, requiring payloads to use something like a mutex to limit executions.

We can test that our payload also executes upon Explorer’s start-up by restarting it with taskkill /f /im explorer.exe && start explorer.exe.

Detection

This vector should be fairly straightforward to detect due to it requiring a newly registered COM object, as well as very specific registry keys being written to that aren’t frequently touched.

The keys would be:

  • \Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{GUID}\
  • \Software\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace\{GUID}\
  • \Software\Microsoft\Windows\CurrentVersion\Explorer\NetworkNeighborhood\NameSpace\{GUID}\

And this would be for both the HKCU and HKLM hives given that you could use elevated privileges to register this in HKLM even though you’d end up with an unprivileged payload.

Another avenue would be investigating unusual DLLs loaded by Explorer.exe. Explorer does load a lot of stuff, but the typical filtering for unsigned binaries and/or unusual paths would cut this down a lot.

PoC

Here’s a simple Visual Studio solution containing a basic PoC for both the extension DLL and the registrar executable: https://github.com/inb1ts/NsExPersist


References