WMI is Microsoft's implementation of WBEM and CIM developed and published by DMTF. WMI provides means of gathering and sending management information in an enterprise environment.
WMI is a com based service which provides various variations of access to system related information.
WMI introduces something called a "managed object." This is a component on the machine that is managed by WMI. A managed object is a logical or physical part of a computer, such as a hard disk drive, network adapter, database system, operating system, process, or service. Applications that retrieve info from these managed objects are called "consumers."
Consumers query managed objects from these things called "providers." A provider is either a user-mode COM DLL or a kernel driver which expose . The default WMI providers are called the "standard providers", meaning that we can create our own provider and register it with WMI. This WMI provider is contains an associated GUID which is registered in the registry.
More specifically WMI providers provide information about managed objects as instances of classes, consumers do not access these instances directly, consumers query WMI, and WMI then forwards that query to the provider. The provider then processes the query and sends info back to WMI, in which WMI sends it back to the consumer again.
These queries happen in WQL, which is basically SQL for WMI. If you have programmed in SQL before, you will notice that the syntax is very similar.
An example of a WQL query is this:
SELECT * FROM Win32_Process WHERE Name LIKE "%chrome%"
This query will return all running processes where the executable names is chrome.
The diagram below shows the relationship of the WMI infrastructure and WMI providers and managed objects.
Examples of WMI providers are the Registry provider:
Which helps you access data in the registry. This provider only has one WMI class:
which is StdRegProv. This class has many methods which we can use.
WMI Providers also contain a Managed Object Format file which defines the classes which the provider returns.
Classes are also categorized hierarchically into namespaces. All namespaces derive from the ROOT namespace and Microsoft uses ROOT\CIMV2 as the default namespace.
WMI settings are stored here:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WBEM
the WMI service is implemented as a service process within SVCHOST. This service interacts with consumers through the COM interface. Providers register their location with the WMI service, which allows WMI to route data requests.
WMI also uses ETW to record its own service activity.
For more information, please check out the Microsoft documentation:
For this lab(ish), we will just be using the standard providers to make any process of our choice spawn under wmiPRVSE.exe service.(in c/c++)
Implementation
Since we are dealing with COM, we must setup COM by calling CoInitializeEx. If you don't recall, COM is a a language agnostic library which allows application in different programming languages to interact with each other.
The steps to create a WMI application are to:
Initialize COM
Create a connection to a WMI namespace
Set security levels of the WMI connection
Use the COM interfaces(functionality)
cleanup and shutdown your application properly
Microsoft already has a code implementation on how to create a process using WMI, the final code is here(stolen from MS)
#define_WIN32_DCOM#include<iostream>usingnamespace std;#include<comdef.h>#include<Wbemidl.h>#pragmacomment(lib, "wbemuuid.lib")intmain(int iArgCnt,char** argv){ HRESULT hres; // Step 1: -------------------------------------------------- // Initialize COM. ------------------------------------------ hres =CoInitializeEx(0, COINIT_MULTITHREADED);if (FAILED(hres)) { cout <<"Failed to initialize COM library. Error code = 0x"<< hex << hres << endl;return1; // Program has failed. } // Step 2: -------------------------------------------------- // Set general COM security levels -------------------------- hres =CoInitializeSecurity(NULL,-1, // COM negotiates serviceNULL, // Authentication servicesNULL, // Reserved RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication RPC_C_IMP_LEVEL_IMPERSONATE, // Default ImpersonationNULL, // Authentication info EOAC_NONE, // Additional capabilities NULL // Reserved );if (FAILED(hres)) { cout <<"Failed to initialize security. Error code = 0x"<< hex << hres << endl;CoUninitialize();return1; // Program has failed. } // Step 3: --------------------------------------------------- // Obtain the initial locator to WMI ------------------------- IWbemLocator* pLoc =NULL; hres =CoCreateInstance( CLSID_WbemLocator,0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);if (FAILED(hres)) { cout <<"Failed to create IWbemLocator object. "<<"Err code = 0x"<< hex << hres << endl;CoUninitialize();return1; // Program has failed. } // Step 4: --------------------------------------------------- // Connect to WMI through the IWbemLocator::ConnectServer method IWbemServices* pSvc =NULL; // Connect to the local root\cimv2 namespace // and obtain pointer pSvc to make IWbemServices calls. hres =pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),NULL,NULL,0,NULL,0,0,&pSvc );if (FAILED(hres)) { cout <<"Could not connect. Error code = 0x"<< hex << hres << endl;pLoc->Release();CoUninitialize();return1; // Program has failed. } cout <<"Connected to ROOT\\CIMV2 WMI namespace"<< endl; // Step 5: -------------------------------------------------- // Set security levels for the proxy ------------------------ hres =CoSetProxyBlanket( pSvc, // Indicates the proxy to set RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx NULL, // Server principal name RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxxNULL, // client identity EOAC_NONE // proxy capabilities );if (FAILED(hres)) { cout <<"Could not set proxy blanket. Error code = 0x"<< hex << hres << endl;pSvc->Release();pLoc->Release();CoUninitialize();return1; // Program has failed. } // Step 6: -------------------------------------------------- // Use the IWbemServices pointer to make requests of WMI ---- // set up to call the Win32_Process::Create method BSTR MethodName =SysAllocString(L"Create"); BSTR ClassName =SysAllocString(L"Win32_Process"); IWbemClassObject* pClass =NULL; hres =pSvc->GetObject(ClassName,0,NULL,&pClass,NULL); IWbemClassObject* pInParamsDefinition =NULL; hres =pClass->GetMethod(MethodName,0,&pInParamsDefinition,NULL); IWbemClassObject* pClassInstance =NULL; hres =pInParamsDefinition->SpawnInstance(0,&pClassInstance); // Create the values for the in parameters VARIANT varCommand;varCommand.vt = VT_BSTR;varCommand.bstrVal =_bstr_t(L"notepad.exe"); // Store the value for the in parameters hres =pClassInstance->Put(L"CommandLine",0,&varCommand,0);wprintf(L"The command is: %s\n",V_BSTR(&varCommand)); // Execute Method IWbemClassObject* pOutParams =NULL; hres =pSvc->ExecMethod(ClassName, MethodName,0,NULL, pClassInstance,&pOutParams,NULL);if (FAILED(hres)) { cout <<"Could not execute method. Error code = 0x"<< hex << hres << endl;VariantClear(&varCommand);SysFreeString(ClassName);SysFreeString(MethodName);pClass->Release();pClassInstance->Release();pInParamsDefinition->Release();pOutParams->Release();pSvc->Release();pLoc->Release();CoUninitialize();return1; // Program has failed. } // To see what the method returned, // use the following code. The return value will // be in &varReturnValue VARIANT varReturnValue; hres =pOutParams->Get(_bstr_t(L"ReturnValue"),0,&varReturnValue,NULL,0); // Clean up //--------------------------VariantClear(&varCommand);VariantClear(&varReturnValue);SysFreeString(ClassName);SysFreeString(MethodName);pClass->Release();pClassInstance->Release();pInParamsDefinition->Release();pOutParams->Release();pLoc->Release();pSvc->Release();CoUninitialize();return0;}
This part of the code includes the libraries to compile correctly:
hres =CoInitializeEx(0, COINIT_MULTITHREADED);if (FAILED(hres)) { cout <<"Failed to initialize COM library. Error code = 0x"<< hex << hres << endl;return1; // Program has failed. }
We then setup the COM security levels with CoInitializeSecurity. here, RPC_C_AUTHN_LEVEL_DEFAULT and RPC_C_IMPL_LEVEL_IMPERSONATE are the only parameters to be set.
after COM and COM-security have been set up, the class:IWbemLocator is used to access the WMI service, we initialize the IWebLocator interface through a call to CoCreateInstance.
The first parameter specifies the CIM namespace the consumer connects to, like \\server\namespace... If the namespace is no the local machine, server can be omitted, like namespace\namespace1...
user and pass are in the second and third parameters, in the scenario if a local machine, they must be set to NULL or else they will fail
the fourth parameter if set to null, the current local will be used. If it is not NULL, this must be a valid BSTR
The fifth parameter if set to 0, results in ConnectServer returning only after the connection to the server is established, This could result in your program ceasing to respond indefinitely if the server is broken. The following list lists the other valid values for lSecurityFlags. Note that you can also set it to WBEM_FLAG_USE_MAX_WAIT, which guarantees the call to return within two minutes. This safeguards consumers against blocking indefinitely if the targeted machine is down
Our call looks like this:
IWbemServices* pSvc =NULL; // Connect to the local root\cimv2 namespace // and obtain pointer pSvc to make IWbemServices calls. hres =pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),NULL,NULL,0,NULL,0,0,&pSvc );if (FAILED(hres)) { cout <<"Could not connect. Error code = 0x"<< hex << hres << endl;pLoc->Release();CoUninitialize();return1; // Program has failed. } cout <<"Connected to ROOT\\CIMV2 WMI namespace"<< endl;
Once you have received a pointer to the IWbemServices proxy, you will then have to set the security settings on the proxy to access WMI. Here, we call it so that impersonation of the client occurs on the proxy.
hres =CoSetProxyBlanket( pSvc, // Indicates the proxy to set RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx NULL, // Server principal name RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxxNULL, // client identity EOAC_NONE // proxy capabilities );if (FAILED(hres)) { cout <<"Could not set proxy blanket. Error code = 0x"<< hex << hres << endl;pSvc->Release();pLoc->Release();CoUninitialize();return1; // Program has failed. }
After that, we can finally access the Process::create method which will create a process under the WMI service.
We will use the IWbemServices pointer to make requests to WMI, we will use IWbemServices::ExecMethod to call Win32_Process::Create.
If the provider supports any in or out parameters, then the values of the parameters must be given to the IWbemClassObject pointers. For in parameters, you must spawn an instance of the in parameter definitions, and set the values of these new instances.
Process::Create needs a CommandLine in parameter to execute. As seen in its prototype:
Here, we will create a IWbemClassPointer, spawn a new instance of Win32_Process::Create, and set the value of CommandLine to notepad.exe.
// Set up to call the Win32_Process::Create methodBSTR MethodName =SysAllocString(L"Create");BSTR ClassName =SysAllocString(L"Win32_Process");IWbemClassObject* pClass =NULL;hres =pSvc->GetObject(ClassName,0,NULL,&pClass,NULL); // Remember that pSvc is IWbemServices Pointer, we get the Win32Process ClassnameIWbemClassObject* pInParamsDefinition =NULL;hres =pClass->GetMethod(MethodName,0,&pInParamsDefinition,NULL); // We get the Create Method nameIWbemClassObject* pClassInstance =NULL;hres =pInParamsDefinition->SpawnInstance(0,&pClassInstance); // Spawn new instance of Win32_Process::Create// Create the values for the in-parametersVARIANT varCommand;varCommand.vt = VT_BSTR;varCommand.bstrVal =_bstr_t(L"notepad.exe");// Stores notepade in the CommandLine parameterhres =pClassInstance->Put(L"CommandLine",0,&varCommand,0);wprintf(L"The command is: %s\n",V_BSTR(&varCommand));
We will then handle the out-parameters, giving it to an IWbemClassObject pointer. We will use the GET method and store it in a VARIANT variable so we can display it back to the user
// Executes Win32_Process::create, and stores OutParams as a ClassObject pointerIWbemClassObject* pOutParams =NULL;hres =pSvc->ExecMethod(ClassName, MethodName,0,NULL, pClassInstance,&pOutParams,NULL);// store the result in a Variant variableVARIANT varReturnValue;hres =pOutParams->Get(_bstr_t(L"ReturnValue"),0,&varReturnValue,NULL,0);
We will then implement some error checking if anything fails, this simply prints out the error code and cleans and shuts down.
We release any open COM interfaces, free our allocated memory, call CoUninitialize, and exit our application:
if (FAILED(hres)) { cout <<"Could not execute method. Error code = 0x"<< hex << hres << endl;VariantClear(&varCommand);SysFreeString(ClassName);SysFreeString(MethodName);pClass->Release();pClassInstance->Release();pInParamsDefinition->Release();pOutParams->Release();pSvc->Release();pLoc->Release();CoUninitialize();return1; // Program has failed. }
Note that this is also what we do at the end of our code even if it works:
// Clean up //--------------------------VariantClear(&varCommand);VariantClear(&varReturnValue);SysFreeString(ClassName);SysFreeString(MethodName);pClass->Release();pClassInstance->Release();pInParamsDefinition->Release();pOutParams->Release();pLoc->Release();pSvc->Release();CoUninitialize();return0;
Observations
Let's compile the code above and see it in action!
We can see we spawned the WmiPrvSe service in Process Hacker like so:
And our notepad with WMiPrvSe.exe as its parent
We have successfuly spoofed notepad's parent to be the WMI service!
But there is a weird thing I found in which I don't have enough knowledge to explain why this happens.
After around one minute and thirty seconds, this popups up about notepad.exe
As we can see, it has a "Non-existent" parent.
ETW also has a WMI provider we can enable. We will use Event Viewer for this.
Lets enable tracing for the WMI-Activity, and see what kind of logs we get when we run our ppid spoofing executable:
Once enabled, lets run our executable.
...
If everything works, you should get a good amount of logs from one executable. I was too lazy to take screenshots of the logs, so I'll leave this as an "exercise" to the reader to test out.