![]() |
General Reading »
Hardware & System »
General
Advanced
API Monitoring UnleashedBy Parag PaithankarShows undocumented stuff to monitor 3rd party applications |
VC6, VC7Win2K, WinXP, Visual Studio, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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 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:
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.SetHookNow 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. DllMainDllMain 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 parag_pp@yahoo.com.
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.
EnumModulesThis 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.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 12 May 2003 Editor: Nishant Sivakumar |
Copyright 2003 by Parag Paithankar Everything else Copyright © CodeProject, 1999-2009 Web21 | Advertise on the Code Project |