Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

API Monitoring Unleashed

0.00/5 (No votes)
12 May 2003 1  
Shows undocumented stuff to monitor 3rd party applications

The Concept

Often, applications need to be monitored. Say, you need to know how an application is performing and need to keep a track on the resource usage. This is quite possible if you are the author of the application or have control over the source tree. What if the application is a third-party application? How can you plan to monitor a complete black box? This article will target this area and show you various ways in which this can be achieved. Even though the APIs used in this article are documented in the Microsoft developer network (MSDN), you won�t find a correct code snippet that shows you how to use them. And when you read the documentation, you will yourself notice that it is quite complicated to understand what has been written, forget about knowing how to use it! That�s why I would like to say that whatever appears in the form of program code from now on is all �undocumented� stuff. Like all undocumented code, this code is not supported by the vendor and can change across versions.

The need

Before we even get started with understanding how to achieve the above, first let us try and understand properly why one would require to do this, or, what the �business benefit� would be. Even when I learned this technique, I was working on a project that required me to monitor a particular set of APIs. Although my scope was the entire system and monitoring each and every application running, we will limit the scope of this article to monitoring just one running application. Here are a few reasons why one would require doing so:

  1. To log how the application is using a particular API. Now, this API could also be your written function exposed by some custom COM component or library dll. In a normal case, you will have to write the entire logging code inside your dll. But with the API monitoring system, you can actually keep this logging code generic and keep the monitoring specific to a running process.
  2. To change the behaviour of a particular API. Say you need to disable all calls to a particular API. For example, user32.dll has an exported API called OpenFile, in order for you to disable a particular application from using this API, you can override the OpenFile API and write your own code (in case of disabling it, returning a failure value to the caller). In this way, two things are highlighted, first, you can disable any such API from any dll, and second, you can override the default behaviour of a particular API and provide your own functionality.

The method of injecting code

Before we start of with understanding how to �inject� code into a running process, let us try and understand how applications work and how process memory is organized. If this explanation gets too complicated, you can skip this part and jump to the next section, �Writing the code�.

Import Table

Internal Shared Memory

Process Stack and Heap

Export Table

The above table gives a brief idea of how memory is allocated for any running process in the Windows environment

Every process has sections in memory as seen in the diagram. When the process starts, the global/shared data is loaded in the �Internal Shared Memory� area. It has been called �Internal� because the data is global within the process but not outside the process boundary. After this, the �Import Table� and the �Export Table� are populated for this process. This information comes from the binary of the process. Basically, the Import Table and the Export Table have the following meaning:

Import Table

The Import Table lists all the dlls that the process will be using. From now on, I will refer to dlls as �Modules�. It not only lists all the modules that the process will use, but also pointers to functions. Keep this point in mind, because we will be digging into this �Import Table� and changing the function pointer to our function. Although the mechanism to do so sounds pretty simple and straightforward, when you take a look at the code you will notice that it is in fact not the case.

Export Table

The Export Table lists all the functions and the respective function pointers that the running process will export. This is true if the process itself is a dynamic link library.

Process Stack and Heap

These are locations that are used by the process dynamically. It is on the stack that all function pointer tables and the respective variables required for an entering function are stored.

The method that we will use to inject a particular code will run in the following manner. Later, I will show you the respective code for the steps given below:

  1. Develop a dll that will contain an exported function for the API, which you require to monitor/override.
  2. Get a process id to a running process, which you need to modify.
  3. Open the process with respect to the process id using the OpenProcess API.
  4. Create a remote thread inside the running process to control the LoadLibrary functionality of the running process.
  5. Execute this thread to ensure that the running process loads your dll into its memory.
  6. Once your dll is loaded, and when the dll is attaching, get a pointer to the import list inside the running process. This part is a bit tricky and will require an understanding of how to iterate an Import Table list. To make life easier, there are helper APIs available, which allow you to do this. This will be explained in detail when explaining the code.
  7. Now after getting a pointer to the import function and a pointer to the function itself, replace this pointer with a pointer to your function from the dll.

After following the above steps, you will observe that any call to the particular API will result in your function being called. In this way you can monitor or override any API from any Module, provided that you know the name of the Module.

A little clarity

Now that I have explained the steps involved, let�s go ahead and write the code to achieve this. Before we write the code, we need to make the following things very clear:

  1. This code �injection� system will only work in the context of the currently logged-in user. This clearly means that it is not possible to monitor an application running under a different user context. This also means that you cannot monitor or inject code into any application over a network and running under a different user context.
  2. This system uses the OpenProcess API with the OPEN_ALL_ACCESS bit, which means that when you open the process, you need to be the administrator of the machine or the creator of the process. Also, you cannot open a process that has been created with a completely different security descriptor. There is a way to handle this situation but it might not work in all circumstances. There is something called �opening a process with debug privileges�, read up on it in MSDN.

So, what are the pieces that we need to code? For API Monitoring consider the following pieces of code:

  1. Shared Module - This is the dll that will export our function for the corresponding API.
  2. Injector Application - This is the executable, or the utility that we will write for code injection. This application basically accepts a process id of the application we need to monitor and injects our shared module inside the process space of that application.
  3. [Optional] - A device driver for new process creation/destruction notificationThis is an Optional component, which I will explain but won�t provide the source code. This is a device driver, which compiles with Microsoft Windows DDK using Microsoft Visual C++ 6.0 Compiler. This driver basically fires an event whenever a process has been created or destroyed. This driver is important if you write a system-level injection code and need to know when a process is created or destroyed. I wrote a device driver for this (kernel-mode) because I couldn�t find an alternative for this in user-mode. Not even using Hooks!

We will now plunge right into the Shared Module. This is a Windows Dynamic Link Library, a very simple DLL that contains the overridden code as well as the actual injection code. Why do we need the Injector Application if this DLL is doing the injection part? We need the application for the simple reason that DLLs can�t run by themselves. Another reason is that the injector application is the one that will create memory, load the DLL, etc, inside the remote processes space.

Now, let us see how to write such a DLL in MS VC++ 6.0: Just follow the following steps; they are so simple that even if you have never used a VC++ IDE, you can still create the DLL and write the corresponding code very easily:

  1. Start VC++ IDE. Select the Project template (from the Projects Tab) as �Win32 Dynamic-Link Library.� DO NOT touch any other settings or checkboxes. Enter the Project Name as �SharedModule�.
  2. Now, select the Project type as �A simple DLL project�.
  3. Now, the wizard will create the files for you and inject the basic code. Open the file view from the Workspace View. If workspace view is not visible, press ALT+0.
  4. From the file view, double-click on the SharedModule.cpp file. This file will be visible under the �Source Files� tree.
  5. Add the code from SharedModule.cpp below the #include �stdafx.h� line. There will be a point-by-point explanation of the code after we have finished all the steps.

Now compile the application, it should successfully compile and generate the DLL. If it fails, please feel free to send me a dump of the error log. Please send it to Parag_pp@yahoo.com.

I will now explain the above code and we can then move ahead to writing the code for the injector application.

Let us try and understand the code now. It�s assumed that you are a C/C++ developer. If you are not, then you can simply skip this section. Apart from the standard stdio and stdlib, you will see that imghelp has been included�you require this header file for the �helper APIs� that we have used. In order to locate the module handle of our own DLL, we need to store the module handle passed to our DllMain entry point. This is stored in the g_hModule variable. You would have guessed by now that we are hooking the CopyFileW function of the user32.dll library. In order to call the original function after our work is done, we need to store the function pointer of the original function. This is stored in the g_OriginalCopyFileW variable.

Next, as we are using function pointers, we need to have a typedef that contains the exact prototype declaration of the function as it exists in the dll. The prototype of CopyFileW is MyCopyFileW_t. Just below the prototype, you will see our function, which will be called after the hooking has been done. If you see the function body, you will notice that the function does not do anything at the moment. It simply calls the original function, which is pointed by the g_OriginalCopyFileW pointer. It is at this place that you can write your implementation code and may or may not call the original function. In this way, you can override the default behaviour of the CopyFileW library function.

I am sure you must be wondering why there is a �W� at the end of the CopyFile API always. This is because CopyFileW is the Unicode implementation of the CopyFile function. There is also an ANSI version of the CopyFile API, called CopyFileA. If you are or have been a Visual Basic developer you might have been using an A at the end of the API call while declaring the API using �Declare�. Alias�.� etc. But now I guess you know what the A at the end exactly means.

Let�s move on. Now comes the most important function that actually does the hooking. This function is called SetHook. SetHook accepts four parameters. Here are the four parameters and their significance:

  1. hModuleOfCaller - This is the module handle of the caller who is making the call to this API. Now, say there is a process called �A� that is making a call to the CopyFile API. But this actual implementation of the API call may or may not be in the process itself. It could also reside inside a dll, which the process A refers to.
  2. When we iterate through all such loaded modules (dlls) for a particular process, we have to make sure that all these modules/processes call our function instead of the original library�s function. This parameter is hence the module handle of one such loaded module.
  3. LibraryName - This is the textual name of the library. If the original function belongs to user32.dll, this parameter will contain �user32.dll�.
  4. OldFunctionPointer - This is the pointer to the original function from the base library. Hence, if we are hooking the CopyFile function of user32.dll, this parameter will contain the pointer to the original function from user32.dll. This can be obtained by using the GetProcAddress API.
  5. NewFunctionPointer - This is the pointer to our function.

Digging into SetHook

Now let us try and understand the SetHook function in detail. As mentioned earlier, we need to iterate through a list of import descriptors and change the function pointers to point to our function. This is exactly what the SetHook function does. Let us understand how it does this. In order to understand this quickly and clearly, consider dividing its tasks into the following list:

  1. Get the address of the module�s import section. This is done using the ImageDirectoryEntryToData helper API. Please read up on this API in MSDN for further information. This is a very powerful and interesting function.
  2. Loop through all descriptors and find the import descriptor containing references to functions called.
  3. Once we get an address to the module�s import section we iterate through the various entries and compare the library name passed to the function.
  4. Get pointer to caller�s Import Address Table (IAT). Once the library is located, get the pointer to the Import Address Table that will actually contain the various function pointers.
  5. Iterate through the import list, locate the function we want to hook and replace the original function pointer to our function.

Now iterate through the IAT to locate our function. This function pointer will be exactly the same as the one obtained by calling a GetProcAddress. Once located, replace the old function pointer to point to our function.

SetHook and multiple modules

Now that we have understood how to hook inside a loaded module using the SetHook function, let us understand where to apply this knowledge. It is quite possible that a particular process had loaded multiple modules at a time. In order to apply a process-wise hook in an efficient manner, we need to set a hook into each and every module loaded by the process. This is exactly what the EnumAndSetHooks function does. Let us now try and understand how this function works.

The following simple steps explain how EnumAndSetHook works:

  1. Loads the original library and gets an address to the original function.
  2. Depending on the parameter passed, it decides whether we need to Hook the function or to restore (Unhook) the function. This is decided by the UnHook flag passed to this function.
  3. It then calls the EnumProcessModules API from PSAPI.DLL. This API gets a list of all the loaded modules for a process. As the dll is loaded and run inside the hooked process, GetCurrentProcess returns us the handle to the currently running process.
  4. After getting a list of modules inside hMods, we iterate through the list and pass on every module handle to a subsequent call of SetHook.


The DllMain

DllMain is the entry point to our dll. It does the following:

  1. If the operation requested is of type �DLL_PROCESS_ATTACH�, store the original Module handle and call EnumAndSetHooks with UnHook as FALSE indicating that we are hooking this API. EnumAndSetHooks returns a pointer to the original function; we will need this when unhooking from the API.
  2. If the operation requested is of type �DLL_PROCESS_DETACH�, restore the original function pointer by calling EnumAndSetHooks with the UnHook set to TRUE.

We will now move on to understanding the Injector Application.

Now that we have sharedmodule.dll ready and have understood the code, let�s move on to the "action application": the Injector Application. Here�s where all the "remote" magic is taken care of. The Injector Application is the application to which is passed the process id of the application we want to monitor. As explained earlier, this application will open the process and load our DLL into the process space. Here is the code to do that. The Injector Application is an EXE file, so please follow the steps shown below to create one such EXE file. We will create the EXE in VC++ as we created a DLL in VC++. The steps are very simple. Please DO NOT modify any of the checkboxes or settings when following the steps given below:

  1. Start VC++ IDE. Select the Project template (from the Projects Tab) as "Win32 Console Application". Please remember not to tamper with any other settings or checkboxes. Enter the Project Name as "Injector".
  2. Now, select the Project type as �A "Hello, World!" application.�
  3. Now, the wizard will create the files for you and inject the basic code. Open the file view from the Workspace View. If workspace view is not visible, press ALT+0.
  4. From the file view, double click on the Injector.cpp file. This file will be visible under the "Source Files" tree.
  5. Add the code from Injector.cpp, just below the #include "stdafx.h" line.

Please compile sharedmodule.dll and Injector.exe. Now go to the command prompt and run Injector.exe. Pass the process id of the application you wish to monitor. This process id has to be passed to injector.exe at the command prompt. Say, the process id of Explorer.exe is 1676, then you need to run Injector.exe as follows:

C:\work\vc6\injector\injector.exe 1676

After running, your application will list out all the modules loaded by Explorer.exe. It will then hook into Explorer.exe and wait for you to press a key, after which it will unhook from Explorer.exe.

If you have any problems compiling Injector.exe, please send an e-mail to parag_pp@yahoo.com.

Let us now understand what happens in the code.

The working

The Injector Application works in the following steps:

  1. Get the process id of the application from the command line (argv[1]).
  2. Call the DoHook function with the UnHook parameter set to false.
  3. Enumerate all the modules for that particular process, and during the process store the handle of our "sharedmodule.dll" module.
  4. Call DoHook with UnHook set to true and module handle set to the stored module handle.

So, as you would have understood so far, the heart of the application lies inside two main functions DoHook & EnumModules.

DoHook�the process hooker

This function is called with three parameters:

  1. pid � the Process id of the process which we are going to hook/monitor.
  2. UnHook � Boolean flag indicating whether we are going to Hook or UnHook the process.
  3. hFreeModule � Handle to the module which will host our Remote Thread. This is neglected when hooking into the process.

DoHook itself works in the following steps:

  1. Open the process with all possible access (PROCESS_ALL_ACCESS) with the OpenProcess API.
  2. Allocate memory into the remote process using the VirtualAllocEx API. We will require this memory to store the path to our module. When our thread is called, this path will be used by the remote process to load the module.
  3. Copy the path to the module into the memory we allocated inside the remote
    process. The API used to do this is WriteProcessMemory.
  4. Now, if the operation is a hooking operation (which means UnHook is false), get an address to the FreeLibrary function from Kernel32.dll, or if it is the other way around, then get an address to the LoadLibraryA function from Kernel32.dll.
  5. If the operation is a hooking operation, call LoadLibraryA with a path to the module to be loaded. This is the magic call, which actually loads our DLL into the remote processes address space! The moment our DLL is loaded, the respective DllMain code is called from sharedmodule and things start rolling! If the operation is an Un-hooking operation, FreeLibrary is called with handle to our module. This forces a DLL_PROCESS_DETACH being called.
  6. In both cases, whether hooking or un-hooking, we have to wait for LoadLibraryA or FreeLibrary to finish. This is achieved with a WaitForSingleObject API call.
  7. After completion, free the memory allocated inside the remote process. This is achieved by the VirtualFreeEx API.
  8. Now, close the respective handles, freeing the memory. So, as you must have understood, it is this function which actually opens the remote process and pushes in a thread into that process. Then our module is loaded by calling the subsequent thread and the action begins.

In order to free or un-hook the application from our module, it is important that we store the handle to our module once it�s loaded into the remote process. To achieve this, we have the EnumModules function, explained next.

EnumModules

This is a very simple function and works in the following steps:

  1. Open the process, whose process id (pid) is passed to this function. This is achieved by calling OpenProcess API with PROCESS_ALL_ACCESS.
  2. As we are going to call API from a library called PSAPI.dll, it happens at times that the appropriate header file is missing. Considering that, let�s load PSAPI.dll in order to be able to call the function from this module.
  3. The two functions we are going to call from PSAPI.dll are: EnumProcessModules � This function enumerates all a modules for a particular process and stores their handles in a one-dimensional array. GetModuleFileNameEx � This function gets the path to the loaded module.
  4. Now, call EnumProcessModules using the handle to the opened process.
  5. Iterate through all the loaded modules and make subsequent calls to GetModuleFileNameEx.
  6. During iteration, compare the retrieved module to our module path.
  7. If found, free this module and return the handle to the module, to the caller.

You have to understand that we will require this function only when we are unloading our DLL and require the module handle to our DLL.

That�s it! See how simple that was? With respect to all the APIs provided throughout the articles, try hunting for them in MSDN. When found, forget about implementing them, even understanding how they work would look difficult. We hope this exercise provides a fairly good understanding of how all these APIs are used. You can go ahead and modify the overridden MyCopyFileA function to do whatever you wish! In this way, all subsequent calls to MyCopyFileA from within the remote application will call your code!!!

You might remember that we had mentioned something about a device driver. Well, here�s a short introduction to that: All applications running on the Windows operating system work in two modes�user mode and kernel mode. As far as we know, there is no way of knowing when a process was created or deleted in user mode. Although you can know which window was created or destroyed by setting the system-wide CBT (Computer-Based Training) Hook, you still can�t know which process was created or destroyed in user mode. To be notified of this, Windows OS provides a kernel mode API called PsSetCreateProcessNotifyRoutine. Using this, you can be notified of a creation/deletion of a process. Now, you must be thinking how can a kernel mode application talk to a user mode application? Well, it�s simple: by setting a kernel mode event and acknowledging the same in user mode! Send an e-mail to the aforementioned addresses if you are interested in obtaining the source code demonstrating this.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here