I Like to Move It: Windows Lateral Movement Part 1 – WMI Event Subscription


Performing lateral movement in an OpSec safe manner in mature Windows environments can often be a challenge as defenders hone their detections around the indicators generated by many of the commonly used techniques. These indicators often include execution of Live off the Land Binaries (LOLBins), dropped and executed artifacts such as DLLs, EXEs and MSIs (which are often subsequently removed), or the use of known (and often monitored) features such as WMI’s Win32_Process.Create or Win32_Product.Install methods.

Navigating and minimising these IoCs while attempting to use public tools such as Cobalt Strike, Impacket or similar can be a minefield. As such, as a red teamer it is important to understand the techniques that are available, how they function and the indicators that might stem from use of a particular technique or tool.

Last year, I presented a three part series of posts on some my favourite techniques for persistence. This time, in the following series of posts I will document three of my favourite techniques for lateral movement, explaining how they work and how defenders can detect them starting off with WMI Event Subscription.

Lateral Movement with WMI Event Subscription

Part 3 of my persistence series described how WMI event subscription could be used for persistence. However, this is not the only potential use case for event subscriptions, and although it seems to be much less widely known/documented, they can also be deployed remotely and used for lateral movement.

As with most (if not almost all) lateral movement techniques, remotely deploying a WMI event subscription requires administrative rights on the remote system (and is subject to token filtering), therefore for this post we will assume that you have already verified sufficient privileges on the remote host.

The primary reason I favour this technique so much is that it can be implemented filelessly; that is, no artifacts need to touch disk so it can be relatively OpSec friendly.

Deploying a remote event subscription is not significantly different than deploying one locally with the exception that you must configure your ManagementScope with ConnectionOptions set to the remote namespace. This can be achieved using C# similar to the following:

string NAMESPACE = "\\\\\\\\" + Config.REMOTE_HOST + "\\\\root\\\\subscription";

ConnectionOptions cOption = new ConnectionOptions();
ManagementScope scope = null;
scope = new ManagementScope(NAMESPACE, cOption);
	scope.Options.Username = ACTIVE_DIRECTORY_USERNAME;
	scope.Options.Password = ACTIVE_DIRECTORY_PASSWORD;
	scope.Options.Authority = string.Format("ntlmdomain:{0}", ACTIVE_DIRECTORY_DOMAIN);
scope.Options.EnablePrivileges = true;
scope.Options.Authentication = AuthenticationLevel.PacketPrivacy;
scope.Options.Impersonation = ImpersonationLevel.Impersonate;

Following that, the components required are much similar to as described in our persistence post; an event and consumer must be bound together. I covered what these components are in my previous post, but to recap we can consider them to be:

  • Event Filter: A WQL event query that filters event to a specific set of conditions such as, Outlook.exe just spawned on the endpoint. A WQL query may looks something like:
Select * From __InstanceCreationEvent Within 5 
Where TargetInstance Isa “Win32_Process” AND TargetInstance.Name = "Outlook.exe"
  • Event Consumer: this is the specific action that we want to happen when the event is triggered, the two classes of interest for us from a red team perspective are the ActiveScriptEventConsumer and CommandLineEventConsumer classes. The ActiveScriptEventConsumer class allows for the execution of scripting code (from either JScript or VBScript engines), while the CommandLineEventConsumer class permits an arbitrary command to be run. My personal preference is always the ActiveScriptEventConsumer class so we can avoid the nuisances of navigating the LOLBin minefield.

When considering how to create a WMI event filter that is useful for lateral movement, we need a query that will automatically trigger either at a point of time in the near future, or through an action that we can instigate.

When I first started looking in to event filters, my first thought was to use a timer and indeed spent some time researching how to monitor for changes in the Win32_LocalTime and Win32_UTCTime classes, as well as using event timers. Unfortunately, for some undiscovered reason I could not make this work reliably so opted down a different path of implementing an event filter based on something that I could somewhat reliably control. My first thought was to monitor for creation of a specific process and potentially cause WMIPrvSE.exe or similar to launch which would in turn cause the filter to trigger. However, an alternate approach came to mind which was to monitor the Win32_LogonSession class and then just simply trigger a second authentication. This can be achieved using a query similar to the following:

SELECT * FROM __InstanceCreationEvent Within 5 Where TargetInstance Isa 'Win32_LogonSession'

The filter can be applied using code similar to the following:

ManagementClass wmiEventFilter = new ManagementClass(scope, new ManagementPath("__EventFilter"), null);
WqlEventQuery myEventQuery = new WqlEventQuery(Config.eventQuery);
myEventFilter = wmiEventFilter.CreateInstance();
myEventFilter["Name"] = filterName;
myEventFilter["Query"] = myEventQuery.QueryString;
myEventFilter["QueryLanguage"] = myEventQuery.QueryLanguage;
myEventFilter["EventNameSpace"] = @"\\root\\cimv2";

Once the filter has been deployed, we simply need to authenticate using code similar to as we described earlier, wait for the beacon and then remove the filter, consumer and binding. There is of course the potential for a legitimate authentication to occur during this short period, in which case we may end up with two beacons but this has yet to happen to me during practical use.

Now that we’re reliably able to catch events, we need apply an Event Consumer; to remain fileless, I would normally defer to the ActiveScriptEventConsumer class which can be applied using c# similar to the following which deploys the VBScript string held in the vbscript variable:

myEventConsumer = new ManagementClass(scope, new ManagementPath("ActiveScriptEventConsumer"), null).CreateInstance();
Console.WriteLine("[*] Attempting to create ActiveScriptEventConsumer with name: " + scriptName);
myEventConsumer["Name"] = scriptName;
myEventConsumer["ScriptingEngine"] = "VBScript";
myEventConsumer["ScriptText"] = vbscript;

Finally, we need to bind both the filter and the consumer together:

myBinder = new ManagementClass(scope, new ManagementPath("__FilterToConsumerBinding"), null).CreateInstance();
myBinder["Filter"] = myEventFilter.Path.RelativePath;
myBinder["Consumer"] = myEventConsumer.Path.RelativePath;

It’s worth noting that you may want to try and blend the names of your events and consumers with something that may already be in the environment, software such as SCCM is known to use event subscriptions.

You can convert your favourite .NET loader to VBScript using GadgetToJScript and this should work reliably for obtaining arbitrary shellcode execution if like me you prefer not to endure VBScript for too long 🙂

Once the follow up authentication has been initiated, you’re free to clean down your filter, consumer and binder using the Delete() method:


Let’s take a look at this in action for staging a Cobalt Strike beacon:

In my example, scrcons.exe will spawn from svchost.exe and load the CLR:

As my payload injects in to a running process (in this case svchost.exe), there are no additional process creation events and scrcons.exe will exit shortly after, leaving little trace that the payload executed.


Now that we’ve established how this technique works and how we can leverage it for lateral movement, let’s take a deeper look at potential ways that the blue team are able to detect its abuse.

Using Sysmon, we’re able to collect the WmiEventFilter (ID 19), WmiEventConsumer (ID 20) and WmiEventConsumterToFilter (ID 21) events:

<RuleGroup name="" groupRelation="or">
	<!-- Event ID 19,20,21, == WmiEvent. Log all WmiEventFilter, WmiEventConsumer, WmiEventConsumerToFilter activity-->
	<WmiEvent onmatch="exclude"/>

Applying these to our lab using Blacksmith (courtesy of @Cyb3rWard0g), we now get a much more detailed view of the activity:

As we can see from the screenshot above, we’re able to not only see the event filter used but we can also recover the VBS executed (even after it’s been deleted) in event ID 20:

In the above example, we’re using a simple VBS script that creates a file on the file system. However, while I was testing this with a real-world loader, I discovered that the event ID 20s weren’t being logged, noting the chain of 19 – 21 events with the event 20 missing:

Following some back and forth DMs with @Cyb3rWard0g, he was also able to reproduce this behaviour in his lab. This was curious and my initial thinking was that perhaps the events just weren’t showing in the event viewer due to the size of the VBS (circa 2MB), so with some assistance from @Cyb3rWard0g we reverted to recovering the logs via PowerShell, unfortunately they were not available here either:

Diving a little deeper, we soon discovered the root cause of the missing logs; the large WmiEventConsumer was causing Sysmon to crash:

We can confirm this by watching execution in ProcessHacker while the lateral movement occurs:

Sysmon64.exe does eventually restart, but courtesy of the crash we’re triggering we’re also able to evade the Event ID 20 logging which is where the guts of our payload resides 🙂

If you’re looking to reproduce this technique and hone your detections, we worked with @Cyb3rWard0g to produce a dataset for the excellent Mordor project, which is now available here.

@Cyb3rWard0g was also kind enough to put together a notebook for the Threat Hunters Playbook, which is available here.


WMI Event Subscription provides a viable candidate for lateral movement and can offer a relatively OpSec safe approach, avoiding command line execution and filesystem artifacts to achieve arbitrary script execution. While it is possible to detect this technique through acquiring the appropriate telemetry, limitations in Sysmon may make mean some artifacts go undetected.

This blog post was written by Dominic Chell.

written by

MDSec Research

Ready to engage
with MDSec?

Copyright 2021 MDSec