9

I'm new to Windows API programming. I am aware that there are ways to check if a process is already running (via enumeration). However, I was wondering if there was a way to listen for when a process starts and ends (for example, notepad.exe) and then perform some action when the starting or ending of that process has been detected. I assume that one could run a continuous enumeration and check loop for every marginal unit of time, but I was wondering if there was a cleaner solution.

John Roberts
  • 5,885
  • 21
  • 70
  • 124

6 Answers6

10

Use WMI, Win32_ProcessStartTrace and Win32_ProcessStopTrace classes. Sample C# code is here.

You'll need to write the equivalent C++ code. Which, erm, isn't quite that compact. It's mostly boilerplate, the survival guide is available here.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
1

If you can run code in kernel, check Detecting Windows NT/2K process execution.

Sheng Jiang 蒋晟
  • 15,125
  • 2
  • 28
  • 46
1

Hans Passant has probably given you the best answer, but... It is slow and fairly heavy-weight to write in C or C++.

On versions of Windows less than or equal to Vista, you can get 95ish% coverage with a Windows WH_CBT hook, which can be set with SetWindowsHookEx.

There are a few problems:

  1. This misses some service starts/stops which you can mitigate by keeping a list of running procs and occasionally scanning the list for changes. You do not have to keep procs in this list that have explorer.exe as a parent/grandparent process. Christian Steiber's proc handle idea is good for managing the removal of procs from the table.

  2. It misses things executed directly by the kernel. This can be mitigated the same way as #1.

  3. There are misbehaved apps that do not follow the hook system rules which can cause your app to miss notifications. Again, this can be mitigated by keeping a process table.

The positives are it is pretty lightweight and easy to write.

For Windows 7 and up, look at SetWinEventHook. I have not written the code to cover Win7 so I have no comments.

JimR
  • 15,513
  • 2
  • 20
  • 26
  • But you need to write it in a dll and also it will inject your dll into all the processes, and now you need to have shared memory in your dll to communicate back to your original process. – KulaGGin Jul 23 '23 at 10:44
0

Process handles are actually objects that you can "Wait" for, with something like "WaitForMultipleObjects".

While it doesn't send a notification of some sort, you can do this as part of your event loop by using the MsgWaitForMultipleObjects() version of the call to combine it with your message processing.

Christian Stieber
  • 9,954
  • 24
  • 23
0
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
\Image File Execution Options

You can place a registry key here with your process name then add a REG_SZ named 'Debugger' and your listner application name to relay the process start notification.

Unfortunately there is no such zero-overhead aproach to recieving process exit that i know of.

Ujjwal Singh
  • 4,908
  • 4
  • 37
  • 54
0

Only for start of processes, can be easily extended for ending of processes, too.

Took the answer posted by KRUZ:

And made an actual event dispatcher out of it:

ProcessCreatedEventDispatcher.h:

#pragma once
#include <functional>
#include <vector>

#include <Wbemidl.h>
#include <wrl.h>

using namespace Microsoft::WRL;

class ProcessCreatedEventDispatcher : public IWbemObjectSink {

public:
    ProcessCreatedEventDispatcher();
    ~ProcessCreatedEventDispatcher();

    ULONG STDMETHODCALLTYPE AddRef() override;
    ULONG STDMETHODCALLTYPE Release() override;
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override;
    HRESULT STDMETHODCALLTYPE Indicate(LONG lObjectCount, IWbemClassObject __RPC_FAR* __RPC_FAR* apObjArray) override;
    HRESULT STDMETHODCALLTYPE SetStatus(LONG lFlags, HRESULT hResult, BSTR strParam, IWbemClassObject __RPC_FAR* pObjParam) override;

    using NewProcessCreatedListener = void(HANDLE ProcessHandle);
    std::vector<std::function<NewProcessCreatedListener>> NewProcessCreatedListeners{};
private:
    LONG m_lRef{};
    ComPtr<IWbemServices> pSvc{};
    ComPtr<IWbemLocator> pLoc{};
    ComPtr<IUnsecuredApartment> pUnsecApp{};
    ComPtr<IUnknown> pStubUnk{};
    ComPtr<IWbemObjectSink> pStubSink{};
};

ProcessCreatedEventDispatcher.cpp:

#include "ProcessCreatedEventDispatcher.h"

# pragma comment(lib, "wbemuuid.lib")

#include <iostream>
#include <functional>
#include <string>
#include <vector>

#define _WIN32_DCOM
#include <Windows.h>
#include <comdef.h>
#include <Wbemidl.h>
#include <wrl.h>

using namespace std;
using namespace Microsoft::WRL;

ProcessCreatedEventDispatcher::ProcessCreatedEventDispatcher() {
    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;
        return; // Program has failed.
    }

    // Step 2: --------------------------------------------------
    // Set general COM security levels --------------------------
    // Note: If you are using Windows 2000, you need to specify -
    // the default authentication credentials for a user by using
    // a SOLE_AUTHENTICATION_LIST structure in the pAuthList ----
    // parameter of CoInitializeSecurity ------------------------

    hres = CoInitializeSecurity(NULL,
        -1, // COM negotiates service
        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
    );


    if(FAILED(hres)) {
        cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
        CoUninitialize();
        return; // Program has failed.
    }

    // Step 3: ---------------------------------------------------
    // Obtain the initial locator to WMI -------------------------
    hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)pLoc.GetAddressOf());

    if(FAILED(hres)) {
        cout << "Failed to create IWbemLocator object. " << "Err code = 0x" << hex << hres << endl;
        CoUninitialize();
        return; // Program has failed.
    }

    // Step 4: ---------------------------------------------------
    // Connect to WMI through the IWbemLocator::ConnectServer method

    // 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();
        return; // Program has failed.
    }

    cout << "Connected to root\\CIMV2 WMI namespace" << endl;


    // Step 5: --------------------------------------------------
    // Set security levels on the proxy -------------------------

    hres = CoSetProxyBlanket(pSvc.Get(), // 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 
    );

    if(FAILED(hres)) {
        cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl;
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return; // Program has failed.
    }

    // Step 6: -------------------------------------------------
    // Receive event notifications -----------------------------

    // Use an unsecured apartment for security
    hres = CoCreateInstance(CLSID_UnsecuredApartment, NULL, CLSCTX_LOCAL_SERVER, IID_IUnsecuredApartment, (void**)&pUnsecApp);

    this->ProcessCreatedEventDispatcher::AddRef();

    pUnsecApp->CreateObjectStub(this, &pStubUnk);

    pStubUnk->QueryInterface(IID_IWbemObjectSink, &pStubSink);

    _bstr_t WQL = L"Select * From __InstanceCreationEvent Within 1 "
        L"Where TargetInstance ISA 'Win32_Process' ";

    // The ExecNotificationQueryAsync method will call
    // The EventQuery::Indicate method when an event occurs
    hres = pSvc->ExecNotificationQueryAsync(_bstr_t("WQL"), WQL, WBEM_FLAG_SEND_STATUS, NULL, pStubSink.Get());

    // Check for errors.
    if(FAILED(hres)) {
        printf("ExecNotificationQueryAsync failed with = 0x%X\n", hres);
        pSvc->Release();
        pLoc->Release();
        pUnsecApp->Release();
        pStubUnk->Release();
        this->ProcessCreatedEventDispatcher::Release();
        pStubSink->Release();
        CoUninitialize();
        return;
    }
}

ProcessCreatedEventDispatcher::~ProcessCreatedEventDispatcher() {
    auto Result = pSvc->CancelAsyncCall(pStubSink.Get());
    pSvc->Release();
    pLoc->Release();
    pUnsecApp->Release();
    pStubUnk->Release();
    this->ProcessCreatedEventDispatcher::Release();
    pStubSink->Release();
    CoUninitialize();
}

ULONG ProcessCreatedEventDispatcher::AddRef() {
    return InterlockedIncrement(&m_lRef);
}

ULONG ProcessCreatedEventDispatcher::Release() {
    LONG lRef = InterlockedDecrement(&m_lRef);
    if(lRef == 0)
        delete this;
    return lRef;
}

HRESULT ProcessCreatedEventDispatcher::QueryInterface(REFIID riid, void** ppv) {
    if(riid == IID_IUnknown || riid == IID_IWbemObjectSink) {
        *ppv = (IWbemObjectSink*)this;
        AddRef();
        return WBEM_S_NO_ERROR;
    }
    else return E_NOINTERFACE;
}


HRESULT ProcessCreatedEventDispatcher::Indicate(long lObjectCount, IWbemClassObject** apObjArray) {
    HRESULT hr = S_OK;
    _variant_t vtProp;

    std::string ProcessHandleString{};

    for(int i = 0; i < lObjectCount; i++) {
        hr = apObjArray[i]->Get(_bstr_t(L"TargetInstance"), 0, &vtProp, 0, 0);
        if(!FAILED(hr)) {
            ComPtr<IUnknown> str = static_cast<IUnknown*>(vtProp);
            hr = str->QueryInterface(IID_IWbemClassObject, reinterpret_cast<void**>(&apObjArray[i]));
            if(SUCCEEDED(hr)) {
                _variant_t cn;

                hr = apObjArray[i]->Get(L"Handle", 0, &cn, NULL, NULL);
                if(SUCCEEDED(hr)) {
                    if((cn.vt == VT_NULL) || (cn.vt == VT_EMPTY))
                        std::cout << "Handle : " << ((cn.vt == VT_NULL) ? "NULL" : "EMPTY") << endl;
                    else if((cn.vt & VT_ARRAY))
                        std::cout << "Handle : " << "Array types not supported (yet)" << endl;
                    else {
                        std::wstring WideProcessHandle = std::wstring(cn.bstrVal);
                        ProcessHandleString = std::string(WideProcessHandle.begin(), WideProcessHandle.end());

                        for(auto& NewProcessCreatedListener : NewProcessCreatedListeners) {
                            NewProcessCreatedListener(reinterpret_cast<void*>(std::stoul(ProcessHandleString, nullptr)));
                        }
                    }
                }
                VariantClear(&cn);
            }
        }
        VariantClear(&vtProp);
    }

    return WBEM_S_NO_ERROR;
}

HRESULT ProcessCreatedEventDispatcher::SetStatus(
    /* [in] */ LONG lFlags,
    /* [in] */ HRESULT hResult,
    /* [in] */ BSTR strParam,
    /* [in] */ IWbemClassObject __RPC_FAR* pObjParam
) {
    if(lFlags == WBEM_STATUS_COMPLETE) {
        printf("Call complete. hResult = 0x%X\n", hResult);
    }
    else if(lFlags == WBEM_STATUS_PROGRESS) {
        printf("Call in progress.\n");
    }

    return WBEM_S_NO_ERROR;
} // end of EventSink.cpp

Usage of this class is very easy:

#include <conio.h>
#include <iostream>

#include "ProcessCreatedEventDispatcher.h"

int main(int iArgCnt, char** argv) {
    ProcessCreatedEventDispatcher ProcessCreatedEventDispatcher{};
    ProcessCreatedEventDispatcher.NewProcessCreatedListeners.emplace_back([](auto ProcessHandle) {
        std::cout << "New Process Handle: " << std::hex << "0x" << ProcessHandle << std::endl;
        std::flush(std::cout);
    });

    // Wait for key press to exit the program
    std::cout << "Press any key to terminate" << std::endl;
    while(!_kbhit()) {}

    return 0; // Program successfully completed.  
}

Is it any good? I'd say yes: modular, RAII and basic observer design pattern.

KulaGGin
  • 943
  • 2
  • 12
  • 27