Extending (and Detecting) PersistAssist: Act II

In the previous PersistAssist post, we looked at how to create a new persistence module to automate backdooring a PSProfile at a user level. In this post, we'll look at how to create a CommandLineEventConsumer WMI based persistence module. Aside from creating the module, we'll also take a look at WMI internals and how to detect this persistence mechanism.

WMI? Where's My Interface?

Fair warning: this section will get into the weeds of WMI (more specifically WMI subscriptions), if you want to get straight to the creation of the WMI persistence module, feel free to skip this section.

Close...but no cigar. Windows Management Interface (WMI) is...well its a management interface for windows built upon the Common Information Model (CIM). Many know WMI as the underlying tech that wmic uses, but wmic is just an interface for WMI the same way that powershell.exe is an interface for PowerShell.

It would be impossible to perform a proper deep dive into all that is WMI in a single blog post, for an in depth analysis of WMI check out this great series by Jonathon Johnson. Instead, we'll focus on the portion of WMI we care about for persistence operations: subscriptions.

WMI subscriptions are a mechanic that allows an attacker to trigger an event as a result of some performed operation. There are 3 main components to creating an event subscription:

  • EventFilter - Sets the operations that will kick off the event
  • EventConsumer - Determines the event to occur when the EventFilter operation is triggered
  • FilterToConsumerBinding - Binds the filter and consumer created

The EventFilter will be a WQL (Windows Query Language) query that will determine when to send the signal to the EventConsumer bound to the filter (the consumer would be bound using the FilterToConsumerBinding class). This filter is usually a query that triggers when a certain process is started, but it could be anything that could be specified using WQL.

In this context, there are two possible EventConsumers we can use to establish persistence: CommandLineEventConsumer or ActiveScriptEventConsumer. CommandLineEventConsumer allows for command execution via command line and ActiveScriptEventConsumer allows for either VBScript or JScript code execution. The module we'll be adding to PersistAssist in this blog post will utilize CommandLineEventConsumer.

Creating a WMI persist Module

In the previous post, we went over the various components of the persist module and the role that each part plays so we'll skip straight to writing the code. If you'd like a refresher on those components, refer to the Creating a new Persistence Module section of this post or check out the wiki.

The methods to interface with WMI to create a subscription is already written in the WMIOps.cs file in the Utils/PersistOps/ path. All that's left now is to actually implement these methods to register a subscription using the EventConsumer of our choosing (in this case it'll be using CommandLineEventConsumer).

When creating a new module, it'll look something like this. Aside from importing the usual Models namespace (which allows us to inherit the Persist abstract class), we'll also include the Utils.WMIOps and Utils.Extensions namespaces to allow us to access the WMI methods that will serve as the backbone for this module and extensions we'll want access to for input verification.

PersistName, PersistDesc, and PersistCategory are all fairly straightforward. To fill out PersistUsage, we have to think about what needs to be passed to the functions. Establishing the subscription will require a query and name for the filter, a name for the consumer (we won't need to specify the consumer type since the WMI methods already have this done), and a path that we want the subscription to execute. Cleaning up the subscription will require only the filter name and consumer name. This persistence method does require admin perms so we'll have to tell PersistAssist that the module requires admin. The result should look something like the following:

The arguments should already be included in Program.cs and Models/Data/ParsedArgs.cs so we don't have to worry about adding them to the framework.

Now that we've filled out the metadata, it's time to write the actual code the module will execute. As mentioned in the previous section, to successfully establish a WMI subscription, three components are required: an EventFilter, an EventConsumer, and a FilterToConsumerBinding to bind the previous two. The code for all of these functions is already available in the WMIOps.cs.

The registerEventFilter function allows for the creation of an EventFilter given the query that'll trigger the event consumer and the event name.

The registerCommandLineEventConsumer function will create an EventConsumer that'll trigger a command execution given the name for the EventConsumer and the path to the executable we want executed.

The registerFilterToConsumerBinding function will take the path returned from the previous two commands and bind the them.

Now that we know which functions we'll be using, let's write out the code to establish persistence. Though before attempting the persistence, we'll want to verify that the flags passed are valid. For PersistExec(), this is done by adding in this line before any other code:

if (pArgs.eventFilterQuery.isEmpty() || pArgs.eventFilterName.isEmpty() || pArgs.eventConsumerName.isEmpty() || pArgs.eventConsumerValue.isEmpty()) {
	throw new PersistAssistException("Incorrect parameters passed. See technique usage");

Once that's done, we'll implement the WMI subscription code and return a string will the data we'll need for both logging and cleanup purposes. The result would look something like this

Since the actual WMI functions have already been written, all that's needed to create a new module is to implement those functions previously discussed, because of this the actual persistence code is only three lines.

The cleanup method is going to be a bit different as this will return three lines of PowerShell that will perform the cleanup. Note that these commands to have to be run as admin to successfully execute.

Now that the module is created, it's testing time! For this example, I'll be using an executable that will ping a kali machine on my network that has tcpdump listening for ICMP requests. The trigger for this executable will be starting notepad.exe, using the following filter query:

SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA "Win32_Process" AND TargetInstance.Name = "notepad.exe"

Let's break down this filter query. Seems like it can be broken up unto a few chunks and then use those parts to get an idea of what exactly is going on here.

SELECT * FROM __InstanceCreationEvent WITHIN 5 

This first part is looking at every action from the __InstanceCreationEvent class within the span of 5 seconds.

WHERE TargetInstance ISA "Win32_Process"

This next bit is looking for a case where the TargetInstance property from the __InstanceCreationEvent class is a Win32_Process. Ok so it's looking for process creation.

AND TargetInstance.Name = "notepad.exe"

The last chuck is fetching the name of the process and looking to see if its notepad.exe.

Putting all of this together, we can surmise that the filter query is searching for an process creation instance within 5 seconds, where the process being created is notepad.exe

First, we're starting up a tcpdump to listen for ICMP traffic.

Next up, use the PersistAssist module we just created and trigger the event.

Now let's check tcpdump and see if we have any ICMP traffic on the network.

Judging by the ICMP traffic flowing through the network, we can assume the subscription was successfully created and triggers when the notepad.exe process is started. This can be verified using the Get-WMIObject cmdlet in PowerShell.

To remove the subscription, we'll run the cleanup module which will output a few PowerShell lines to clean up the persistence:

Artifact hunting: CommandLineEventConsumer Edition

WMI subscriptions will appear on Autoruns, or at least a portion of it. During my testing, I've found that only the EventConsumer is shown. I believe a more accurate approach to searching for WMI event subscriptions would be use the Get-WMIObject cmdlet to search for __EventFilter, CommandLineEventConsumer and __FilterToConsumerBinding.

Another reliable source of telemetry that can be used to detect WMI subscription activity would be sysmon logs. Sysmon is a wonderful logging tool that uses ETW providers for device activity.

Quick aside for sysmon install: Installing sysmon is as simple as downloading the sysinternals suite and running the sysmon binary found in the zipped folder. The beauty of sysmon is in its ability to be easily modified to search for events via a config file. For this post, I'll be using the sysmon-modular config. Sysmon can now be started using the sysmon-modular config using the command sysmon.exe -accepteula -i config.xml. The logs can then be viewed from EventViewer at the following path:

Application and Services Logs -> Microsoft -> Windows -> Sysmon

Now that sysmon is installed, let's take a look at what sort of events are created when running the persist module.

There are three event IDs we'd want to look out for: 19, 20. and 21. Event 19 will trigger on any EventFilter activity.

Event 20 will trigger on EventConsumer activity.

And Event 21 will trigger whenever a FilterToConsumerBinding class is created.

Feel free to fork PersistAssist and add in your own persistence mechanisms! We hope this is useful, and if you have any questions, don't hesitate to contact us or check out similar blog posts on our website.