分类: C/C++
2008-08-26 11:51:54
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.
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:
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. 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:
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.
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.
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:
OpenProcess
API.LoadLibrary
functionality of the running process.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.
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:
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:
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:
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 .
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:
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.LibraryName
- This is the textual name of the library. If the original function belongs to user32.dll, this parameter will contain “user32.dll”.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.NewFunctionPointer
- This is the pointer to our function.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:
ImageDirectoryEntryToData
helper API. Please read up on this API in MSDN for further information. This is a very powerful and interesting 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 modulesNow 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:
Hook
the function or to restore (Unhook
) the function. This is decided by the UnHook
flag passed to this function. 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. hMods
, we iterate through the list and pass on every module handle to a subsequent call of SetHook
. DllMain
DllMain
is the entry point to our dll. It does the following:
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. 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:
#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 .
Let us now understand what happens in the code.
The Injector Application works in the following steps:
DoHook
function with the UnHook
parameter set to false. 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 hookerThis function is called with three parameters:
pid
– the Process id of the process which we are going to hook/monitor.UnHook
– Boolean flag indicating whether we are going to Hook or UnHook the process.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:
PROCESS_ALL_ACCESS
) with the OpenProcess
API.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.WriteProcessMemory
.FreeLibrary
function from Kernel32.dll, or if it is the other way around, then get an address to the LoadLibraryA
function from Kernel32.dll.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.LoadLibraryA
or FreeLibrary
to finish. This is achieved with a WaitForSingleObject
API call.VirtualFreeEx
API.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:
OpenProcess
API with PROCESS_ALL_ACCESS
.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. EnumProcessModules
using the handle to the opened process.GetModuleFileNameEx
.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.