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:
- 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.
- 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:
- Develop a dll that will contain an exported function for the API, which you
require to monitor/override.
- Get a process id to a running process, which you need to modify.
- Open the process with respect to the process id using the
OpenProcess
API.
- Create a remote thread inside the running process to control the
LoadLibrary
functionality of the running process.
- Execute this thread to ensure that the running process loads your dll into
its memory.
- 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.
- 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:
- 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.
- 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:
- Shared Module - This is the dll that will export our function for the
corresponding API.
- 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.
- [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:
- 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�.
- Now, select the Project type as �A simple DLL project�.
- 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.
- From the file view, double-click on the SharedModule.cpp file. This
file will be visible under the �Source Files� tree.
- 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:
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.
- 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.
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.
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:
- 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.
- Loop through all descriptors and find the import descriptor containing
references to functions called.
- 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.
- 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.
- 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:
- Loads the original library and gets an address to the original function.
- 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.
- 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.
- 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:
- 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.
- 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:
- 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".
- Now, select the Project type as �A "Hello, World!" application.�
- 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.
- From the file view, double click on the Injector.cpp file. This file will be
visible under the "Source Files" tree.
- 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:
- Get the process id of the application from the command line (argv[1]).
- Call the
DoHook
function with the UnHook
parameter
set to false.
- Enumerate all the modules for that particular process, and during the
process store the handle of our "sharedmodule.dll" module.
- 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:
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:
- Open the process with all possible access (
PROCESS_ALL_ACCESS
)
with the OpenProcess
API.
- 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.
- Copy the path to the module into the memory we allocated inside the remote
process. The API used to do this is WriteProcessMemory
.
- 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.
- 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.
- 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.
- After completion, free the memory allocated inside the remote process. This
is achieved by the
VirtualFreeEx
API.
- 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:
- Open the process, whose process id (pid) is passed to this function. This is
achieved by calling
OpenProcess
API with
PROCESS_ALL_ACCESS
.
- 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.
- 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.
- Now, call
EnumProcessModules
using the handle to the opened
process.
- Iterate through all the loaded modules and make subsequent calls to
GetModuleFileNameEx
.
- During iteration, compare the retrieved module to our module path.
- 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.