Click here to Skip to main content
Click here to Skip to main content

Detecting Windows NT/2K process execution

By , 25 Mar 2002
Rate this:
Please Sign up or sign in to vote.
<!-- Download Links -->

Sample Image

Abstract

Intercepting and tracing process execution is a very useful mechanism for implementing NT Task Manager-like applications and systems that require manipulations of external processes. Notifying interested parties upon starting of a new processes is a classic problem of developing process monitoring systems and system-wide hooks. Win32 API provides set of great libraries (PSAPI and ToolHelp [1]) that allow you to enumerate processes currently running in the system. Although these APIs are extremely powerful they don't permit you to get notifications when a new process starts or ends up. This article provides an efficient and robust technique based on a documented interface for achieving this goal.

Solution

Luckily, NT/2K provides a set of APIs, known as "Process Structure Routines" [2] exported by NTOSKRNL. One of these APIs PsSetCreateProcessNotifyRoutine() 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. The mentioned API can be employed as an easy to implement method for tracking down processes simply by implementing a NT kernel-mode driver and a user mode Win32 control application. The role of the driver is to detect process execution and notifiy the control program about these events.

Requirements

  • Provide a simple, efficient, reliable and thread-safe mechanism for monitoring process execution
  • Resolve synchronization issues between the driver and the user mode application
  • Build an easy to use and extend OOP user-mode framework
  • Allow registering and un-registering of the callback as well as ability to dynamically load and unload the kernel driver

How it works

The control application register the kernel mode driver under HKLM\SYSTEM\CurrentControlSet\Services and dynamically loads it. The kernel driver then creates a named event object that is used to signal the user-mode application when new event has been fired (i.e. process starts or ends up). The control application opens the same event object and creates a listening thread that waits on this event. Next, the user mode application sends a request to the driver to start monitoring. The driver invokes PsSetCreateProcessNotifyRoutine(), which accepts two parameters. One of them specifies the entry point of a caller-supplied callback routine, responsible for receiving all notifications from Windows. Upon a notification, that takes place in the callback, the driver signals that event in order to inform the user-mode application that something has happened. The control application then gets the data for that particular event from the driver and stores it in a special queue container for further processing. If there is no need for detecting process execution anymore the user mode application sends a request to the driver to stop monitoring. The driver then deactivates the observing mechanism. Later the control mode application can unload the driver and un-register it.

Design and implementation

NT Kernel mode driver (ProcObsrv)

The entry point DriverEntry() (ProcObsrv.c) performs the driver's initialization only. The I/O manager calls this function when the driver is loaded. Since PsSetCreateProcessNotifyRoutine() allows to un-register the callback I implemented the actual process of registration and un-registration in the driver's dispatch routine. This allows me dynamically to start and stop the monitoring activities by using a single IOCTL (control code IOCTL_PROCOBSRV_ACTIVATE_MONITORING). Once the callback is registered each time when a process starts or terminates the OS calls user supplied ProcessCallback(). This function populates a buffer that will be picked up by the user mode application. Next the driver signals the named event object, thus the user-mode application that waits on it will be informed that there is available information to be retrieved.

Control application (ConsCtl)

For the sake of simplicity I decided to provide a simple console application, leaving the implementation of the fancy GUI stuff to you. Designing of an application to be multithreaded allows that application to scale and be more responsive. On the other hand, it is very important to take into account several considerations related to synchronizing the access to information provided by the publisher (i.e. kernel driver) and retrieved by the subscriber (i.e. control application). The other important key point is that a detecting system must be reliable, and makes sure that no events are missed out. To simplify the design process, first I needed to assign the responsibilities between different entities in the user mode application, responsible for handling the driver. However it isn't difficult to do it by answering these questions [5]:

  1. What are the processes in the system
  2. What are the roles in the framework
  3. Who does what and how do they collaborate

Follows UML class diagram, that illustrates the relations between classes:

CApplicationScope implements a singleton and wraps up the main interface to the framework. It exposes two public methods that start and stop the monitoring process.

class CApplicationScope  
{
    .. Other Other details ignored for the sake of simplicity  ....
public:
    // Initiates process of monitoring process 
    BOOL StartMonitoring(PVOID pvParam);
    // Ends up the whole process of monitoring
    void StopMonitoring();
};

CProcessThreadMonitor is the thread that waits on the created by the driver event to be signaled. As soon as a process has been created or ended up, the driver signals this event object and CProcessThreadMonitor's thread wakes up. Then the user mode application retrieves the data from the driver. Next, the data is appended to queue container (CQueueContainer) using its method Append().

CQueueContainer is a thread-safe queue controller that offers an implementation of the Monitor/Condition variable pattern. The main purpose of this class is to provide a thread-safe semaphore realization of a queue container. This is how the method Append() works:

  1. Lock access to the aggregated STL deque object
  2. Add the data item
  3. Signal m_evtElementAvailable event object
  4. Unlock the deque

And here is its actual implementation:

// Insert data into the queue
BOOL CQueueContainer::Append(const QUEUED_ITEM& element)
{
    BOOL bResult = FALSE;
    DWORD dw = ::WaitForSingleObject(m_mtxMonitor, INFINITE);
    bResult = (WAIT_OBJECT_0 == dw);
    if (bResult)
    {
        // Add it to the STL queue
        m_Queue.push_back(element);
        // Notify the waiting thread that there is 
        // available element in the queue for processing
        ::SetEvent(m_evtElementAvailable);
    }//
    ::ReleaseMutex(m_mtxMonitor);
    return bResult;
}

Since it is designed to notify when there is an element available in the queue, it aggregates an instance of CRetreivalThread, which waits until an element becomes available in the local storage. This is its pseudo implementation:

  1. Wait on m_evtElementAvailable event object
  2. Lock access to the STL deque object
  3. Extract the data item
  4. Unlock the deque
  5. Process the data that has been retrieved from the queue

Here is the method invoked when something has been added to the queue:

// Implement specific behavior when kernel mode driver notifies 
// the user-mode app void
CQueueContainer::DoOnProcessCreatedTerminated() {
    QUEUED_ITEM element; // Initially we have at least one element for processing 
    BOOL    bRemoveFromQueue = TRUE; 
    while    (bRemoveFromQueue) {
        DWORD dwResult = ::WaitForSingleObject( m_mtxMonitor, INFINITE );
        if (WAIT_OBJECT_0 == dwResult) {

            // Is there anything in the queue 
            bRemoveFromQueue = (m_Queue.size() > 0); 
            if (bRemoveFromQueue) {
                // Get the element from the queue 
                element    = m_Queue.front(); m_Queue.pop_front();    
            } // if 
            else
                // Let's make sure that the event hasn't been 
                // left in signaled state if there are no items 
                // in the queue 
                ::ResetEvent(m_evtElementAvailable);
            }
            // if ::ReleaseMutex(m_mtxMonitor);
            // Process it only if there is an element that has 
            // been picked up
            if (bRemoveFromQueue)    
                m_pHandler->OnProcessEvent( &element, m_pvParam );
        else
            break;
    } // while
}

CCustomThread - To help manage the complexity of maintaining raw threads I encapsulated all thread's related activities in an abstract class. It provides a pure virtual method Run(), that must be implemented by any specific thread class (e.g. CRetrievalThread and CProcessThreadMonitor). CCustomThread is designed to ensure that thread function returns when you want the thread to terminate as the only way to make sure that all thread's resources are cleaned up properly. It offers a means to shut any of its instances down by signaling a named event m_hShutdownEvent.

CCallbackHandler is an abstract class that has been designed to provide interface for performing user-supplied actions when process is created or terminated. It exposes a pure virtual method OnProcessEvent(), which must be implemented according to the specific requirements of the system. In the sample code you will see a class CMyCallbackHandler, that inherits from CCallbackHandler and implements OnProcessEvent() method. One of the parameters pvParam of OnProcessEvent() method allows you to pass any kind of data, that's why it is declared as PVOID. In the sample code a pointer to an instance of CWhatheverYouWantToHold is passed to the OnProcessEvent(). You might want to use this parameter to pass just a handle to a window, that could be used for sending a message to it within OnProcessEvent() implementation.

class CCallbackHandler  
{
public:
    CCallbackHandler();
    virtual ~CCallbackHandler();
    // Define an abstract interface for receiving notifications
    virtual void OnProcessEvent(
        PQUEUED_ITEM pQueuedItem, 
        PVOID        pvParam
        ) = 0;
};

Compiling the sample code

You need to have installed MS Platform SDK on your machine. Provided sample code of the user-mode application can be compiled for ANSI or UNICODE. In case you would like to compile the driver you have to install Windows DDK as well.

Running the sample

However, it is not a problem if you don't have Windows DDK installed, since the sample code contains a compiled debug version of ProcObsrv.sys kernel driver as well as it source code. Just place control program along with the driver in single directory and let it run.

For demonstration purposes, the user mode application dynamically installs the driver and initiates process of monitoring. Next, you will see 10 instances of notepad.exe launched and later on closed. Meanwhile you can peek at the console window and see how the process monitor works. If you want you can start some program and see how the console will display its process ID along with its name.

Conclusion

This article demonstrated how you can employ a documented interface for detecting NT/2K process execution. However it is by far not the only one solution to this issue and certainly might miss some details, but I hope you would find it helpful for some real scenarios.

References:

  1. Single interface for enumerating processes and modules under NT and Win9x/2K, Ivo Ivanov
  2. Windows DDK Documentation, Process Structure Routines
  3. Nerditorium, Jim Finnegan, MSJ January 1999
  4. Windows NT Device Driver Development, Peter G. Viscarola and W. Anthony Mason
  5. Applying UML and Patterns, Craig Larman
  6. Using predicate waits with Win32 threads, D. Howard, C/C++ Users Journal, May 2000

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Ivo Ivanov
Chief Technology Officer
Australia Australia
I specialise in Malware Analysis, Application Security, Windows Kernel, x86/x64, and Reverse Engineering.
My blog: http://vinsula.com/security-blog
http://vinsula.com/
http://www.infoprocess.com.au
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralQuestion.. PinmemberNehaMishra286845-Oct-10 21:18 
GeneralRe: Question.. PinmemberIvo Ivanov29-Jul-11 22:54 
Generalunable to run the exe Pinmembersrshilpa10-Aug-09 22:03 
GeneralVista Compatible PinmemberIdentity Undisclosed3-Jun-09 20:02 
QuestionIs it applicable on Windows 9x,XP??? PinmemberKrazzze's14-May-09 9:46 
AnswerRe: Is it applicable on Windows 9x,XP??? PinmemberIvo Ivanov14-May-09 21:46 
Questiondon't it proper for WIN2003 with sp2...?? Pinmemberryancs18-Sep-08 23:54 
AnswerRe: don't it proper for WIN2003 with sp2...?? PinmemberIvo Ivanov21-Sep-08 16:45 
See this one:
 
http://www.codeproject.com/KB/threads/procmon.aspx?fid=3523&select=2296613&fr=1#xx2296613xx[^]
 
Ivo Ivanov, Security Researcher
Author of "API Hooking Revealed" http://www.codeproject.com/system/hooksys.asp
AntiHook 3.0, HookTool.NET SDK v3.6: http://www.infoprocess.com.au

QuestionUsing this method as non-administrator account PinmemberGaro1-Jun-08 21:20 
AnswerRe: Using this method as non-administrator account PinmemberIvo Ivanov1-Jun-08 21:35 
QuestionHow to build this Project as DLL? PinmemberMarvin Ferber20-Apr-08 7:56 
AnswerRe: How to build this Project as DLL? PinmemberBlake Miller29-Jul-11 10:30 
GeneralRe: How to build this Project as DLL? PinmemberIvo Ivanov29-Jul-11 22:46 
GeneralThere is a much easier way of doing this PinmemberJan Stetka14-Jan-08 8:50 
GeneralRe: There is a much easier way of doing this PinmemberIvo Ivanov14-Jan-08 12:28 
GeneralRe: There is a much easier way of doing this PinmemberJan Stetka10-Apr-08 23:22 
GeneralOpenEvent() on xp return NULL Pinmemberliy7716120-Dec-07 22:50 
GeneralOpenEvent() on xp return NULL Pinmemberliy7716120-Dec-07 22:48 
QuestionOpenEvent() on vista return NULL Pinmemberflyonok16-Oct-07 0:45 
AnswerRe: OpenEvent() on vista return NULL PinmemberByeBye12330-Oct-07 20:14 
General.NET Version PinmemberReMuZe3-Oct-07 5:40 
QuestionThread exception Pinmembersatish_abiram19-Aug-07 23:24 
GeneralCPU utilizing Pinmembersatish_abiram13-Aug-07 0:54 
GeneralRe: CPU utilizing PinmemberIvo Ivanov14-Aug-07 11:36 
Generalexe compile errors fixing [modified] PinmemberAlex_I25-Jun-07 23:45 
GeneralI'm able to run Consctl.c but it is using 100% cpu PinmemberChandrashekhar.M31-Jan-07 23:59 
GeneralRe: I'm able to run Consctl.c but it is using 100% cpu PinmemberLepon2k23-Apr-07 20:51 
Generalunable to compile ProcObsrv.c PinmemberChandrashekhar.M31-Jan-07 22:20 
GeneralRe: unable to compile ProcObsrv.c PinmemberIvo Ivanov14-Aug-07 11:37 
GeneralConsCtl does not compile PinmemberDave Midgley18-Sep-06 23:42 
GeneralRe: ConsCtl does not compile PinmemberDave Midgley20-Sep-06 4:39 
Generalhelp please Pinmemberrabah170720-Apr-06 8:13 
Generalproblem with driver Pinmembereuacela27-Mar-06 2:37 
GeneralRe: problem with driver Pinmemberbbcca27-Mar-06 12:53 
GeneralRe: problem with driver Pinmemberlijf97116-Apr-07 0:00 
GeneralRe: problem with driver Pinmembercoremsg17-Oct-07 20:42 
GeneralComplie Errors in ConsCtl,But build successful the ProcObsrv.c ,Why,Please help me,thank you! Pinmemberbbcca26-Mar-06 14:27 
GeneralRe: Complie Errors in ConsCtl,But build successful the ProcObsrv.c ,Why,Please help me,thank you! PinmemberDave Midgley20-Sep-06 4:39 
GeneralDetect process attempt to start PinmemberJuliano27-Feb-06 0:19 
GeneralRe: Detect process attempt to start PinmemberIvo Ivanov27-Feb-06 10:14 
GeneralCPU overload: CCustomThread Pinmemberpphilippot30-Jan-06 23:21 
GeneralRe: CPU overload: CCustomThread Pinmemberpphilippot31-Jan-06 0:11 
GeneralRe: CPU overload: CCustomThread Pinmemberpphilippot6-Feb-06 1:34 
GeneralRe: CPU overload: CCustomThread Pinmembereaster_200720-Feb-06 17:38 
GeneralRe: CPU overload: CCustomThread Pinmemberpphilippot20-Feb-06 21:01 
GeneralRe: CPU overload: CCustomThread Pinmembereaster_200721-Feb-06 12:51 
Generalcapture file access events PinmemberW M Suleiman30-Jan-06 6:41 
GeneralRe: capture file access events PinmemberIvo Ivanov30-Jan-06 15:03 
GeneralRe: capture file access events PinmemberW M Suleiman31-Jan-06 3:12 
GeneralRe: capture file access events Pinmembertasioreczek15-Mar-07 1:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140421.2 | Last Updated 26 Mar 2002
Article Copyright 2002 by Ivo Ivanov
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid