42

I know that to receive notifications about Win32 process creation or termination we might implement a NT kernel-mode driver using the APIs PsSetCreateProcessNotifyRoutine() that offers the ability to register system-wide callback function which is called by OS each time when a new process starts, exits or is terminated.

Is this possible without creating a NT kernel-mode driver, only using Win32 API functions using c++? Not using the basic solution of a infinite cycle querying the list of active process of course.

Is there any library or win32 API that provides the same functionality (system wide callback, asynchronous events)?

Mike G
  • 4,232
  • 9
  • 40
  • 66
Nuno
  • 1,910
  • 2
  • 21
  • 33

9 Answers9

30

WMI is great and it works with process names too. Although if you need to track process termination the more lightweight and easier way is the following:

VOID CALLBACK WaitOrTimerCallback(
    _In_  PVOID lpParameter,
    _In_  BOOLEAN TimerOrWaitFired
    )
{
    MessageBox(0, L"The process has exited.", L"INFO", MB_OK);
    return;
}

DWORD dwProcessID = 1234;
HANDLE hProcHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);

HANDLE hNewHandle;
RegisterWaitForSingleObject(&hNewHandle, hProcHandle , WaitOrTimerCallback, NULL, INFINITE, WT_EXECUTEONLYONCE);

This code will call WaitOrTimerCallback once the process terminated.

Anton K
  • 4,658
  • 2
  • 47
  • 60
14

The only thing I could think of is WMI, not sure if it provides a process creation callback, but it might be worth looking into.

Anders
  • 97,548
  • 12
  • 110
  • 164
  • 13
    Yes WMI can provide what i'm looking for (process creation/termination callback). If someone is interesting on how then take a look into http://msdn.microsoft.com/en-us/library/aa390425%28VS.85%29.aspx Thanks – Nuno Aug 24 '10 at 16:30
9

Anders is correct, WMI works nicely for this. Since I needed this for a project I can share the code for detecting (arbitrary) process termination (given its ID):

ProcessTerminationNotification.h:

#ifndef __ProcessTerminationNotification_h__
#define __ProcessTerminationNotification_h__

#include <boost/function.hpp>

namespace ProcessTerminationNotification
{
    typedef boost::function< void(void) > TNotificationFunction;

    void registerTerminationCallback(TNotificationFunction callback, unsigned processId);
}
#endif // __ProcessTerminationNotification_h__

ProcessTerminationNotification.cpp:

#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <comdef.h>
#include <Wbemidl.h>
#include <atlcomcli.h>

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

#include "ProcessTerminationNotification.h"

class EventSink : public IWbemObjectSink
{
    friend void ProcessTerminationNotification::registerTerminationCallback(TNotificationFunction callback, unsigned processId);

    CComPtr<IWbemServices> pSvc;
    CComPtr<IWbemObjectSink> pStubSink;

    LONG m_lRef;
    ProcessTerminationNotification::TNotificationFunction m_callback;

public:
    EventSink(ProcessTerminationNotification::TNotificationFunction callback)
        : m_lRef(0) 
        , m_callback(callback)
    {}
    ~EventSink()
    {}

    virtual ULONG STDMETHODCALLTYPE AddRef()
    {
        return InterlockedIncrement(&m_lRef);
    }
    virtual ULONG STDMETHODCALLTYPE Release()
    {
        LONG lRef = InterlockedDecrement(&m_lRef);
        if (lRef == 0)
            delete this;
        return lRef;
    }
    virtual HRESULT STDMETHODCALLTYPE 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;
    }

    virtual HRESULT STDMETHODCALLTYPE Indicate( 
        LONG lObjectCount,
        IWbemClassObject __RPC_FAR *__RPC_FAR *apObjArray
        )
    {
        m_callback();
        /* Unregister event sink since process is terminated */
        pSvc->CancelAsyncCall(pStubSink);
        return WBEM_S_NO_ERROR;
    }

    virtual HRESULT STDMETHODCALLTYPE SetStatus( 
        /* [in] */ LONG lFlags,
        /* [in] */ HRESULT hResult,
        /* [in] */ BSTR strParam,
        /* [in] */ IWbemClassObject __RPC_FAR *pObjParam
        )
    {
        return WBEM_S_NO_ERROR;
    } 

};


void ProcessTerminationNotification::registerTerminationCallback( TNotificationFunction callback, unsigned processId )
{
    CComPtr<IWbemLocator> pLoc;

    HRESULT 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;
        throw std::exception("ProcessTerminationNotificaiton initialization failed");
    }

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

    CComPtr<EventSink> pSink(new EventSink(callback));

    // 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, 
        &pSink->pSvc
        );

    if (FAILED(hres))
    {
        cout << "Could not connect. Error code = 0x" 
            << hex << hres << endl;
        throw std::exception("ProcessTerminationNotificaiton initialization failed");
    }

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

    hres = CoSetProxyBlanket(
        pSink->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 
        );

    if (FAILED(hres))
    {
        cout << "Could not set proxy blanket. Error code = 0x" 
            << hex << hres << endl;
        throw std::exception("ProcessTerminationNotificaiton initialization failed");
    }

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

    // Use an unsecured apartment for security
    CComPtr<IUnsecuredApartment> pUnsecApp;

    hres = CoCreateInstance(CLSID_UnsecuredApartment, NULL, 
        CLSCTX_LOCAL_SERVER, IID_IUnsecuredApartment, 
        (void**)&pUnsecApp);

    CComPtr<IUnknown> pStubUnk; 
    pUnsecApp->CreateObjectStub(pSink, &pStubUnk);

    pStubUnk->QueryInterface(IID_IWbemObjectSink,
        (void **) &pSink->pStubSink);

    // The ExecNotificationQueryAsync method will call
    // The EventQuery::Indicate method when an event occurs
    char buffer[512];
    sprintf_s(buffer, "SELECT * " 
        "FROM __InstanceDeletionEvent WITHIN 1 "
        "WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.ProcessId=%u", processId);

    hres = pSink->pSvc->ExecNotificationQueryAsync(
        _bstr_t("WQL"), 
        _bstr_t(buffer), 
        WBEM_FLAG_SEND_STATUS, 
        NULL, 
        pSink->pStubSink);

    // Check for errors.
    if (FAILED(hres))
    {
        cout << "ExecNotificationQueryAsync failed "
            "with = 0x" << hex << hres << endl;
        throw std::exception("ProcessTerminationNotificaiton initialization failed");
    }
}

Note that the code to initialize COM and COM process security (CoInitializeEx and CoInitializeSecurity) is omitted here since it should be done in the application initialization.

Use it with global functions or use boost::bind to connect to an arbitrary method, example of the latter:

class MyClass
{
public:
    void myProcessTerminationCallback() { cout << "Wohoo!!" << endl; }
};


ProcessTerminationNotification::registerTerminationCallback(
    boost::bind(&MyClass::myProcessTerminationCallback, <pointer to MyClass instance>),
    1234); // Process ID = 1234
Robert
  • 2,330
  • 29
  • 47
  • Does anyone have an idea how WMI implements the events internally? – Christian K. Feb 20 '14 at 18:01
  • 1
    According to the book "Windows Internals Part 1" there is a mechanism called "Event Tracing for Windows (ETW)" which is capable of tracing process creation and termination. In the end there is a significant drawback with the WMI solution as it is not providing the events in real-time (synchronously). As can be seen by looking at WMI WQL's WITHIN clause. – Christian K. Feb 20 '14 at 21:49
  • There is very few, if any, circumstances where the information is needed in real-time. It is in 99.999% of the cases enough to KNOW the process has died. – Robert Feb 21 '14 at 10:11
  • With C++0x you should be able to replace dependency on boost with STL. Or roll your own. This is left as an exercise ;) – Robert Jul 21 '14 at 07:58
  • 1
    I think the issue with this is that the `WITHIN` clause causes the query to not report processes which start and terminate within one second, i.e. very short-lived processes won't be reported. – Frerich Raabe Apr 22 '15 at 09:55
  • The query `'SELECT * FROM Win32_ProcessStartTrace'` seems to produce events even for short-lived processes. The downside seems to be that that query can only be run with elevated privileges. – alx9r Apr 08 '17 at 16:12
  • Why not simply use Win32_ProcessStopTrace ? – Mecanik Jan 19 '21 at 08:10
4

As already hinted by a previous comment, there's a drawback with using WMI to monitor process events as WMI is not providing events synchronously, .i.e. with a short delay.

The book "Windows Internals Part 1" is referring to a mechanism called "Event Tracing for Windows (ETW)" which is a low-level mechanism for operating system events.

The following blog post shows how ETW can be used within .Net to monitor processes: http://blogs.msdn.com/b/vancem/archive/2013/03/09/using-traceevent-to-mine-information-in-os-registered-etw-providers.aspx

Christian K.
  • 605
  • 8
  • 10
  • 4
    Unfortunately it turns out that ETW is also not providing synchronous / real-time events. ETW events are also subject to buffering. – Christian K. Feb 22 '14 at 18:58
  • 1
    I don't want to state the obvious but there is no "real time" with Windows which at any time schedules multiple threads, where your thread waiting for an event about the system creating a process, invariably consumes such event delayed, by strict definition of "delayed" -- it's not like Windows pauses the process creation and waits for your thread to return from a callback before continuing. So in that sense, whether you consume events from a buffer and with a delay or whether Windows say, posts a message to your thread queue, it's the same thing -- an async. event (message) queue. – Armen Michaeli Feb 24 '20 at 17:26
  • A new valid link to GC ETW blog post series: [https://devblogs.microsoft.com/dotnet/gc-etw-events-1/](https://devblogs.microsoft.com/dotnet/gc-etw-events-1/) – Vurdalakov Nov 04 '22 at 10:10
2

You can monitor all Window creating processes using SetWindowsHookEx with a CBTProc, however anything more than that requires either WMI, a windows driver or a little bit of 'Black Magic'

Necrolis
  • 25,836
  • 3
  • 63
  • 101
  • 2
    This only tracks processes with windows, but this is a good answer for a lot of scenarios. This also injects assemblies into said process. – justin.m.chase Aug 27 '15 at 21:07
  • 1
    CBT hooks cannot track process creation/termination. This is not an answer to the question. – IInspectable Jun 09 '17 at 15:19
  • @IInspectable: CBT hooks can track window processes; however the rest of my answer answers the question; CBT hooks are just simple in terms of window based processes. – Necrolis Jun 09 '17 at 19:52
  • 1
    No. CBT hooks can monitor window creation an destruction. There is no relation between window creation/destruction and process creation/termination. The question is asking about the latter. – IInspectable Jun 09 '17 at 20:13
  • @IInspectable: so then said person can use WMI or various other methods as I mention in my answer; it honestly just appears that just you cannot read. – Necrolis Jun 09 '17 at 21:08
  • 1
    A process can create a window, destroy it, sleep for a while, like a month, and then create a new window. How do you propose, that a CBT hook copes with this situation, when an application needs to be informed, that a process terminated? This is not an answer to the question that was asked. It really is that simple. – IInspectable Jun 09 '17 at 21:31
  • 1
    *"it honestly just appears that just you cannot read."* - I can, no worries. Like this: *"You can monitor all Window creating processes using SetWindowsHookEx with a CBTProc"* - that is wrong. You **cannot** monitor processes with a CBT hook. You can observe circumstantial evidence, at best. The rest of the answer is fuzzy. *"Anything more than that"* is useless, unless you explain what *"that"* is (which you failed numerous times already). Not much substantial information, and a link to an off-site resource. In other words: This answer is not useful. – IInspectable Jun 10 '17 at 12:51
  • @IInspectable: yes you can, you use it to inject into the processes creating the windows; more means processes that don't create windows. WMI is the broad catch-all (and what the accepted answer recommended, just btw.). And said off-site link is 100% spot on with the info it provides, but heavy-handed because it starts requiring you to work against the system in effect; I am not going to duplicate someones work when a simple link will do (and credit them in the process). Honestly, your comments are what is "not useful"... – Necrolis Jun 11 '17 at 17:31
  • 3
    You have been a member for more than 7 years. You should be aware of the rules. Links to off-site resources should be treated as *optional* information. If your answer does not contain enough information by itself, you might as well leave out the link altogether. Links to off-site resources do become inaccessible, meaning that your answer no longer is. – IInspectable Jun 11 '17 at 19:05
  • 1
    See [answer] (*"Provide context for links"*). My arguments are anything but weak. CBT hooks cannot monitor process creation and termination. If you did mean to rely on the (undocumented) implementation detail, that a global CBT hook gets injected into processes, and you could then handle `DLL_PROCESS_ATTACH`/`DLL_PROCESS_DETACH` callbacks, it is unclear why you preclude non-Window-creating processes. This answer misleading, at best, i.e. it is *"not useful"*. If you feel that you have been unfairly treated, take it to meta. – IInspectable Jun 12 '17 at 18:57
1

You can monitor process creation by hooking CreateProcessInternalW function. By hooking this function, you can even inject DLLs into the new process.

zwclose7
  • 19
  • 1
1

WMI queries can cost heavy CPU performance if not designed properly. If an intrinsic event from Win32_Process class is used to track process creation event, this impacts performance heavily. An alternate approach is to leverage Security Audit logs. You can enable Process Tracking using Local Security Policy or using a GPO in case of multiple machines. Once the process tracking starts, you can subscribe to security event logs with a custom XML query to monitor certain processes of your interest. The process creation event ID is 4688. `

<QueryList>
 <Query Id="0" Path="Security">
   <Select Path="Security">
       *[EventData[Data[@Name='NewProcessName'] ='C:\Windows\explorer.exe']]
       and
       *[System[(EventID=4688)]]
   </Select>
 </Query>
</QueryList>

`

Red John
  • 167
  • 10
0

Besides WMI, or if you need to prevent the process or thread from being started, or when you need synchronous notifications, you can use a kernel-mode driver approach. Our CallbackProcess product, for example, does exactly this.

Eugene Mayevski 'Callback
  • 45,135
  • 8
  • 71
  • 121
-2

API-hooking should be the right way to fullfill something like that. You can hook createProcess(A/W/asUserA/W.... etc) and NtTerminateProcess

christian
  • 391
  • 2
  • 10
  • 2
    That can't achieve system wide monitoring unless you do it in kernel and with PatchGuard that's not really possible. – Tal Jerome Sep 27 '17 at 12:38