H3llo ReLearEx-Nation,

A few days ago, I had the idea to write a short series of hooking techniques. In this post, I will demonstrate how the Import Address Table of a running PE module can be hooked successfully.

Before we jump to the interesting code snippets, I will talk a little bit about the theoretical stuff that is required to understand the whole hooking procedure.
The theory behind IAT hooking is essential (the same goes for any other hooking technique) and I recommend to read the theory section carefully.
The better you internalize the theory, the easier the understanding of the IAT hooking code will be.


Introduction

The Import Address Table (IAT) is a call table of user-space modules. The executable modules running on Windows possess one or more IATs integrated as part of their file structures.
For example, in case of an .exe file, an IAT stores the addresses of particular library functions imported from DLLs. That explains the name of this table.
Generally speaking, a call table is nothing more than an array where each element contains the address of a certain routine. In the realm of Windows, the IAT is not the only call table. Call tables exist both in user-space and kernel space. The following table provides you an overview of the the different call tables and the corresponding location where they reside.

Screenshot from 2017-11-30 00-13-26
Tab 1: Call tables with their locations

 

As you can see from Tab 1, there are a bunch of them. We will discuss each table but in this blog entry, we are going to dissect the IAT in detail. Although these tables differ from each other, the general idea of hooking can be divided into following steps:

1.) Locate the call table in question
2.) Store a target entry in table (could be also more than one entry !)
3.) Replace address of existing entry with address of your choice
(Swap in – Swap out)
4.) Restore old address after the work is done (ALWAYS transform the abused table back into its unmanipulated state)

Simply put, we are able to control the normal code path of program execution and redirect it to function of our choice by replacing a call table entry. That’s it. Sounds simple, right ? Well, we will see how that works programmatically in later sections.
Once we have the execution path under our control, we are able to do the following:

a) thwarting certain API calls by blocking them
b) hijacking original routines by replacing them with our functions
c) absorbing information passed to functions by intercepting input parameters
d) screening of results by filtering output parameters


Roadmap

Hooking an entry of Import Address Table requires the following operations:

1st:  Access address space of process
2nd: Locate IAT tables in the memory image of the PE file
3rdModify the IAT

The first step is a very important one. Without this, we can pack up & go home.
One of the easiest way to achieve this is DLL injection. We will discuss three DLL injection methods to access the address space of process in the following section (two of them in a superficial way & the last one will be explained explicitly).

The second step deals with the inwards of the PE file in memory.
The IAT is embedded into the file structure of the PE file. To localize it, we jump between several structures via offsets added to pointers until we find the IAT.
Keep in mind that for each DLL the PE file uses, we will encounter a corresponding IAT. Later, this will be discussed in the PE file structure section.

The third step is the most interesting one, in my opinion. But we have some work to do.


Accessing the address space

As mentioned above, there are 3 methods that can be applied to gain access to address space of the PE file. All of these methods rely on injecting a dynamic link library (DLL) into the targeted module to which the IAT belongs.
The purpose of a DLL is to make defined functions and variables accessible to other modules. It exports them so that they can be used by other modules. This exporting mechanism has the advantage that common routines can be put in a shared module. In that way, memory can be used very efficiently.

In the following code snippet, there is a very basic implementation of a DLL:

#include windows.h
#include stdio.h
/*
DllMain represents the entry point of the module
it will be invoked in following situations:

a) when a process loads DLL - DLL_PROCESS_ATTACH
b) when a process unloads DLL - DLL_PROCESS_DETACH
c) when a process creates a thread - DLL_THREAD_ATTACH
d) when a thread exits normally - DLL_THREAD_DETACH

NOTE: all of these cases are defined in the "winnt.h" header file
*/
BOOL APIENTRY DllMain
(
     HINSTANCE hinsDLL,       //handle to the DLL module
     DWORD fdwReason,         //represents the reason for calling the function
     LPVOID lpReserved        //reserved (means: you can ignore this parameter)

)
{
     switch(fdwReason)
     {
         case DLL_PROCESS_ATTACH:
         // ... do some stuff...
         // here, we will put our IAT hooking code
         break;

        case DLL_THREAD_ATTACH:
        // thread has been created
        break;

        case DLL_THREAD_DETACH:
        // thread is exiting in a normal way
        break;

        case DLL_PROCESS_DETACH:
        // process unloads DLL
        break;
     }

// if you wish:
// put here the functions that
// can be shared by other modules
// for example:
__declspec(dllexport) void function1(){

// ... some code ...
}
}

Most of the code is self-explanatory with the help of the comments that are included.
The only piece of information that remains is __declspec keyword.
For the sake of simplicity, I am going to tell this about that:
Using __declspec(dllexport), the DLL can export the routine (in our code specified by function1) so that it is visible to other modules.
In the same manner, it could also be used to export variables. As you may notice, exporting variables is not shown in previous code.

Method 1 to inject a DLL : Use of registry value

This relies heavily on default behaviour of user32.dll. It has the nice side effect that when user32.dll is loaded by a process, then this DLL will call LoadLibrary() to load ALL DLLs specified by AppInit_DLLs.
The AppInit_DLLs registry value is located under the key:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows

Screenshot from 2017-12-01 12-33-45
Fig. 2 – Location of registry value AppInit_DLLs

To put it in a nutshell, user32.dll loads other DLLs specified by AppInit_DLLs when it itself gets loaded. On Fig. 2, we can see that the list of DLLs given for AppInit_DLLs is empty but we could change it immediately by double-clicking on it:

Screenshot from 2017-12-01 21-12-43
Fig. 3 – After double-clicking on AppInit_DLLs

To enable this “automatic loading of DLLs”-feature, we need to set LoadAppInit_DLLs (which is a REG_DWORD Boolean value) to 0x00000001. Otherwise, it would fail.

On the one hand, this method is an effective approach because most applications import user32.dll. On the other hand, using this method we will affect every program launched AFTER the registry value has been changed. Applications launched before the registry value was changed will be untouched.

Method 2 to inject a DLL: Associate an event type with a DLL routine

The SetWindowsHookEx() is a well-documented Windows API. It connects a certain type of event with a routine that is defined in a DLL. Whenever the event occurs, the routine will be executed.
To our surprise, this routine will exactly the one that implements the hooking code.
Windows hosts a bunch of different event types so that an attacker can choose for his/her hooking adventures. The choice which event type to select is not so import.
As long as it is an event type that occurs frequently, we can select what we want. In the following, there is a code snippet that can be wrapped up as project. We won’t dive into that because (like the previous method) it is not my choice to inject the DLL into the address space of a process.

Method 3 to inject a DLL: Create a remote thread

The CreateRemoteThread() API is also officially documented. How this can be used for hooking can be divided into the following six steps:

1.)  get address of the LoadLibrary() function in kernel32.dll
2.)
  allocate some memory in address space of target process
3.)  copy name of DLL into this allocated memory space
4.)  create thread in target process
5.)  Let the injection begin !

Specifically in step 5.), the code does the following:
Using the address from step 1.), the remote thread calls LoadLibrary() to load the DLL whose name was stored into the address space of our target in step 3.)  .
In other words, address of LoadLibrary() equals starting address of remote thread

The CreateRemoteThread() call appears in step 4 . The majority of our work deals with the setup (steps 1 – 3 ) before we can pass the DLL name as parameter to LoadLibrary().
As you may expected, the hooking code is placed into the DLL as a routine.
To sum up this method, let’s look at following bunch of lines:

...
// we need to get a handle to the target process
handleToProc = OpenProcess(
               PROCESS_ALL_ACCESS,
               FALSE,
               procID      // procID can given to program via run-time in form of an input
               );

// check
if (handleToProc == NULL) {
    errorCode = GetLastError();
    printf("OpenProcess has failed.\n");
    printf("Error code is %d\n", errorCode);
    return -1;
}

// get handle to Kernel32.dll
// we will need it later to get the address of
// LoadLibrary()
handleToDLL = GetModuleHandleA("Kernel32");

// again, check
if (handleToDLL == NULL) {
    errorCode = GetLastError();
    printf("GetModuleHandleA has failed.\n");
    printf("Error code is %d\n", errorCode);
    return -1;
}

// get address of LoadLibrary()
addrOfLL = GetProcAddress(dllHandle, "LoadLibraryA");

if (addrOfLL == NULL) {
    errorCode = GetLastError();
    printf("LoadLibraryA has failed.\n");
    printf("Error code is %d\n", errorCode);
    return -1;
 }

// Create argument to LoadLibraryA in remote process
startingAddr = VirtualAllocEx(
               procHandle,
               NULL,
               256,
               MEM_COMMIT | MEM_RESERVE,
               PAGE_READWRITE);

if (startingAddr == NULL) {
    errorCode = GetLastError();
    printf("VirtualAllocEx has failed.\n");
    printf("Error code is %d\n", errorCode);
    return -1;
 }

// take path of our DLL with the hooking code in it
// and write the path to the newly allocated mem. space
// of the target process
validWriting = WriteProcessMemory(
               handleToProc,      // handle to target process
               startingAddr,      // starting address
               (LPCVOID)dllPath,  // array that contains path to DLL
               sizeof(dllPath) + 1,
               NULL);

// check to see if everything was ok
if (validWriting == 0) {
    errorCode = GetLastError();
    printf("WriteProcessMemory has failed.\n");
    printf("Error code is %d\n", errorCode);
    return -1;
}

// at this point, the setup is complete
// we can invoke a DLL in remote thread
handleToThread = CreateRemoteThread(
                         handleToProc,
                         NULL,
                         0,
                         addrOfLL,
                         startingAddr,
                         0,
                         NULL);
...

 


PE File Format

The second part of our theory stuff deals with the composition of the PE (“portable executable”) File Format which is the format of every executable file on Windows.
Remember that we want to access the IAT of a module.
To access it, we need to know how to find it in the memory and the PE File Format gives us enough hints in form of structures, pointers, offsets etc. that we can use to locate the IAT.
This format is very well analyzed and documented so that the search engine of your choice can give you a huge amount of sources about it.
Therefore, I will examine only the relevant parts that can lead us to the IAT.

The DOS Header

The first 40 bytes of any PE file cover the DOS executable header.
From the perspective of a developer, this header is defined by the IMAGE_DOS_HEADER structure.
The prototype of that structure can be shortened as follows:


typedef struct _IMAGE_DOS_HEADER {

     WORD e_magic;      // Magic number
     .....
     .....
     .....
     LONG e_lfanew;     // RVA of PE's file header

} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

As stated above, we will look at the interesting fields of the structures.
In that case, the first and last member of the IMAGE_DOS_HEADER structure are of interest for us with regard to the hooking code that we will see later.

The first field e_magic represents the magic number of a PE file which is 0x4D5A (or “MZ” in ASCII).
The initials “MZ” has to do with Mark Zbikowski who is the developer/inventor (or “father”) of this file format.

Note: You can read more about Mark Zbikowski and check what he had accomplished in his career under https://en.wikipedia.org/wiki/Mark_Zbikowski

The last field e_lfanew gives us an offset, a so called relative virtual address (RVA) that we can add to the base address of our module to obtain the linear address of the file header in memory.

We will also encounter the concept of the RVA in the following structures. Therefore, you need to know that the elements in a PE image can be accessed by simply adding the RVA of the element in question to the base address of the image file.
In general, we will use the formula presented below:

base address of module + RVA of PE element = linear address of PE element

The PE Header

This header is the next step of our tour to the IAT. As I wrote above, it can be reached by adding  the value of e_lfanew member of the previous structure to the base address of the module. The PE file header is defined by IMAGE_NT_HEADERS structure:


typedef struct _IMAGE_NT_HEADERS {

    DWORD Signature;                       // another magic nr.
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

In the next few sections, we will discuss each of these three members.

Beginning with the first field called Signature, we can say that this another magic number that can be used for sanity checks as we will see later in the coding section of the IAT hooking procedure.
The signature has the value 0x50450000 (“PE\0\0”).

The next field is a substructure whose purpose is to store some basic attributes of the PE file. The reason why I mention this is that the Characteristics member of this structure can be used to check if a module is DLL or EXE.
Specifically, it is nothing more than a set of binary flags. To make this concrete, the 14th bit of this field will be set if the module represents a DLL or clear if it is an EXE. Nothing more, nothing less.

typedef struct _IMAGE_FILE_HEADER {
...
...
...
WORD Characteristics;
}IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

The 3rd field of the IMAGE_NT_HEADERS structure OptionalHeader is maybe the most important one because it represents the first step into the realm of the imported modules. Similar to FileHeader, the member OptionalHeader is a substructure that itself contains a bunch of members. But as in the previous sections, I will only talk about the first and last member of OptionalHeader since these two will be used later in the code. Here is the prototype:

#define IMAGE_NUMBER_OF_DIRECTORY_ENTRIES 16 

typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD Magic;
    ...
    ...
    ...
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBER_OF_DIRECTORY_ENTRIES];
}IMAGE_OPTIONAL_HEADERS32, *PIMAGE_OPTIONAL_HEADERS32;

As usual, the first field is a magic number:
For normal executables, this is set to the value 0x10B. Again, this value is later used in our code to check whether we are dealing with a valid PE file or not.

The last field called DataDirectory is an array of IMAGE_DATA_DIRECTORY structures which is defined below:

typedef struct _IMAGE_DATA_DIRECTORY {

   DWORD VirtualAddress;     // RVA of data
   DWORD Size;               // Size of the data in bytes

}IMAGE_OPTIONAL_HEADERS32, *PIMAGE_OPTIONAL_HEADERS32;

Since our goal is to access the imports, we will take the 2nd entry of the DataDirectory array which represents the import directory.
Therefore, one can define the integer macro as :

// Import Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT       1

The VirtualAddress member in this array element describes the location of the import directory. The import directory in turn is also an array. The elements of this array consist of structures of type IMAGE_IMPORT_DESCRIPTOR.
One structure of this type is assigned for each DLL that is imported by the module.

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk;   // RVA of ILT
    };

    ...
    ...
    DWORD Name;       // RVA of imported DLL name
    DWORD FirstThunk; // RVA to IAT

}IMAGE_IMPORT_DESCRIPTOR;

Again, only the most relevant fields for us will be discussed. For that reason, the three MIMs (Most Import Members) are the following:

a) OriginalFirstThunk
-> RVA of the Import Lookup Table(ILT)
b) Name
-> RVA of an ASCII string (null terminated) -> i.e. DLL name
c) FirstThunk
-> RVA of Import Address Table (IAT)

The members OriginalFirstThunk and FirstThunk point to an array of IMAGE_THUNK_DATA structures. This structure embodies a union of several members.
For each function used by the module in form of an import, we will encounter a IMAGE_THUNK_DATA structure (like each imported DLL is represented by a  IMAGE_IMPORT_DESCRIPTOR structure as explained above).
The prototype of the IMAGE_THUNK_DATA structure can be shown as follows:

typedef struct _IMAGE_THUNK_DATA {
     union {
         ...
         PDWORD Function;
         DWORD Ordinal;
         PIMAGE_IMPORT_BY_NAME AddressOfData;
     }u1;
}IMAGE_THUNK_DATA32;

The reason why we have two arrays is very simple:
The purpose of the ILT array is to store names of the imported functions whereas the purpose of the IAT array is to conserve the addresses of the imports.

At this point, we can close the theory section and plunge into the blocks that make up the IAT hooking code where will use our theoretical knowledge about the PE file structure.


IAT Hooking Code

As promised, in this section we are going to build the IAT hooking code step by step.

Suppose that we have injected our malicious DLL into the address space of the target process. The code lines that will be executing right after injection will be:


....

case DLL_PROCESS_ATTACH:
{
hook = hooking("GetCurrentProcessID");

if (hook == FALSE){
   // print out a msg. that the hooking has failed
}

}

Remember the basic DLL that we implemented at the beginning.
Here, we will use it by placing the hooking() function within the case-block DLL_PROCESS_ATTACH.
The hooking() routine that will be executed when the process loads the DLL takes the name of the API (here: GetCurrentProcessID) that we want to hook.

Now, let’s look at the hooking() routine:


BOOL hooking(char *nameOfAPI){

DWORD addrOfModule;
BOOL resultOfParsing;
// according to MSDN:
// passing NULL to GetModuleHandle() gives us base address
addrOfModule = (DWORD)GetModuleHandle(NULL);

resultOfParsing = parsingImports(addrOfModule, nameOfAPI);

return resultOfParsing;
}

Recall the equation that is used to locate the PE elements in memory. In that equation, we add the RVA of a PE element to the base address of the module.
Therefore, we call GetModuleHandle API with NULL as parameter which will return the base address of the module. Then, this address is passed to the parsingImports() routine as first argument with the name of the target API as second argument.

Next, let’s take look at parsingImports() :


BOOL parsingImports
(
DWORD baseAddress,
char *nameOfAPI
)
{
PIMAGE_DOS_HEADER dosHeader;
PIMAGE_NT_HEADERS peHeader;

IMAGE_OPTIONAL_HEADER32 optionalHeader;
IMAGE_DATA_DIRECTORY importDirectory;
DWORD descriptorStartRVA;

PIMAGE_IMPORT_DESCRIPTOR importDescriptor;

int index;
// here, the sanity checks start
// it checks the module's magic numbers

dosHeader = (PIMAGE_DOS_HEADER)baseAddress;

// if modules magic number is wrong,
// then report it and return false
if (((*dosHeader).e_magic) != IMAGE_DOS_SIGNATURE) {

   // Logging if you want for debugging purposes
   return(FALSE);
} 

// check PE signature
peHeader = (PIMAGE_NT_HEADERS)((DWORD)
baseAddress + (*dosHeader).e_lfanew);

// if PE Header Signature is wrong,
// then log it and return false
if (((*peHeader).Signature) != IMAGE_NT_SIGNATURE) {

   // Logging
   return(FALSE);
}
// checking magic nr. of Optional Header
optionalHeader = (*peHeader).OptionalHeader;

// if magic number of OptionalHeader is wrong,
// then report it and return false
if ((optionalHeader).Magic != 0x10B) {
   // Logging
   return(FALSE);
}

// here, the sanity checks end

// now we parse through its import descriptors
importDirectory  =
(optionalHeader).DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

// get the RVA of the import descriptor
descriptorStartRVA = importDirectory.VirtualAddress;

// since, the module is in memory we can use the
// RVAs to locate PE file components
// Just use RVA to the base address
importDescriptor =
(PIMAGE_IMPORT_DESCRIPTOR)
(descriptorStartRVA + (DWORD) baseAddress);

index = 0;

// Remember that each DLL imported by function is represented
// by the _IMAGE_IMPORT_DESCRIPTOR structure
// we look at each import descriptor to see which routines are
// imported from corresponding DLL

while (importDescriptor[index].Characteristics != 0) {
   char *nameOfDLL;
   nameOfDLL = (char *)((importDescriptor[index]).Name + (DWORD)baseAddress);

   if (dllName == NULL) {
      // Logging
   }
   else {
    // Logging if you want to see which DLL is imported
   }

   // analyzeImportDescriptor() fct. takes the _IMAGE_IMPORT_DESCRIPTOR
   // and walks through the list of api names that the module has
   // imported
   // After finding the target fct. specified by apiName,
   // it then replaces it with address of own function

   analyzeImportDescriptor(
              importDescriptor[index],
              peHeader,
              baseAddress,
              nameOfAPI
   );

   index++;
} // end of while-loop

return(TRUE);

} // end of parsingImports()

Most of the code in this routine consists of sanity checks. We use these checks to make sure that we deal with a valid PE file. In case of a failure, we return FALSE since it would not make any sense to hook an API of an invalid PE file.
The remainder of the code after the sanity checks is explained in the comments. It is up to the reader to compare the comments of the code lines with the theoretical contents from the previous sections.

The only routine that needs to be looked is analyzeImportDescriptor(). The definition of that is :


void analyzeImportDescriptor
(
IMAGE_IMPORT_DESCRIPTOR importDescriptor,
PIMAGE_NT_HEADERS peHeader,
DWORD baseAddress,
char *nameOfAPI
)
{
PIMAGE_THUNK_DATA thunkILT;
PIMAGE_THUNK_DATA thunkIAT;
PIMAGE_IMPORT_BY_NAME nameData;

int numberOfFuncs;
int numberOfOrdinalFuncs;

// declare the function ptr that will point to our own function
// used to replace the target API specified by apiName
DWORD(WINAPI *procPtr)();

// gets the RVAs of OriginalFirstThunk & FirstThunk
thunkILT = (PIMAGE_THUNK_DATA)(importDescriptor.OriginalFirstThunk);
thunkIAT = (PIMAGE_THUNK_DATA)(importDescriptor.FirstThunk);

// in the following there is a bunch of code to check for
// empty ILTs & empty IATs
if (thunkILT == NULL) {
// Logging
return;
}
if (thunkIAT == NULL) {
// Logging
return;
}
// getting lin. addr. of thunkILT
thunkILT = (PIMAGE_THUNK_DATA)((DWORD)thunkILT + baseAddress);
if (thunkILT == NULL) {
// Logging
return;
}
// getting lin. addr. of thunkIAT
thunkIAT = (PIMAGE_THUNK_DATA)((DWORD)thunkIAT + baseAddress);
if (thunkIAT == NULL) {
// Logging
return;
}

numberOfFuncs = 0;
numberOfOrdinalFuncs = 0;

// IMPORTANT:
// this technique does not work if
// the routine we wish to hook has been imported
// as an ordinal
// OR
// if program is using run-time linking
// loop as long as RVA of imported name (AddressOfData) is not 0
while ((*thunkILT).u1.AddressOfData!=0) {
   // has been the routine imported as ordinal number
   if (!((*thunkILT).u1.Ordinal & IMAGE_ORDINAL_FLAG)) {
       // get the RVA of imported function's name
       nameData = (PIMAGE_IMPORT_BY_NAME)((*thunkILT).u1.AddressOfData);

       // add base address to RVA of imported function's name to get the location of it
       nameData = (PIMAGE_IMPORT_BY_NAME)
       ((DWORD)nameData + baseAddress);

       // we compare names in descriptor's ILT against name of the fct.
       // that we want to supplant
       // if we find a match
       // then we swap in the addr. of a hook routine
       if (strcmp(apiName,(char*)(*nameData).Name)==0) {
          // function ptr gets address of our function that will
          // replace the GetCurrentProcessId()
          procPtr = ModifiedGetCurrentProcessId;

          // here, the overwriting the address occurs
          thunkIAT->u1.Function = (DWORD)procPtr;
       }
   }
   else {
      nOrdinalFunctions++;
   }

   thunkILT++;
   thunkIAT++;
   nFunctions++;
} // end of while-loop

return;
} // end of analyzeImportDescriptor() -----------------------------------------------

As you can see, within the while-loop, we check if one of the entries in the ILT table matches with the given target API name. If a match is found, we set a function pointer to the address of the function ModifiedGetCurrentProcessId which will be the function that replaces the original routine GetCurrentProcessId.
After this setting, we change the address of the GetCurrentProcessId in the IAT table with address of ModifiedGetCurrentProcessId which is stored in procPtr.
At this point, the hooking procedure is finished. Everything is done.

Note: The ModifiedGetCurrentProcessId routine is shown below. For the sake of simplicity, I leave it very simple.


int ModifiedGetCurrentProcessId(){

    return 0;

}

Its only purpose is to assign the PID value 0 to every process that we hook by simply returning that value when that GetCurrentProcessId API is called.

In the following GIF, an example is presented in which the process named TargetProcess gets the ID 3628 when it is created. The code for TargetProcess.exe is not shown but it simply puts the id of TargetProcess on the screen via an endless loop. After injecting the DLL HookIAT.dll into the address space, we can see that id value changes from 3628 to 0.

Record_2017_12_26_07_05_41_40

 

I hope you liked this article. Please leave comments if you wish and you can also write to me via relearex.gmail.com.
Feedbacks are welcome !!!

 

 

 

 

Leave a Reply