Basics of Tracking WMI Activity
WMI (Windows Management Instrumentation) has been part of the Windows Operating System since since Windows 2000 when it was included in the OS. The technology has been of great value to system administrators by providing ways to pull all types of information, configure components and take action based on state of several components of the OS. Due to this flexibility it has been abused by attackers that saw its potential since it early inclusion in the OS.
As security practitioners it is one of the technologies on Microsoft Windows that is of great importance to master. Until recently there was little to now logging of the actions one could take using WMI. Blue Teams where left leveraging third party tools or coding their own solution to cover gaps, this allowed for many year the abuse of WMI by Red Teams simulating the very actions that attackers of all kind have used in their day to day operation. We will take a look at how Microsoft improved the logging of WMI actions.
The WMI Activity Provider
The WMI Activity eventlog provider in Windows until 2012 was mostly for logging debug and trace information for WMI when it was enabled. It was expanded with this release of Windows to have an Operational log that logged several actions. Lets take a look at the provider it self and what does it offer. For this we will use PowerShell and in it the Get-WinEvent cmdlet to get the information.
We start by getting the object representing the provider:
PS C:\> $WmiProv = Get-WinEvent -ListProvider "Microsoft-Windows-WMI-Activity" PS C:\> $WmiProv Name : Microsoft-Windows-WMI-Activity LogLinks : {Microsoft-Windows-WMI-Activity/Trace, Microsoft-Windows-WMI-Activity/Operational, Microsoft-Windows-WMI-Activity/Debug} Opcodes : {} Tasks : {}
PowerShell formats the output of this object so we need to use the Format-List parameter to see all properties and which have values set on them.
PS C:\> $WmiProv | Format-List -Property * ProviderName : Microsoft-Windows-WMI-Activity Name : Microsoft-Windows-WMI-Activity Id : 1418ef04-b0b4-4623-bf7e-d74ab47bbdaa MessageFilePath : C:\WINDOWS\system32\wbem\WinMgmtR.dll ResourceFilePath : C:\WINDOWS\system32\wbem\WinMgmtR.dll ParameterFilePath : HelpLink : https://go.microsoft.com/fwlink/events.asp?CoName=Microsoft Corporation&ProdName=Microsoft® Windows® Operating System&ProdVer=10.0.15063.0&FileName=WinMgmtR.dll&FileVer=10.0.15063.0 DisplayName : Microsoft-Windows-WMI-Activity LogLinks : {Microsoft-Windows-WMI-Activity/Trace, Microsoft-Windows-WMI-Activity/Operational, Microsoft-Windows-WMI-Activity/Debug} Levels : {win:Error, win:Informational} Opcodes : {} Keywords : {} Tasks : {} Events : {1, 2, 3, 11...}
Lets take a look at the LogLinks or where does the provider saves its events.
PS C:\> $WmiProv.LogLinks LogName IsImported DisplayName ------- ---------- ----------- Microsoft-Windows-WMI-Activity/Trace False Microsoft-Windows-WMI-Activity/Operational False Microsoft-Windows-WMI-Activity/Debug False
The one we are interested in is the Microsoft-Windows-WMI-Activity/Operational one. Now that we have identified the specific EventLog name for which we are interested in the events that will be saved there we can now take a look at the events the provider generates.
A event provider can generate from a few events to over a 100. So look at how many events the provider generates using the Measure-Object cmdlet
PS C:\> $WmiProv.Events | Measure-Object Count : 22 Average : Sum : Maximum : Minimum : Property :
We see that the provider generates 22 events. We take a look at how each object is composed using the Get-Member cmdlet.
PS C:\> $WmiProv.Events | Get-Member TypeName: System.Diagnostics.Eventing.Reader.EventMetadata Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() Description Property string Description {get;} Id Property long Id {get;} Keywords Property System.Collections.Generic.IEnumerable[System.Diagnostics.Eventing.Reader.EventKeyword] Keywords {get;} Level Property System.Diagnostics.Eventing.Reader.EventLevel Level {get;} LogLink Property System.Diagnostics.Eventing.Reader.EventLogLink LogLink {get;} Opcode Property System.Diagnostics.Eventing.Reader.EventOpcode Opcode {get;} Task Property System.Diagnostics.Eventing.Reader.EventTask Task {get;} Template Property string Template {get;} Version Property byte Version {get;}
We can see that each event has a LogLink property and the value is of type System.Diagnostics.Eventing.Reader.EventLogLink. The value is an object of it self. We can quickly take a peak in to the object to see what the values are and how they are formated.
PS C:\> $WmiProv.Events[0].LogLink LogName IsImported DisplayName ------- ---------- ----------- Microsoft-Windows-WMI-Activity/Trace False PS C:\> $WmiProv.Events[0].LogLink | gm TypeName: System.Diagnostics.Eventing.Reader.EventLogLink Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() DisplayName Property string DisplayName {get;} IsImported Property bool IsImported {get;} LogName Property string LogName {get;}
We can now filter for the events we want to take a look at.
PS C:\> $WmiProv.Events | Where-Object {$_.LogLink.LogName -eq "Microsoft-Windows-WMI-Activity/Operational"} Id : 5857 Version : 0 LogLink : System.Diagnostics.Eventing.Reader.EventLogLink Level : System.Diagnostics.Eventing.Reader.EventLevel Opcode : System.Diagnostics.Eventing.Reader.EventOpcode Task : System.Diagnostics.Eventing.Reader.EventTask Keywords : {} Template : <template xmlns="http://schemas.microsoft.com/win/2004/08/events"> <data name="ProviderName" inType="win:UnicodeString" outType="xs:string"/> <data name="Code" inType="win:HexInt32" outType="win:HexInt32"/> <data name="HostProcess" inType="win:UnicodeString" outType="xs:string"/> <data name="ProcessID" inType="win:UInt32" outType="xs:unsignedInt"/> <data name="ProviderPath" inType="win:UnicodeString" outType="xs:string"/> </template> Description : %1 provider started with result code %2. HostProcess = %3; ProcessID = %4; ProviderPath = %5 Id : 5858 Version : 0 LogLink : System.Diagnostics.Eventing.Reader.EventLogLink Level : System.Diagnostics.Eventing.Reader.EventLevel Opcode : System.Diagnostics.Eventing.Reader.EventOpcode Task : System.Diagnostics.Eventing.Reader.EventTask Keywords : {} Template : <template xmlns="http://schemas.microsoft.com/win/2004/08/events"> <data name="Id" inType="win:UnicodeString" outType="xs:string"/> <data name="ClientMachine" inType="win:UnicodeString" outType="xs:string"/> <data name="User" inType="win:UnicodeString" outType="xs:string"/> <data name="ClientProcessId" inType="win:UInt32" outType="xs:unsignedInt"/> <data name="Component" inType="win:UnicodeString" outType="xs:string"/> <data name="Operation" inType="win:UnicodeString" outType="xs:string"/> <data name="ResultCode" inType="win:HexInt32" outType="win:HexInt32"/> <data name="PossibleCause" inType="win:UnicodeString" outType="xs:string"/> </template> Description : Id = %1; ClientMachine = %2; User = %3; ClientProcessId = %4; Component = %5; Operation = %6; ResultCode = %7; PossibleCause = %8 Id : 5859 Version : 0 LogLink : System.Diagnostics.Eventing.Reader.EventLogLink Level : System.Diagnostics.Eventing.Reader.EventLevel Opcode : System.Diagnostics.Eventing.Reader.EventOpcode Task : System.Diagnostics.Eventing.Reader.EventTask Keywords : {} Template : <template xmlns="http://schemas.microsoft.com/win/2004/08/events"> <data name="NamespaceName" inType="win:UnicodeString" outType="xs:string"/> <data name="Query" inType="win:UnicodeString" outType="xs:string"/> <data name="User" inType="win:UnicodeString" outType="xs:string"/> <data name="processid" inType="win:UInt32" outType="xs:unsignedInt"/> <data name="providerName" inType="win:UnicodeString" outType="xs:string"/> <data name="queryid" inType="win:UInt32" outType="xs:unsignedInt"/> <data name="PossibleCause" inType="win:UnicodeString" outType="xs:string"/> </template> Description : Namespace = %1; NotificationQuery = %2; OwnerName = %3; HostProcessID = %4; Provider= %5, queryID = %6; PossibleCause = %7 Id : 5860 Version : 0 LogLink : System.Diagnostics.Eventing.Reader.EventLogLink Level : System.Diagnostics.Eventing.Reader.EventLevel Opcode : System.Diagnostics.Eventing.Reader.EventOpcode Task : System.Diagnostics.Eventing.Reader.EventTask Keywords : {} Template : <template xmlns="http://schemas.microsoft.com/win/2004/08/events"> <data name="NamespaceName" inType="win:UnicodeString" outType="xs:string"/> <data name="Query" inType="win:UnicodeString" outType="xs:string"/> <data name="User" inType="win:UnicodeString" outType="xs:string"/> <data name="processid" inType="win:UInt32" outType="xs:unsignedInt"/> <data name="MachineName" inType="win:UnicodeString" outType="xs:string"/> <data name="PossibleCause" inType="win:UnicodeString" outType="xs:string"/> </template> Description : Namespace = %1; NotificationQuery = %2; UserName = %3; ClientProcessID = %4, ClientMachine = %5; PossibleCause = %6 Id : 5861 Version : 0 LogLink : System.Diagnostics.Eventing.Reader.EventLogLink Level : System.Diagnostics.Eventing.Reader.EventLevel Opcode : System.Diagnostics.Eventing.Reader.EventOpcode Task : System.Diagnostics.Eventing.Reader.EventTask Keywords : {} Template : <template xmlns="http://schemas.microsoft.com/win/2004/08/events"> <data name="Namespace" inType="win:UnicodeString" outType="xs:string"/> <data name="ESS" inType="win:UnicodeString" outType="xs:string"/> <data name="CONSUMER" inType="win:UnicodeString" outType="xs:string"/> <data name="PossibleCause" inType="win:UnicodeString" outType="xs:string"/> </template> Description : Namespace = %1; Eventfilter = %2 (refer to its activate eventid:5859); Consumer = %3; PossibleCause = %4
We can now see the events that are specific for that eventlog. We can also see the amount of details we can get for each event, including the XML template for the message. This will be useful when we write XPath filters.
We can save them to a variable and pull the IDs for the events.
PS C:\> $WmiEvents = $WmiProv.Events | Where-Object {$_.LogLink.LogName -eq "Microsoft-Windows-WMI-Activity/Operational"} PS C:\> $WmiEvents | Select-Object -Property Id Id -- 5857 5858 5859 5860 5861
Provider Loading
Every time WMI is initialized it loads providers that will build the classes and provide access to the OS and System components that it exposes as classes and its instances. This provides executes under the context of SYSTEM in the user, in other words they execute with a very high privilege in Windows. Several actors and Red Teams leverage malicious providers as backdoor so as as to keep persistent access on systems. Many forensic and incident response team do not look for new providers being added on systems or for suspicious exiting ones. Some examples of malicious providers are:
- https://gist.github.com/subTee/c6bd1401504f9d4d52a0 SubTee Shellcode Execution WMI Class
- https://github.com/jaredcatkinson/EvilNetConnectionWMIProvider Jared Atkinson Evil WMI Provider Example
In the list of Event Id we saw this would be event 5857 and it's structure we have some very useful information that is of great help when hunting.
As we can see in its structure we have information on the ProcessID and Thread that loaded the provider, we can also see the name of the host process name and the path to the DLL loaded.
If we are using Windows Event Collector we can create an XML filter with known providers and filters those out so we only see new unseen providers . We can generate a quick list of the unique privider files with a bit of PowerShell
PS C:\> Get-WinEvent -FilterHashtable @{logname='Microsoft-Windows-WMI-Activity/Operational';Id=5857} | % {$_.properties[4].value} | select -unique %SystemRoot%\system32\wbem\wbemcons.dll %systemroot%\system32\wbem\wmipiprt.dll %systemroot%\system32\wbem\wmiprov.dll C:\Windows\System32\wbem\krnlprov.dll %systemroot%\system32\wbem\wmipcima.dll C:\Windows\System32\wbem\WmiPerfClass.dll %SystemRoot%\system32\tscfgwmi.dll %systemroot%\system32\wbem\cimwin32.dll %systemroot%\system32\wbem\vdswmi.dll %SystemRoot%\System32\sppwmi.dll %systemroot%\system32\wbem\WMIPICMP.dll %SystemRoot%\System32\Win32_DeviceGuard.dll %SYSTEMROOT%\system32\PowerWmiProvider.dll %SystemRoot%\System32\storagewmi.dll %systemroot%\system32\wbem\stdprov.dll %systemroot%\system32\profprov.dll C:\Windows\System32\wbem\WmiPerfInst.dll %systemroot%\system32\wbem\DMWmiBridgeProv.dll C:\Windows\SysWOW64\wbem\WmiPerfClass.dll
We can turn this in to a XML Filter that we can use either with Get-WinEvent while hunting or WEC for collection
<QueryList> <Query Id="0" Path="Microsoft-Windows-WMI-Activity/Operational"> <Select Path="Microsoft-Windows-WMI-Activity/Operational">*[System[(EventID=5857)]] </Select> <Suppress Path="Microsoft-Windows-WMI-Activity/Operational"> (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\wmiprov.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\wmipcima.dll']) or (*[UserData/*/ProviderPath='%SystemRoot%\System32\sppwmi.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\vdswmi.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\DMWmiBridgeProv.dll']) or (*[UserData/*/ProviderPath='C:\Windows\System32\wbem\WmiPerfClass.dll']) or (*[UserData/*/ProviderPath='C:\Windows\System32\wbem\krnlprov.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\wmipiprt.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\stdprov.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\profprov.dll']) or (*[UserData/*/ProviderPath='%SystemRoot%\System32\Win32_DeviceGuard.dll']) or (*[UserData/*/ProviderPath='%SystemRoot%\System32\smbwmiv2.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\cimwin32.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\wmiprvsd.dll']) or (*[UserData/*/ProviderPath='%SystemRoot%\system32\wbem\scrcons.exe']) or (*[UserData/*/ProviderPath='%SystemRoot%\system32\wbem\wbemcons.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\vsswmi.dll']) or (*[UserData/*/ProviderPath='%SystemRoot%\system32\tscfgwmi.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\ServerManager.DeploymentProvider.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\mgmtprovider.dll']) or (*[UserData/*/ProviderPath='%systemroot%\system32\wbem\ntevt.dll']) or (*[UserData/*/ProviderPath='%SYSTEMROOT%\System32\wbem\DnsServerPsProvider.dll']) </Suppress> <Suppress Path="Microsoft-Windows-WMI-Activity/Operational"> (*[UserData/*/ProviderPath='%windir%\system32\wbem\servercompprov.dll']) or (*[UserData/*/ProviderPath='C:\Windows\System32\wbem\WmiPerfInst.dll']) </Suppress> </Query> </QueryList>
WMI Query Errors
EventLog Id 5858 logs all query errors, the data include the error code in the element ResultCode and the Query that caused it under the element Operation. The Process Id is also included but in this event it is under the ClientProcessId element.
The error code is in hex format. Thankfully Microsoft has a list of WMI Error Constants in MSDN https://msdn.microsoft.com/en-us/library/aa394559(v=vs.85).aspx that we can use to figure what the specific error was. We can query easily for specific result codes using an XPathFilter. In the following exampled we are looking for queries that failed because the specified Class did not exist searching for ResultCode 0x80041010. This could useful if an attacker is looking for a specific class present on the systems. Like a permanent event consumer he can modify for persistence.
PS C:\> Get-WinEvent -FilterXPath "*[UserData/*/ResultCode='0x80041010']" -LogName "Microsoft-Windows-WMI-Activity/Operational"
We could also search for queries that failed due to insufficient permissions.
PS C:\> Get-WinEvent -FilterXPath "*[UserData/*/ResultCode='0x80041003']" -LogName "Microsoft-Windows-WMI-Activity/Operational"
Eventing
The way events operate by monitoring for specific events generated by the CIM Database in Windows. WMI Events are those events that happen when a specific Event Class instance is created or they are defined in the WMI Model. The way most event work is by the creation of a query that defines what we are looking for to happen, a action that will be taken once the event happens and both are registered together. There are 2 types of WMI Event Subscription:
- Temporary – Subscription is active as long as the process that created the subscription is active. (They run under the privilege of the process)
- Permanent – Subscription is stored in the CIM Database and are active until removed from it. (They always run as SYSTEM)
Temporary Events
One of the areas where some have moved to now that the detection of permanent WMI events is more common due to some tools is the use of temporary event consumers. This 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 it self. These are not common but easy to write and operate as long as the application is running. We can track this events with events with Id 5860. Once the application registers the Event Consumer the event is created.
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 see that the Query being used to monitor for events is logged under UserData under the element Query and under the element PlaussibleCause we see that it is marked as Temporary.
Permanent Events
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 is created. The event is also created in modification if any of the component class instances are modified. The event will contain all the information associated about the permanent even in the UserData element under the PossibleCause subelement.
When __EventFilter or Consumer that make up a permanent event is modified it generates the same event but there is no field in the data to show if it was modified or not.
The event says to look at Event Id 5859 for the __EventFilter class that makes up the permanent event but at the moment I have not seen a event created with this Id in all my testing.
Conclusion
As you can see Microsoft has improved quiet a bit the natural logging capabilities in the latest version of Windows. Sadly this has not been back ported to Windows 7 and Windows 2008/2008 R2. We are able to track:
- Query Errors.
- Temporary Event Creation
- Permanent Event Creation and Modification
- Loading of Providers.
From a Red Team perspective this lets us know that our actions can be tracked and we should look if this events are collected when we measure the level of maturity of the target. From a Blue Team perspective these are events you should be collecting and analyzing in your enviroment for possible malicious actions.