Tracking WMI Activity with PSGumshoe

WMI (Windows Management Instrumentation) is the Microsoft implementation of the Web-Based Enterprise Management (WBEM) and Common Information Model (CIM) standards from the Distributed Management Task Force (DMTF). This allows for a unified way to manage a group of systems by administrators allowing them to get information about the system, its current state and to execute actions. Because of this many attackers leverage this for enumeration, lateral movement, and persistence. Defenders and security vendors leverage it heavily also, in fact, most vulnerability scanners would not be able to do a lot of what they do on windows hosts without it.

I wrote the PSGumshoe PowerShell module initially with a group of good friends of mine to help with the job of performing threat hunting and incident response. I maintain the module as my personal tool for working in my research of finding IOCs for the tools I write in my day job for the Red Team and the Pentest Team, also when I help out in some of the incident response engagements.

I would like to share some of the new functions I added to the module for the purpose of tracking some of the WMI activities that attackers may perform once they gained a foothold on the system.

Execution of Methods

In one engagement many years ago I learned that one could track WMI execution of class methods and even queries by enabling Other Object Access audit setting and then on each WMI namespace. A well-versed Windows Admin was able to track my queries and method executions and contain my action by having some well-tuned ingestion filters that alerted on any action which did not fit the normal behavior of the systems in his environment.

When configuring a GPO the setting is under Computer Configuration -> Windows Settings -> Security Settings -> Advanced Audit Policy Configuration -> Audit Policies -> Object Access

Audit Settings

Once the audit setting is set and pushed via GPO a logon script or manual process is needed to set the proper audit settings to track actions on a given namespace. To do this manually:

  1. open the WMI Control MMC or Computer Management MMC and open the properties for WMI Control.

  2. Select the Security tab, select the namespace to apply the adit settings to, and click on Security

On the next window we click on Advanced.

On the Advanced Security Settings for the namespace we do the following:

  1. Click on the Auditing tab

  2. Click on Add

In the Audit Entry window, do the following:

  1. Select the Principal the rule will apply to, my recommendation would be either Everyone or Authenticated Users.

  2. On Type select ALL since we want success and failure events.

  3. In Advanced Permission, I would recommend starting with Execute Method to detect class methods for latera movement and Full Write in the case a malicious WMI Provider creates a class or Permanent event components are created outside the root/subscription namespace. Also helpful in detection of WMI used as a C2 Channel http://2014.hackitoergosum.org/slides/day1_WMI_Shell_Andrei_Dumitrescu.pdf

The settings shown will not cover queries, without a process and capability for validating and filtering events before sending them to the SIEM this would be too noisy in a production environment.

Once the settings have been applied any attempt like the one bellow where a locally the Win32_Process class Create() method is used to create a process using WMI to break parent child relation will be logged.

PS C:\Users\Admin> $proc = Get-WmiObject -List win32_process
PS C:\Users\Admin> $proc.Create("notepad.exe")


__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 2
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ProcessId        : 5908
ReturnValue      : 0
PSComputerName   :

When we examine the logs under Security we will se event with Id 4662 where the ObjectServer will be WMI. The event will include under what namespace the event happened and under what user context. Under AdditinalInformation fields we will see if it was local or remote and the method that was invoked.

Local Method Execution

When a method is executed against the host from a remote system like in the example bellow the log will show that the method was executed remotely.

PS C:\Users\Admin> $remoteproc = Get-WmiObject -List win32_process -ComputerName sddc01
PS C:\Users\Admin> $remoteproc.Create("cmd.exe")


__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 2
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ProcessId        : 5812
ReturnValue      : 0
PSComputerName   :

We will see under AdditionalInfo that the method execution is Remote Execute.

Remote Method Execution

In PSGumshoe to aid with looking for this types of IOC the Get-EventWmiObjectAccess function is available.

PS C:\Users\Carlos Perez\Desktop> help Get-EventWmiObjectAccess

NAME
    Get-EventWmiObjectAccess

SYNOPSIS
    Get WMI Object Access events (EventId 4662).


SYNTAX
    Get-EventWmiObjectAccess [-LogName <String>] [-SubjectUserSid <String[]>] [-SubjectUserName <String[]>] [-SubjectDomainName
    <String[]>] [-SubjectLogonId <String[]>] [-ObjectName <String[]>] [-AdditionalInfo <String[]>] [-AdditionalInfo2 <String[]>]
    [-MaxEvents <Int64>] [-StartTime <DateTime>] [-EndTime <DateTime>] [-ChangeLogic] [<CommonParameters>]

    Get-EventWmiObjectAccess [-LogName <String>] [-SubjectUserSid <String[]>] [-SubjectUserName <String[]>] [-SubjectDomainName
    <String[]>] [-SubjectLogonId <String[]>] [-ObjectName <String[]>] [-AdditionalInfo <String[]>] [-AdditionalInfo2 <String[]>]
    [-Path] <String[]> [-MaxEvents <Int64>] [-StartTime <DateTime>] [-EndTime <DateTime>] [-ChangeLogic] [<CommonParameters>]

    Get-EventWmiObjectAccess [-LogName <String>] [-SubjectUserSid <String[]>] [-SubjectUserName <String[]>] [-SubjectDomainName
    <String[]>] [-SubjectLogonId <String[]>] [-ObjectName <String[]>] [-AdditionalInfo <String[]>] [-AdditionalInfo2 <String[]>]
    -ComputerName <String[]> [-Credential <PSCredential>] [-MaxEvents <Int64>] [-StartTime <DateTime>] [-EndTime <DateTime>]
    [-ChangeLogic] [<CommonParameters>]


DESCRIPTION
    Get WMI Object Access events (EventId 4662). from a local or remote host. Events can be filtered by fields.


RELATED LINKS

REMARKS
    To see the examples, type: "get-help Get-EventWmiObjectAccess -examples".
    For more information, type: "get-help Get-EventWmiObjectAccess -detailed".
    For technical information, type: "get-help Get-EventWmiObjectAccess -full".

As it can be seen in the help information the function allows for the filtering of events by some of the fields and one or more EVTX files can be passed, we can execute the function remotely or locally.

In the example bellow we are looking at a EVTX file that was pulled from a system and we are filtering for Local Execution of methods.

PS C:\Users\Carlos Perez\Desktop> Get-EventWmiObjectAccess -Path .\wmiobjectaccess.evtx -AdditionalInfo "Local Execute (ExecMethod)"


EventId           : 4662
Computer          : SDDC01.acmelabs.pvt
EventRecordID     : 610678
TimeCreated       : 3/27/2022 11:15:07 AM
SubjectUserSid    : S-1-5-21-2481759486-3878440335-2016164793-1000
SubjectUserName   : Admin
SubjectDomainName : acmelabs
SubjectLogonId    : 0x2ed6a
ObjectType        : WMI Namespace
ObjectName        : root\cimv2
AdditionalInfo    : Local Execute (ExecMethod)
AdditionalInfo2   : root\cimv2:Win32_Process::Create

EventId           : 4662
Computer          : SDDC01.acmelabs.pvt
EventRecordID     : 610678
TimeCreated       : 3/27/2022 11:15:07 AM
SubjectUserSid    : S-1-5-21-2481759486-3878440335-2016164793-1000
SubjectUserName   : Admin
SubjectDomainName : acmelabs
SubjectLogonId    : 0x2ed6a
ObjectType        : WMI Namespace
ObjectName        : root\cimv2
AdditionalInfo    : Local Execute (ExecMethod)
AdditionalInfo2   : root\cimv2:Win32_Process::Create

EventId           : 4662
Computer          : SDDC01.acmelabs.pvt
EventRecordID     : 610678
TimeCreated       : 3/27/2022 11:15:07 AM
SubjectUserSid    : S-1-5-21-2481759486-3878440335-2016164793-1000
SubjectUserName   : Admin
SubjectDomainName : acmelabs
SubjectLogonId    : 0x2ed6a
ObjectType        : WMI Namespace
ObjectName        : root\cimv2
AdditionalInfo    : Local Execute (ExecMethod)
AdditionalInfo2   : root\cimv2:Win32_Process::Create

Example passing several EVTX files via the pipeline.

PS C:\Users\Carlos Perez\Desktop> ls *.evtx | Get-EventWmiObjectAccess  -AdditionalInfo "Local Execute (ExecMethod)"


EventId           : 4662
Computer          : SDDC01.acmelabs.pvt
EventRecordID     : 610678
TimeCreated       : 3/27/2022 11:15:07 AM
SubjectUserSid    : S-1-5-21-2481759486-3878440335-2016164793-1000
SubjectUserName   : Admin
SubjectDomainName : acmelabs
SubjectLogonId    : 0x2ed6a
ObjectType        : WMI Namespace
ObjectName        : root\cimv2
AdditionalInfo    : Local Execute (ExecMethod)
AdditionalInfo2   : root\cimv2:Win32_Process::Create

EventId           : 4662
Computer          : SDDC01.acmelabs.pvt
EventRecordID     : 610678
TimeCreated       : 3/27/2022 11:15:07 AM
SubjectUserSid    : S-1-5-21-2481759486-3878440335-2016164793-1000
SubjectUserName   : Admin
SubjectDomainName : acmelabs
SubjectLogonId    : 0x2ed6a
ObjectType        : WMI Namespace
ObjectName        : root\cimv2
AdditionalInfo    : Local Execute (ExecMethod)
AdditionalInfo2   : root\cimv2:Win32_Process::Create

EventId           : 4662
Computer          : SDDC01.acmelabs.pvt
EventRecordID     : 610678
TimeCreated       : 3/27/2022 11:15:07 AM
SubjectUserSid    : S-1-5-21-2481759486-3878440335-2016164793-1000
SubjectUserName   : Admin
SubjectDomainName : acmelabs
SubjectLogonId    : 0x2ed6a
ObjectType        : WMI Namespace
ObjectName        : root\cimv2
AdditionalInfo    : Local Execute (ExecMethod)
AdditionalInfo2   : root\cimv2:Win32_Process::Create

WMI Operation Error Events

In the Microsoft-WMI-Arctivity/Operational log Windows logs all WMI related operation errors by default with Event ID 58585. On a day-to-day operation of Windows and also depending on the software installed on the OS the number of errors is a high one. If a SIEM solution allows for filters before forwarding the logs this will allow for improving the signal-to-noise ratio of the events sent. In the image below we can see that in my lab VM the number of errors is high.

PSGumshoe provides the Get-EventWmiOperationalError function to search and filter the error logs generated. An attacker may commit mistakes or simply do not have permission to perform an action generating an error to be logged. The event will include the computer and PID of the process that generated the even not only locally but also for remote actions.

Just like the other WMI function this function can be ran remotely against another computer, locally or against one or more EVTX files.

PS C:\Users\Admin> help Get-EventWmiOperationFailure

NAME
    Get-EventWmiOperationFailure

SYNOPSIS
    Get WMI Operation Failure events (EventId 5858).


SYNTAX
    Get-EventWmiOperationFailure [-LogName <String>] [-User <String[]>] [-ClientMachine <String[]>] [-ClientProcessId <String[]>]
    [-ResultCode <String[]>] [-Operation <String[]>] [-MaxEvents <Int64>] [-StartTime <DateTime>] [-EndTime <DateTime>] [-ChangeLogic]
    [<CommonParameters>]

    Get-EventWmiOperationFailure [-LogName <String>] [-User <String[]>] [-ClientMachine <String[]>] [-ClientProcessId <String[]>]
    [-ResultCode <String[]>] [-Operation <String[]>] [-Path] <String[]> [-MaxEvents <Int64>] [-StartTime <DateTime>] [-EndTime <DateTime>]
    [-ChangeLogic] [<CommonParameters>]

    Get-EventWmiOperationFailure [-LogName <String>] [-User <String[]>] [-ClientMachine <String[]>] [-ClientProcessId <String[]>]
    [-ResultCode <String[]>] [-Operation <String[]>] -ComputerName <String[]> [-Credential <PSCredential>] [-MaxEvents <Int64>] [-StartTime
    <DateTime>] [-EndTime <DateTime>] [-ChangeLogic] [<CommonParameters>]


DESCRIPTION
    Get WMI Operation Failure events (EventId 5858). from a local or remote host. Events can be filtered by fields.


RELATED LINKS

REMARKS
    To see the examples, type: "get-help Get-EventWmiOperationFailure -examples".
    For more information, type: "get-help Get-EventWmiOperationFailure -detailed".
    For technical information, type: "get-help Get-EventWmiOperationFailure -full".

We can query a remote host and look if there are events from others host by grouping by ClientMachine. In this example we can see there are 2 operation errors from SDCL1.

PS C:\> Get-EventWmiOperationFailure -ComputerName sddc01 | group clientmachine

Count Name                      Group
----- ----                      -----
  490 SDDC01                    {@{EventId=5858; Computer=SDDC01.acmelabs.pvt; EventRecordID=14335; TimeCreated=3/28/2022 2:54:11 PM; User=a...
    2 SDCL1                     {@{EventId=5858; Computer=SDDC01.acmelabs.pvt; EventRecordID=12823; TimeCreated=3/27/2022 8:49:06 PM; User=a...

We can query the host so only events that match the suspicious ClientMachine. We can see they miss-spelled the class.

PS C:\> Get-EventWmiOperationFailure -ComputerName sddc01 -ClientMachine sdcl1


EventId         : 5858
Computer        : SDDC01.acmelabs.pvt
EventRecordID   : 12823
TimeCreated     : 3/27/2022 8:49:06 PM
User            : acmelabs\Admin
ClientMachine   : SDCL1
ClientProcessId : 8868
Component       : Unknown
Operation       : Start IWbemServices::ExecQuery - root\cimv2 : select * from win32_proces
ResultCode      : 0x80041010
PossibleCause   : Unknown

EventId         : 5858
Computer        : SDDC01.acmelabs.pvt
EventRecordID   : 12822
TimeCreated     : 3/27/2022 8:48:29 PM
User            : acmelabs\Admin
ClientMachine   : SDCL1
ClientProcessId : 8868
Component       : Unknown
Operation       : Start IWbemServices::ExecQuery - root\cimv2 : select * from win32_proces
ResultCode      : 0x80041010
PossibleCause   : Unknown

We can group by Resultcode, Result code is the error number and we can use the error constant reference from Microsoft documentation to identify any of interest. https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-error-constants?redirectedfrom=MSDN

PS C:\> Get-EventWmiOperationFailure -ComputerName sddc01 | group -Property resultcode

Count Name                      Group
----- ----                      -----
  505 0x80041032                {@{EventId=5858; Computer=SDDC01.acmelabs.pvt; EventRecordID=14381; TimeCreated=3/28/2022 3:26:11 PM; User=a...
    2 0x80041010                {@{EventId=5858; Computer=SDDC01.acmelabs.pvt; EventRecordID=12823; TimeCreated=3/27/2022 8:49:06 PM; User=a...
`

WMI Provider Loading

WMI Providers for persistence is an old but not widely used technique where an attacker installs a WMI Provider on the system that when there is interaction with the Class or Classes it provides it loads, providing any capability coded into them, executing as SYSTEM. Casey Smith created and then removed the first public POC of the technique openly shared, followed by Jared Atkinson made a POC also public https://github.com/jaredcatkinson/EvilNetConnectionWMIProvider discussion of the technique has also been seen in some hacker forums but I’m not aware of its use in the wild.

When a provider is loaded and Event ID of 5857 in Microsoft-Windows-WMI-Activity/Operational will be created. The important field are the ProvierPath and the TimeCreated, since this can can be correlated to build a timeline of possible classes accessed since the provider will only be loaded when the wmiiprvse.exe needs the class to perform the requested action.

PsGumshoe provides the Get-EventWmiProviderStart function to query for this events.

PS C:\> Get-EventWmiProviderStart | group hostprocess

Count Name                      Group
----- ----                      -----
  874 wmiprvse.exe              {@{EventId=5857; Computer=SDCL1.acmelabs.pvt; EventRecordID=3406; TimeCreated=3/28/2022 10:22:19 AM; Provide...


PS C:\Users\Admin\Downloads\PSGumshoe-master\PSGumshoe-master> help Get-EventWmiProviderStart

NAME
    Get-EventWmiProviderStart

SYNOPSIS
    Get WMI Provider Start events (EventId 5857).


SYNTAX
    Get-EventWmiProviderStart [-LogName <String>] [-ProviderName <String[]>] [-ProviderPath <String[]>] [-MaxEvents <Int64>] [-StartTime
    <DateTime>] [-EndTime <DateTime>] [-ChangeLogic] [<CommonParameters>]

    Get-EventWmiProviderStart [-LogName <String>] [-ProviderName <String[]>] [-ProviderPath <String[]>] [-Path] <String[]> [-MaxEvents
    <Int64>] [-StartTime <DateTime>] [-EndTime <DateTime>] [-ChangeLogic] [<CommonParameters>]

    Get-EventWmiProviderStart [-LogName <String>] [-ProviderName <String[]>] [-ProviderPath <String[]>] -ComputerName <String[]> [-Credential
    <PSCredential>] [-MaxEvents <Int64>] [-StartTime <DateTime>] [-EndTime <DateTime>] [-ChangeLogic] [<CommonParameters>]


DESCRIPTION
    Get WMI Provider Start events (EventId 5857). from a local or remote host. Events can be filtered by fields.


RELATED LINKS

REMARKS
    To see the examples, type: "get-help Get-EventWmiProviderStart -examples".
    For more information, type: "get-help Get-EventWmiProviderStart -detailed".
    For technical information, type: "get-help Get-EventWmiProviderStart -full".

Here is an example to see how many times each provider has been loaded, this may help in identifying suspicious providers in use.

PS C:\> Get-EventWmiProviderStart | group providerpath | select count,name

Count Name
----- ----
  129 C:\Windows\System32\wbem\krnlprov.dll
  216 %systemroot%\system32\wbem\wmipcima.dll
  319 %systemroot%\system32\wbem\cimwin32.dll
   31 %SystemRoot%\System32\Win32_DeviceGuard.dll
   31 C:\Windows\System32\wbem\Win32_TPM.dll
   32 C:\Windows\System32\wbem\WmiPerfClass.dll
   31 %SystemRoot%\system32\tscfgwmi.dll
   41 %SystemRoot%\System32\sppwmi.dll
   34 %systemroot%\system32\wbem\wmiprov.dll
    2 %systemroot%\system32\wbem\stdprov.dll
    2 %systemroot%\system32\umpowmi.dll
    2 %SystemRoot%\System32\storagewmi.dll
    2 %systemroot%\system32\wbem\vdswmi.dll
    2 C:\Windows\System32\wbem\WmiPerfInst.dll

WMI Permanent Events

WMI permanent events have been abused since the Windows 2000/XP days when they were first introduced as a method of persistence or taking an action when some type of event happened on a system. For the purpose of this blogpost I will only cover what a Permanent Event Subscription is in general terms, enough to understand what is being logged.

Permanent events are made of 3 components:

  • Filter – WQL Query for the events we want.

  • Consumer – An action to take upon triggering the filter

  • Binding – Registers a Filter to a Consumer.

Each component is an instance of a class that is created and stored inside the CIM database (objects.data) under the root or root/subscription namespace. When the consumer is executed the action is run as SYSTEM under the context of wmiprvse.exe. They do take a bit more effort since we have to build each part individually and saved it in the CIM database, but most attackers simply automate the process. Its limitation is that only a small set of consumer actions are available.

  • ActiveScriptEventConsumer - Executes a predefined script in an arbitrary scripting language when an event is delivered to it. This consumer is available on Windows 2000 and above.

  • CommandLineEventConsumer - Launches an arbitrary process in the local system context when an event is delivered to it. This consumer is available on Windows XP and above.

  • LogFileEventConsumer - Writes customized strings to a text log file when events are delivered to it. This consumer is available on Windows XP and above.

  • NTEventLogEventConsumer - Logs a specific message to the Windows NT event log when an event is delivered to it. This consumer is available on Windows XP and above.

  • SMTPEventConsumer - Sends an email message using SMTP every time that an event is delivered to it. This consumer is available on Windows 2000 and above.

For the Event Filter object, a query is created that monitors for intrinsic or extrinsic events in the WMI CIM database, these can be the creation, modification, or deletion of any class instance or subscribing to a provider that will generate an event for certain actions.

When a __EventFilter, and any of the consumer type class objects are used in the creation of a Binder instance in the WMI CIM Database to create a permanent event a eventlog entry with Id 5861 in Microsoft-Windows-WMI-Activity/Operational is created by default without any auditing needing to be enabled. The event is also created in modification if any of the component class instances are modified. The event will contain all the information associated with the permanent even in the UserData element under the PossibleCause subelement.

If we are using Sysmon and it is configured to capture WMI events each component being created will be captured. Sysmon provides the advantage of targeting the change to a filter or consumer that is already bound together so as to blend into an environment. APT28 has been seen modifying already existing consumers and filters in a target environment. Bellow is a configuration that will capture all events.

<Sysmon schemaversion="4.81">
  <HashAlgorithms>sha1</HashAlgorithms>
  <CheckRevocation/>
  <EventFiltering>
      <!-- Capture all WMI actions. -->
    <RuleGroup name="WMI Actions" groupRelation="or">
        <WmiEvent onmatch="exclude">
          
        </WmiEvent>
    </RuleGroup>
  </EventFiltering>
</Sysmon>

In the following example we will create each component using PowerShell, the permanent event will detect when a USB device is plugged and copy to the device an executable and set a autorun.ini on the device.

We will start by creating an Event Filter instance that will trigger when a removable volume is added to the host.

##############################
# Filter for removable drive #
##############################

#Creating a new event filter
$RemovableDrvFilter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance()

# Set the properties of the instance
$RemovableDrvFilter.QueryLanguage = 'WQL'
$RemovableDrvFilter.Query = "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE
                             TargetInstance ISA 'Win32_Volume' AND
                             TargetInstance.DriveType=2"
$RemovableDrvFilter.Name = "USBDrvFilter"
$RemovableDrvFilter.EventNamespace = 'root\cimv2'

# Sets the intance in the namespace
$FilterResult = $RemovableDrvFilter.Put()
$USBFilterObj = $FilterResult.Path

We can see that the action was logged under the Sysmon log with Event ID 19, it contains all parts of the filter event and the action is Created.

PSGumshoe has the Get-SysmonWmiFilter that allows for querying for this event and returns the results as an object we can work with.

PS C:\> Get-SysmonWmiFilter -ComputerName sddc01


EventId        : 19
EventType      : WmiFilterEvent
Computer       : SDDC01.acmelabs.pvt
EventRecordID  : 273
RuleName       : -
UtcTime        : 2022-03-29 19:17:40.993
Operation      : Created
User           : acmelabs\Admin
EventNamespace :  "root\\cimv2"
Name           :  "USBDrvFilter"
Query          :  "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE\n                             TargetInstance ISA 'Win32_Volume' AND\n
                                            TargetInstance.DriveType=2"

An attacker as mentioned before, can modify an existing event filter and replace the query. Sysmon is able to track this change. Let’s Change the query so there are no newlines in it.

PS C:\> $filters = Get-WmiObject -Namespace root/subscription -Class __eventfilter
PS C:\> $filters | Set-WmiInstance -Arguments @{Query="SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Volume' AND TargetInstance.DriveType=2"}


__GENUS          : 2
__CLASS          : __EventFilter
__SUPERCLASS     : __IndicationRelated
__DYNASTY        : __SystemClass
__RELPATH        : __EventFilter.Name="SCM Event Log Filter"
__PROPERTY_COUNT : 6
__DERIVATION     : {__IndicationRelated, __SystemClass}
__SERVER         : SDDC01
__NAMESPACE      : ROOT\subscription
__PATH           : \\SDDC01\ROOT\subscription:__EventFilter.Name="SCM Event Log Filter"
CreatorSID       : {1, 2, 0, 0...}
EventAccess      :
EventNamespace   : root\cimv2
Name             : SCM Event Log Filter
Query            : SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Volume' AND
                   TargetInstance.DriveType=2
QueryLanguage    : WQL
PSComputerName   : SDDC01

__GENUS          : 2
__CLASS          : __EventFilter
__SUPERCLASS     : __IndicationRelated
__DYNASTY        : __SystemClass
__RELPATH        : __EventFilter.Name="USBDrvFilter"
__PROPERTY_COUNT : 6
__DERIVATION     : {__IndicationRelated, __SystemClass}
__SERVER         : SDDC01
__NAMESPACE      : ROOT\subscription
__PATH           : \\SDDC01\ROOT\subscription:__EventFilter.Name="USBDrvFilter"
CreatorSID       : {1, 5, 0, 0...}
EventAccess      :
EventNamespace   : root\cimv2
Name             : USBDrvFilter
Query            : SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Volume' AND
                   TargetInstance.DriveType=2
QueryLanguage    : WQL
PSComputerName   : SDDC01

As we can see Sysmon logged the change with the same ID but the operation stated that it was modified.

The functions in PSGumshoe allow for filtering by field, so we can query for only the modied events. We see that Sysmon creates 2 events one with the instance before the changes and another with how the instance now looks.

PS C:\> Get-SysmonWmiFilter -ComputerName sddc01 -Operation modified


EventId        : 19
EventType      : WmiFilterEvent
Computer       : SDDC01.acmelabs.pvt
EventRecordID  : 282
RuleName       : -
UtcTime        : 2022-03-29 20:00:55.044
Operation      : Modified
User           : acmelabs\Admin
EventNamespace :  "root\\cimv2"
Name           :  "USBDrvFilter"
Query          :  "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE\n                             TargetInstance ISA 'Win32_Volume' AND\n
                                            TargetInstance.DriveType=2"

EventId        : 19
EventType      : WmiFilterEvent
Computer       : SDDC01.acmelabs.pvt
EventRecordID  : 281
RuleName       : -
UtcTime        : 2022-03-29 20:00:55.044
Operation      : Modified
User           : BUILTIN\Administrators
EventNamespace :  "root\\cimv2"
Name           :  "SCM Event Log Filter"
Query          :  "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Volume' AND TargetInstance.DriveType=2"

Now we will create a Consumer instance, this consumer will be of type Action Script that will execute a VBScript Script that will Base64 decode a binary and store it on the removable drive, it will also create an autorun.ini and modify it so it is hidden on the drive.

$ScriptConsumer = ([wmiclass]"\\.\root\subscription:ActiveScriptEventConsumer").CreateInstance()
$ScriptConsumer.Name = 'AutoRun'
$ScriptConsumer.ScriptText = '
 Function Base64Decode(ByVal base64String)

    Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    Dim dataLength, sOut, groupBegin

    base64String = Replace(base64String, vbCrLf, "")
    base64String = Replace(base64String, vbTab, "")
    base64String = Replace(base64String, " ", "")


    dataLength = Len(base64String)
    If dataLength Mod 4 <> 0 Then
    Err.Raise 1, "Base64Decode", "Bad Base64 string."
    Exit Function
    End If


    For groupBegin = 1 To dataLength Step 4
    Dim numDataBytes, CharCounter, thisChar, thisData, nGroup, pOut

    numDataBytes = 3
    nGroup = 0
    For CharCounter = 0 To 3

        thisChar = Mid(base64String, groupBegin + CharCounter, 1)
        If thisChar = "=" Then
        numDataBytes = numDataBytes - 1
        thisData = 0
        Else
        thisData = InStr(1, Base64, thisChar, vbBinaryCompare) - 1
        End If
        If thisData = -1 Then
        Err.Raise 2, "Base64Decode", "Bad character In Base64 string."
        Exit Function
        End If
        nGroup = 64 * nGroup + thisData
    Next


    nGroup = Hex(nGroup)


    nGroup = String(6 - Len(nGroup), "0") & nGroup


    pOut = Chr(CByte("&H" & Mid(nGroup, 1, 2))) + _
        Chr(CByte("&H" & Mid(nGroup, 3, 2))) + _
        Chr(CByte("&H" & Mid(nGroup, 5, 2)))


    sOut = sOut & Left(pOut, numDataBytes)
    Next
    Base64Decode = sOut
End Function

Set objFSO=CreateObject("Scripting.FileSystemObject")

payContent = Base64Decode("SSdtIGEgZXZpbCBwYXlsb2Fk")
Set objPayload = objFSO.CreateTextFile("D:\payload.exe",True)
objPayload.Write payContent
objPayload.Close

outFile= TargetEvent.TargetInstance.DriveLetter & "\autorun.inf"
Set objFile = objFSO.CreateTextFile(outFile,True)
objFile.WriteLine("[AutoRun]")
payloadpath = "shellexecute=" & TargetEvent.TargetInstance.DriveLetter & "\payload.exe"
objFile.WriteLine(payloadpath)
objFile.WriteLine("UseAutoPlay=1")
objFile.Close
Set autoRunsFile = objFSO.GetFile(outFile)
autoRunsFile.attributes = 2

'

$ScriptConsumer.ScriptingEngine = 'VBScript'
$scriptResult = $ScriptConsumer.Put()
$scriptObj = $scriptResult.Path

This action will be logged by Sysmon with Event ID 20 and will contain the entire script from the ActioScript consumer.

We can query for the event with the function Get-SysmonWmiConsumer to query for the events. Sysmon only logs Action Script and CommandLine event consumers only, the other consumers are not logged since they are not considered a security risk.

PS C:\> Get-SysmonWmiConsumer -ComputerName sddc01


EventId       : 20
EventType     : WmiConsumerEvent
Computer      : SDDC01.acmelabs.pvt
EventRecordID : 289
RuleName      : -
UtcTime       : 2022-03-29 20:51:56.053
Operation     : Created
User          : acmelabs\Admin
Name          :  "AutoRun"
Type          : Script
Destination   :  "\n Function Base64Decode(ByVal base64String)\n\n    Const Base64 =
                \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n    Dim dataLength, sOut, groupBegin\n  \n
                base64String = Replace(base64String, vbCrLf, \"\")\n    base64String = Replace(base64String, vbTab, \"\")\n    base64String =
                Replace(base64String, \" \", \"\")\n  \n\n    dataLength = Len(base64String)\n    If dataLength Mod 4 <> 0 Then\n    Err.Raise
                1, \"Base64Decode\", \"Bad Base64 string.\"\n    Exit Function\n    End If\n  \n\n    For groupBegin = 1 To dataLength Step
                4\n    Dim numDataBytes, CharCounter, thisChar, thisData, nGroup, pOut\n\n    numDataBytes = 3\n    nGroup = 0\n    For
                CharCounter = 0 To 3\n\n        thisChar = Mid(base64String, groupBegin + CharCounter, 1)\n        If thisChar = \"=\" Then\n
                      numDataBytes = numDataBytes - 1\n        thisData = 0\n        Else\n        thisData = InStr(1, Base64, thisChar,
                vbBinaryCompare) - 1\n        End If\n        If thisData = -1 Then\n        Err.Raise 2, \"Base64Decode\", \"Bad character In
                Base64 string.\"\n        Exit Function\n        End If\n        nGroup = 64 * nGroup + thisData\n    Next\n    \n\n    nGroup
                = Hex(nGroup)\n    \n\n    nGroup = String(6 - Len(nGroup), \"0\") & nGroup\n    \n\n    pOut = Chr(CByte(\"&H\" & Mid(nGroup,
                1, 2))) + _\n        Chr(CByte(\"&H\" & Mid(nGroup, 3, 2))) + _\n        Chr(CByte(\"&H\" & Mid(nGroup, 5, 2)))\n    \n\n
                sOut = sOut & Left(pOut, numDataBytes)\n    Next\n    Base64Decode = sOut\nEnd Function\n\nSet
                objFSO=CreateObject(\"Scripting.FileSystemObject\")\n\npayContent = Base64Decode(\"SSdtIGEgZXZpbCBwYXlsb2Fk\")\nSet objPayload
                = objFSO.CreateTextFile(\"D:\\payload.exe\",True)\nobjPayload.Write payContent\nobjPayload.Close\n\noutFile=
                TargetEvent.TargetInstance.DriveLetter & \"\\autorun.inf\"\nSet objFile =
                objFSO.CreateTextFile(outFile,True)\nobjFile.WriteLine(\"[AutoRun]\")\npayloadpath = \"shellexecute=\" &
                TargetEvent.TargetInstance.DriveLetter &
                \"\\payload.exe\"\nobjFile.WriteLine(payloadpath)\nobjFile.WriteLine(\"UseAutoPlay=1\")\nobjFile.Close\nSet autoRunsFile =
                objFSO.GetFile(outFile)\nautoRunsFile.attributes = 2\n\n"

To make bound the filter with the consumer we need to create a Binder instance that references both together. It can be created in the Root namespace or in Root/Subscription. Let’s create first an instance in the Root/Subscription namespace that is the default one for this type of instance.

$instanceBinding = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance()

$instanceBinding.Filter = $USBFilterObj
$instanceBinding.Consumer = $scriptObj
$result = $instanceBinding.Put()
$newBinding = $result.Path

Windows will log all Binding events with the entire information for each part as part of its event under event ID 5861

We can query for this event with the Get-EventWmiPermanentEvent function.

PS C:\> Get-EventWmiPermanentEvent -Computername sddc01


EventId       : 5861
Computer      : SDDC01.acmelabs.pvt
EventRecordID : 14820
TimeCreated   : 3/29/2022 5:15:16 PM
Namespace     : //./root/subscription
Filter        : USBDrvFilter
Consumer      : ActiveScriptEventConsumer="AutoRun"
Binder        : Binding EventFilter:
                instance of __EventFilter
                {
                        CreatorSID = {1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 254, 164, 236, 147, 143, 77, 44, 231, 185, 59, 44, 120, 232, 3, 0, 0};
                        EventNamespace = "root\\cimv2";
                        Name = "USBDrvFilter";
                        Query = "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Volume' AND
                TargetInstance.DriveType=2";
                        QueryLanguage = "WQL";
                };
                Perm. Consumer:
                instance of ActiveScriptEventConsumer
                {
                        CreatorSID = {1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 254, 164, 236, 147, 143, 77, 44, 231, 185, 59, 44, 120, 232, 3, 0, 0};
                        Name = "AutoRun";
                        ScriptingEngine = "VBScript";
                        ScriptText = "\n Function Base64Decode(ByVal base64String)\n\n    Const Base64 =
                \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n    Dim dataLength, sOut, groupBegin\n  \n
                base64String = Replace(base64String, vbCrLf, \"\")\n    base64String = Replace(base64String, vbTab, \"\")\n    base64String =
                Replace(base64String, \" \", \"\")\n  \n\n    dataLength = Len(base64String)\n    If dataLength Mod 4 <> 0 Then\n    Err.Raise
                1, \"Base64Decode\", \"Bad Base64 string.\"\n    Exit Function\n    End If\n  \n\n    For groupBegin = 1 To dataLength Step
                4\n    Dim numDataBytes, CharCounter, thisChar, thisData, nGroup, pOut\n\n    numDataBytes = 3\n    nGroup = 0\n    For
                CharCounter = 0 To 3\n\n        thisChar = Mid(base64String, groupBegin + CharCounter, 1)\n        If thisChar = \"=\" Then\n
                      numDataBytes = numDataBytes - 1\n        thisData = 0\n        Else\n        thisData = InStr(1, Base64, thisChar,
                vbBinaryCompare) - 1\n        End If\n        If thisData = -1 Then\n        Err.Raise 2, \"Base64Decode\", \"Bad character In
                Base64 string.\"\n        Exit Function\n        End If\n        nGroup = 64 * nGroup + thisData\n    Next\n    \n\n    nGroup
                = Hex(nGroup)\n    \n\n    nGroup = String(6 - Len(nGroup), \"0\") & nGroup\n    \n\n    pOut = Chr(CByte(\"&H\" & Mid(nGroup,
                1, 2))) + _\n        Chr(CByte(\"&H\" & Mid(nGroup, 3, 2))) + _\n        Chr(CByte(\"&H\" & Mid(nGroup, 5, 2)))\n    \n\n
                sOut = sOut & Left(pOut, numDataBytes)\n    Next\n    Base64Decode = sOut\nEnd Function\n\nSet
                objFSO=CreateObject(\"Scripting.FileSystemObject\")\n\npayContent = Base64Decode(\"SSdtIGEgZXZpbCBwYXlsb2Fk\")\nSet objPayload
                = objFSO.CreateTextFile(\"D:\\payload.exe\",True)\nobjPayload.Write payContent\nobjPayload.Close\n\noutFile=
                TargetEvent.TargetInstance.DriveLetter & \"\\autorun.inf\"\nSet objFile =
                objFSO.CreateTextFile(outFile,True)\nobjFile.WriteLine(\"[AutoRun]\")\npayloadpath = \"shellexecute=\" &
                TargetEvent.TargetInstance.DriveLetter &
                \"\\payload.exe\"\nobjFile.WriteLine(payloadpath)\nobjFile.WriteLine(\"UseAutoPlay=1\")\nobjFile.Close\nSet autoRunsFile =
                objFSO.GetFile(outFile)\nautoRunsFile.attributes = 2\n\n";
                };

Sysmon will also log the event as event ID 21 but will simply reference the path of each component. We can query for the event with the Get-SysmonWmiBinding function.

Creating the binding event in Root will bypass both WMI-Activity and Sysmon, it is important to know this and in the case of suspicious processes under wmiprvse.exe to check also in the root path for possible permanent events that may be hidden.

PS C:\> Get-CimInstance -ClassName __FilterToConsumerBinding -Namespace root


Consumer                : ActiveScriptEventConsumer (Name = "AutoRun")
CreatorSID              : {1, 5, 0, 0...}
DeliverSynchronously    : False
DeliveryQoS             :
Filter                  : __EventFilter (Name = "USBDrvFilter")
MaintainSecurityContext : False
SlowDownProviders       : False
PSComputerName          :

Temporary Events

Some actors have moved to use temporary event consumers. They can be written in C++, .Net, WSH and PowerShell, they allow the use of WMI Event filters to trigger an action that is executed by the application itself. The biggest advantage is that we can leverage the capabilities of the language used to write the temporary event and not be limited to only Windows Scripting Host and Command-Line tools. We can track these events with event Id 5860. Once the application registers the Event Consumer the event is created. Sysmon does not offer detection of this type of event.

Here is an example in PowerShell of a temporary event consumer that simply writes the name of a process that has been launched.

# Query for new process events 
$queryCreate = "SELECT * FROM __InstanceCreationEvent WITHIN 5" + 
    "WHERE TargetInstance ISA 'Win32_Process'" 
# Create an Action
$CrateAction = {
    $name = $event.SourceEventArgs.NewEvent.TargetInstance.name
    write-host "Process $($name) was created."
}

# Register WMI event 
Register-WMIEvent -Query $queryCreate -Action $CrateAction

When the Event Consumer is registered with Register-WmiEvent we get the following event logged on the system.

We can use the function Get-EventWmiTemporaryEvent to query for this event. We will see that this type of event is used a lot inside of Windows.

PS C:\> Get-EventWmiTemporaryEvent -ComputerName sddc01


EventId       : 5860
Computer      : SDDC01.acmelabs.pvt
EventRecordID : 14823
TimeCreated   : 3/29/2022 5:49:28 PM
ClientMachine : SDDC01
Namespace     : root\cimv2
Query         : SELECT * FROM __InstanceCreationEvent WITHIN 5WHERE TargetInstance ISA 'Win32_Process'
ProcessId     : 3628
PossibleCause : Temporary

EventId       : 5860
Computer      : SDDC01.acmelabs.pvt
EventRecordID : 14787
TimeCreated   : 3/29/2022 12:51:09 AM
User          :
ClientMachine : SDDC01
Namespace     : ROOT\CIMV2
Query         : SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName = 'wsmprovhost.exe'
ProcessId     : 1380
PossibleCause : Temporary

EventId       : 5860
Computer      : SDDC01.acmelabs.pvt
EventRecordID : 14652
TimeCreated   : 3/28/2022 6:19:55 PM
User          :
ClientMachine : SDDC01
Namespace     : ROOT\Subscription
Query         : SELECT * FROM __InstanceOperationEvent WITHIN 5WHERE TargetInstance ISA '__EventConsumer' OR TargetInstance ISA
                '__EventFilter' OR TargetInstance ISA '__FilterToConsumerBinding'
ProcessId     : 5992
PossibleCause : Temporary

EventId       : 5860
Computer      : SDDC01.acmelabs.pvt
EventRecordID : 13379
TimeCreated   : 3/28/2022 3:31:38 AM
User          :
ClientMachine : SDDC01
Namespace     : ROOT\CIMV2
Query         : SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName = 'wsmprovhost.exe'
ProcessId     : 1380
PossibleCause : Temporary

we can pipe the objects in to Out-GridView to better visualize the output.

We can see that there is a query that looks for creation of instances for the classes that make up Permanent Evtn. We can see that the PID of the process is 5992, when we look at the process we see that the Sysmon service is running this subscription.

PS C:\> Get-Process -PID 5992

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    227      12     2964      10748       0.06   5992   0 Sysmon

Conclusion

PSGumshoe PowerShell module offers great flexibility when investigating WMI type events generated by Windows and from Sysmon. The module is available in the PowerShell gallery and on GitHub