|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
AbstractIntercepting 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 Requirements
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 Design and implementationNT Kernel mode driver (ProcObsrv)
The entry point 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]:
Follows UML class diagram, that illustrates the relations between classes:
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(); };
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
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 }
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 codeYou 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 sampleHowever, 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. ConclusionThis 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:
|
||||||||||||||||||||||