Pulpo
Pulpo

Reputation: 234

WMI Permanent Extrinsic Event registration successful but consumer is never triggered

This has had me pulling my hair out for two days.

I'm trying to familiarize myself with WMI events, so I wrote some code to create a permanent extrinsic event. The filter, consumer and binding are all registered in Wbemtest after running this code, but the consumer is never triggering.

The extrinsic event is registered for the provider RegValueChangeEvent, so our event consumer (in this case, it starts calc.exe in system32) should be triggered any time the registry key is changed.

Minimal working example (quite long, so the details are below the example):

#include <Windows.h>
#include <winternl.h>
#include <iostream>
#include <WbemCli.h>
#include <WbemIdl.h>
#include <comdef.h>

GUID CSLSID_WbemLocator = { 0x4590f811, 0x1d3a, 0x11d0, 0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 };
GUID SIID_IClassFactory = { 0x00000001, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
GUID SIID_IUnknown = { 0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
GUID SIID_IWbemLocator = { 0xdc12a687, 0x737f, 0x11cf, 0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 };

int main()
{
    // Initialize 
    HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED);
    IWbemLocator* pLoc;
    IWbemServices* pSvc;

    std::wstring sFilterPath = L"__EventFilter.Name=\"TestFilter\"";
    std::wstring sConsumerPath = L"CommandLineEventConsumer.Name=\"TestConsumer\"";
    
    if (FAILED(hres))
    {
        printf("Failed to initialize COM library. Error code = 0x%llx", (unsigned long long)hres);
        return FALSE;                  // Program has failed.
    }

    // Get the class factory for the WbemLocator object
    IClassFactory* pClassFactory = NULL;

    hres = CoGetClassObject(CSLSID_WbemLocator, CLSCTX_INPROC_SERVER, NULL, SIID_IClassFactory, (void**)&pClassFactory);

    if (FAILED(hres)) {
        printf("Failed to get class factory. Error code = 0x%llx", (unsigned long long)hres);
        CoUninitialize();
        return FALSE;               // Program has failed.
    }

    // Create an instance of the WbemLocator object
    IUnknown* pUnk = NULL;
    hres = pClassFactory->CreateInstance(NULL, SIID_IUnknown, (void**)&pUnk);
    if (FAILED(hres)) {
        printf("Failed to create instance of WbemLocator. Error code = 0x%llx", (unsigned long long)hres);
        pClassFactory->Release();
        CoUninitialize();
        return FALSE;                 // Program has failed.
    }

    hres = pUnk->QueryInterface(SIID_IWbemLocator, (void**)&pLoc);
    if (FAILED(hres)) {
        printf("Failed to get IWbemLocator interface. Error code = 0x%llx", (unsigned long long)hres);
        pUnk->Release();
        pClassFactory->Release();
        CoUninitialize();
        return FALSE;    // Program has failed.
    }

    if (pLoc == nullptr)
    {
        printf("Failed to get IWbemLocator interface. Error code = 0x%llx", (unsigned long long)hres);
        pUnk->Release();
        pClassFactory->Release();
        CoUninitialize();
        return FALSE;     // Program has failed.
    }

    pUnk->Release();
    pClassFactory->Release();
    
    // Set general COM security levels 
    hres = CoInitializeSecurity(
        NULL,
        -1,                          // COM authentication
        NULL,                        // Authentication services
        NULL,                        // Reserved
        RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication 
        RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation  
        NULL,                        // Authentication info
        EOAC_NONE,                   // Additional capabilities 
        NULL                         // Reserved
    );
    hres = pLoc->ConnectServer(
        _bstr_t(L"ROOT\\subscription"), // Object path of WMI namespace
        NULL,                    // User name. NULL = current user
        NULL,                    // User password. NULL = current
        0,                       // Locale. NULL indicates current
        NULL,                    // Security flags.
        0,                       // Authority (for example, Kerberos)
        0,                       // Context object 
        &pSvc                    // pointer to IWbemServices proxy
    );
    if (FAILED(hres))
    {
        printf("Could not connect. Error code = 0x%llx", (unsigned long long)hres);
        return FALSE;                // Program has failed.
    }

    // Set security levels 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_xxx
        NULL,                        // client identity
        EOAC_NONE                    // proxy capabilities 
    );

    IWbemClassObject* pFilterClass = NULL;
    IWbemClassObject* pConsumerClass = NULL;
    IWbemClassObject* pBindingClass = NULL;
    IWbemClassObject* pConsumer = NULL;
    IWbemClassObject* pFilter = NULL;
    IWbemClassObject* pBinding = NULL;

    // Get the filter class
    hres = pSvc->GetObject(_bstr_t(L"__EventFilter"), 0, NULL, &pFilterClass, NULL);

    // Set Language = WQL
    VARIANT vtProp7;
    VariantInit(&vtProp7);
    vtProp7.vt = VT_BSTR;
    vtProp7.bstrVal = SysAllocString(L"WQL");
    hres = pFilter->Put(_bstr_t(L"QueryLanguage"), 0, &vtProp7, 0);
    if (FAILED(hres))
    {
        printf("Failed to set QueryLanguage property. Error code = 0x%lx\n", hres);
    }

    // Set namespace = ROOT\DEFAULT
    VARIANT vtProp11;
    VariantInit(&vtProp11);
    vtProp11.vt = VT_BSTR;
    vtProp11.bstrVal = SysAllocString(L"ROOT\\DEFAULT");
    hres = pFilter->Put(_bstr_t(L"EventNamespace"), 0, &vtProp11, 0);
    if (FAILED(hres))
    {
        printf("Failed to set Namespace property. Error code = 0x%lx\n", hres);
    }

    // Get the consumer class
    hres = pSvc->GetObject(_bstr_t(L"CommandLineEventConsumer"), 0, NULL, &pConsumerClass, NULL);
    if (FAILED(hres))
    {
        printf("Failed to get CommandLineEventConsumer class object. Error code = 0x%lx\n", hres);
        return 0;
    }

    hres = pConsumerClass->SpawnInstance(0, &pConsumer);
    if (FAILED(hres))
    {
        printf("Failed to spawn CommandLineEventConsumer instance. Error code = 0x%lx\n", hres);
        return 0;
    }

    // Put name and commandline template to consumer
    VARIANT vtProp3;
    VariantInit(&vtProp3);
    vtProp3.vt = VT_BSTR;
    vtProp3.bstrVal = SysAllocString(sConsumerPath.c_str());
    hres = pConsumer->Put(_bstr_t(L"Name"), 0, &vtProp3, 0);
    if (FAILED(hres))
    {
        printf("Failed to set Name property on Consumer. Error code = 0x%lx\n", hres);
        return 0;
    }
    VariantClear(&vtProp3);

    VARIANT vtProp4;
    VariantInit(&vtProp4);
    vtProp4.vt = VT_BSTR;
    vtProp4.bstrVal = SysAllocString(L"calc.exe");
    hres = pConsumer->Put(_bstr_t(L"CommandLineTemplate"), 0, &vtProp4, 0);
    if (FAILED(hres))
    {
        printf("Failed to set CommandLineTemplate property on Consumer. Error code = 0x%lx\n", hres);
        return 0;
    }

    // Set interactive to true
    VARIANT vtPropInteractive;
    VariantInit(&vtPropInteractive);
    vtPropInteractive.vt = VT_BOOL;
    vtPropInteractive.boolVal = VARIANT_TRUE;
    hres = pConsumer->Put(_bstr_t(L"RunInteractively"), 0, &vtPropInteractive, 0);
    if (FAILED(hres))
    {
        printf("Failed to set Interactive property on Consumer. Error code = 0x%lx\n", hres);
        return 0;
    }

    // Get the binding class
    hres = pSvc->GetObject(_bstr_t(L"__FilterToConsumerBinding"), 0, NULL, &pBindingClass, NULL);
    if (FAILED(hres))
    {
        printf("Failed to get __FilterToConsumerBinding class object. Error code = 0x%lx\n", hres);
        return 0;
    }

    // Create the binding instance
    hres = pBindingClass->SpawnInstance(0, &pBinding);
    if (FAILED(hres))
    {
        printf("Failed to spawn __FilterToConsumerBinding instance. Error code = 0x%lx\n", hres);
        return 0;
    }

    // Set the filter and consumer on the binding
    hres = pBinding->SpawnInstance(0, &pBinding);
    if (FAILED(hres))
    {
        printf("Failed to spawn __FilterToConsumerBinding instance. Error code = 0x%lx", hres);
        return 0;
    }

    VARIANT vtProp5;
    VariantInit(&vtProp5);
    vtProp5.vt = VT_BSTR;
    vtProp5.bstrVal = SysAllocString(L"TestFilter");
    hres = pBinding->Put(_bstr_t(L"Filter"), 0, &vtProp5, 0);
    if (FAILED(hres))
    {
        printf("Failed to set Filter property. Error code = 0x%lx\n", hres);
        return 0;
    }

    VARIANT vtProp6;
    VariantInit(&vtProp6);
    vtProp6.vt = VT_BSTR;
    vtProp6.bstrVal = SysAllocString(L"TestConsumer");
    hres = pBinding->Put(_bstr_t(L"Consumer"), 0, &vtProp6, 0);
    if (FAILED(hres))
    {
        printf("Failed to set Consumer property. Error code = 0x%lx\n", hres);
        return 0;
    }

    // Set the filter, consumer, and filter to consumer binding in WMI
    hres = pSvc->PutInstance(pFilter, WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL);
    if (FAILED(hres))
    {
        printf("Failed to put filter instance. Error code = 0x%lx\n", hres);
        return 0;
    }

    hres = pSvc->PutInstance(pConsumer, WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL);
    if (FAILED(hres))
    {
        printf("Failed to put consumer instance. Error code = 0x%lx\n", hres);
        return 0;
    }

    hres = pSvc->PutInstance(pBinding, WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL);
    if (FAILED(hres))
    {
        printf("Failed to put binding instance. Error code = 0x%lx\n", hres);
        return 0;
    }

    printf("Successfully created filter, consumer, and binding.\n");

    return hres;
}

Here is the MOF output for the __EventFilter:

instance of __EventFilter
{
    CreatorSID = { [ Removed ] };
    EventNamespace = "ROOT\\DEFAULT";
    Name = "TestFilter";
    Query = "SELECT * FROM RegistryValueChangeEvent WHERE Hive='HKEY_USERS' AND KeyPath='.DEFAULT\\\\SOFTWARE\\\\MyKey' AND ValueName = 'Test'";
    QueryLanguage = "WQL";
};

After we run this code, our class instances for the consumer, filter and binding all appear in Wbemtest. So we at least know that the Put registrations are going through properly.

If we create a registry key HKEY_USERS\.DEFAULT\MyTest, and populate it with a test value named Test, we can copy/paste the fitler query into Wbemtest as a Notification Query and we get the correct output if we change the value. So our actual WQL query is correct.

Here is our event consumer MOF:

instance of CommandLineEventConsumer
{
    CommandLineTemplate = "calc.exe";
    CreatorSID = { [ Removed ] };
    Name = "TestConsumer";
};

Now we know from the Microsoft WMI documentation that CommandLineEventConsumer consumers just execute CreateProcess internally, and that the CommandLineTemplate parameter is equivalent to the commandLine parameter for CreateProcess. With no executable path specified, the behavior should be identical.

I have tried both fields though, and neither work.

ProcMon tells me that it isn't even trying to start calc.exe, so the issue is probably with how we've registered the event, consumer and binding. We know that they get registered, so we must either be formatting something incorrectly, missing a parameter, or we're registering the incorrect class entirely.

Finally, our filter to consumer binding, as it appears in Wbemtest:

instance of __FilterToConsumerBinding
{
    Consumer = "CommandLineEventConsumer.Name=\"TestConsumer\"";
    CreatorSID = { [ Removed ] };
    Filter = "__EventFilter.Name=\"TestFilter\"";
};

I don't know how this could be wrong.

That's as far as I've been able to get in tracking down the issue. I've been stuck on this for two days so any help or advice is appreciated, incomplete or otherwise.

Upvotes: 0

Views: 188

Answers (1)

Pulpo
Pulpo

Reputation: 234

The issue was in the __EventFilter class instance.

EventNamespace requires a forwardslash instead of an escaped backslash, unlike every other WMI parameter. Changing the namespace from ROOT\\DEFAULT to ROOT/DEFAULT solved the issue.

Upvotes: 0

Related Questions