Most EXE files in PE files only have import tables and no export tables, unless this EXE provides callable functions to the outside, such as some self-extracting files, etc.
DLLs usually have both export and import tables.
The DLL file outputs function names, numbers, and entry addresses, etc., to the system through the output table.
EXPORT_TABLES Export Table
The code reuse mechanism provides a dynamic link library for code reuse, which is used to indicate which functions in the library can be called by others, this is the export table.
MAGE_EXPORT_DIRECTORY

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;// Attribute flags; not defined, always 0
DWORD TimeDateStamp; // Timestamp
WORD MajorVersion; //0
WORD MinorVersion;//0
DWORD Name; // Pointer to the export table file name string
DWORD Base; // Starting number of exported functions
DWORD NumberOfFunctions; // Total number of exported functions
DWORD NumberOfNames; // Number of functions exported by function name
DWORD AddressOfFunctions; // RVA of the export function address table
DWORD AddressOfNames; // RVA of the export function name table
DWORD AddressOfNameOrdinals; // RVA of the export function number table
{ IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;}
The main structure of the Export Table, the number refers to a 16-bit digital (unique) identifier for a function in the DLL, as mentioned in 'Encryption and Decryption', it is not recommended to find the address of a function by number, which may cause problems in DLL maintenance.
The number will change because the DLL has been upgraded or modified.
IMAGE_DATA_DIRECTORY
in the NT tableIMAGE_OPTIONAL_HEADER
}'sExtended PE headerIn which there is a structure called _IMAGE_DATA_DIRECTORY, which records the starting address and size of the export table.
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // Starting address of the export table
DWORD Size; // Size of the export table
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
CFF Explorer
Open with CFF Explorer to view, the information of the export table can be seen at the data directory
The export table contains three tables
_IMAGE_EXPORT_DIRECTORY.AddressOfFunctions; // RVA of the export function address table EAT
_IMAGE_EXPORT_DIRECTORY.AddressOfNames; // RVA of the export function name table ENT
_IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals; // RVA of the export function ordinal table. This table is an array of words. Maps the array index in the ENT (program entry point) to the corresponding output address table entry.
Here you can directly see the value corresponding to the offset in the export table, which is FOA
By bringing in the three function addresses RVA, function name address RVA, and function sequence number RVA of the export tableVA20ffset()
A function to calculate the FOA address, find the corresponding address on the disk.
Adding the buffer address buffer to get the actual address
// Print export table
// Export table file name string
DWORD NameFOA = VA2Offset(exporttable->Name + imagebase, ReadNTHeaders, m_pSecHdr);
// Export table function address FOA
DWORD AddresOfFunctionsFOA = VA2Offset(exporttable->AddressOfFunctions + imagebase, ReadNTHeaders, m_pSecHdr);
// Export table function name address FOA
DWORD AddressOfNamesFOA = VA2Offset(exporttable->AddressOfNames + imagebase, ReadNTHeaders, m_pSecHdr);
//Export table function sequence number FOA
DWORD AddressOfNameOrdinalsFOA = VA2Offset(exporttable->AddressOfNameOrdinals + imagebase, ReadNTHeaders, m_pSecHdr);
printf("\ndll_name:%s\n",(char *)(NameFOA+(LPBYTE)buffer));
//AddressOfFunctions points to the export function address table
DWORD* pAddressTable = (DWORD*)(buffer + AddresOfFunctionsFOA);
//AddressOfNameOrdinalsFOA points to the export function ordinal table
WORD* pOrdinals = (WORD*)(buffer + AddressOfNameOrdinalsFOA);
//Function name, convert RVA, get function name
DWORD* pNameTable = (DWORD*)(buffer + AddressOfNamesFOA);
VA2Offset RVA to FOA
VA2Offset(virtual address, NT table address, first Section table address)
We write a function to calculate the FOA address
DWORD VA2Offset(DWORD dwva, PIMAGE_NT_HEADERS m_pNtHdr, PIMAGE_SECTION_HEADER m_pSecHdr)
{
//imagebase file base address
DWORD dwImageBase = m_pNtHdr->OptionalHeader.ImageBase;
//File alignment and memory alignment are equal and not in the file header
if (m_pNtHdr->OptionalHeader.FileAlignment == m_pNtHdr->OptionalHeader.SectionAlignment || dwva <= m_pNtHdr->OptionalHeader.SizeOfHeaders)
{
return dwva;
}
else
{
for (int nInNum = 0; nInNum < m_pNtHdr->FileHeader.NumberOfSections; nInNum++)
{
if (dwva >= dwImageBase + m_pSecHdr[nInNum].VirtualAddress && dwva <= dwImageBase + m_pSecHdr[nInNum].VirtualAddress + m_pSecHdr[nInNum].Misc.VirtualSize)
{
return m_pSecHdr[nInNum].PointerToRawData + dwva - m_pSecHdr[nInNum].VirtualAddress - m_pNtHdr->OptionalHeader.ImageBase;
}
}
}
}
pAddressTable points to the addresses of various export functions
Determine the export method
Different export methods (also known as export modifiers) affect the form of function names, as well as the methods required by other modules to load and call this function.
The AddressOfNameOrdinals index table stores an index of the AddressOfNames address table[X]
index;
If the index Xstored in the index table, indicating a named export,If the index table does not exist, it is an index export.
When there are AddressOfNameOrdinals[Y] and AddressOfNames[X]
and is named export when there exists AddressOfNameOrdinals[Y] = X
// Determine whether it is an index export or a function name export
BOOL bIndexIsExist = FALSE;
for (int i = 0; i < exporttable->NumberOfFunctions; ++i) {
// Print virtual index, export function address table (RVA)
printf("Virtual index [%d] ", i);
printf("Address (RVA): %08X", pAddressTable[i]);
// Determine whether the current address is exported by name
// Judgment basis:
// The index table stores an index of the address table, which records
// Which address in the address table is exported by name.
// If the current index is stored in the index table, it indicates that this address
// It is exported by name. If this index does not exist in the index table
// It indicates that this address is not exported by name, but by index
bIndexIsExist = FALSE;
// Loop the number of functions exported by the export name
int nNameIndex = 0;
for (; nNameIndex < exporttable->NumberOfNames; ++nNameIndex) {
// Determine if the index in the address table exists in the number table i==AddressOfNameOrdinals[Y]
// printf(" \n %d ",pOrdinals[nNameIndex]);
if (i == pOrdinals[nNameIndex]) {
bIndexIsExist = TRUE;
break;
}
}
// Determine if bIndexIsExist is true, it is function name export, otherwise export by function number
// The function name needs to be converted one more layer of RVA
if (bIndexIsExist == TRUE) {
// Get the RVA in the name table
DWORD dwNameRva = pNameTable[nNameIndex];
// Convert the name RVA to the file offset that contains the actual function name
char* pFunName =
(char*)(buffer + VA2Offset(dwNameRva + imagebase, ReadNTHeaders, m_pSecHdr));
printf(" Function name:【%s】\t", pFunName);
// i: It is the index number in the address table, that is, a virtual number
// The actual number = virtual number + number base (starting number)
printf(" Number:【%d】 ", i + exporttable->Base);
}
// When there is no exported function name, it is exported using the number
if (bIndexIsExist == FALSE)
{
// Determine if the current index of the address table is saving an address
if (pAddressTable[i] != 0) {
printf(" Function name:【-】\t");
// i: It is the index number in the address table, that is, a virtual number
// The actual number = virtual number + number base
printf(" Number:【%d】", i + exporttable->Base);
}
}
printf("\n");
}
Instance
Here is a standard simple dll with 6 methods
To distinguish between number export and name export, define myFunc as 1 and Add as 2 in Source.def
Then the program executes and judges the export method of specific methods in the export table of petest.dll
It can be seen that the actual sequence numbers 1 and 2 are the myFunc and Add defined by us, and the other functions are named exports.
Base: This field contains the starting sequence number (base) used for the output table of this PE file. In normal circumstances, this value is 1.
When querying an output function by number, this value is subtracted from the number, and the result will be used as the index to enter the output address table (EAT).
For example: The value of Base is 1, the actual sequence number (virtual sequence number) of the function with the serial number 5 is 5-1=4, so you can find the corresponding export function by searching for the index 4 in EAT.
Summary
1. The export table is the information provided by this PE file for the APIs that can be used externally.
2. Determine the export method by the correspondence between AddressOfNames and AddressOfNameOrdinals.
3. In EAT Hook, it is through modifying the export function that the hook is completed when another process calls the function of this PE file. It is necessary to modify the PE file on the disk itself.
IMPORT_TABLES Import table
Executable files that use code or data from other DLLs are called imports.
The import table saves the information needed for dynamic linking, such as function names and required DLL names.
The import table plays an important role in obfuscation techniques.
Import functions
Import functions are functions called by the program but whose execution code is not in the program, located in the related DLLs.
For PE files on disk, it is impossible to know the addresses of these import functions in memory. After the PE file is loaded into memory, the Windows loader will load the related DLLs according to the import table, and link the calling functions and related addresses.
Implicit and explicit loading
When a program calls a DLL's code or data, the program is implicitly linked to the DLL, and the process is completely completed by the Windows loader.
Explicitly, the target DLL has been loaded, and then the address of the API is searched, which is almost done by callingLoadLibrary()
andGetProcAddress()
Completed.
Among which there is a set of data structures, each corresponding to the imported DLL.
IMAGE_IMPORT_DESCRIPTOR
1. The second directory in the data directory, namelyIMAGE_DIRECTORY_ENTRY_IMPORT
2. Each DLL corresponds to aIMAGE_IMPORT_DESCRIPTOR
Structure, the imported DLL file is associated withIMAGE_IMPORT_DESCRIPTOR
It is a one-to-one relationship
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; // RVA of the import name table
};DUMMYUNIONNAME;
DWORD TimeDateStamp; // Timestamp, can be used to determine whether the function address is bound
DWORD ForwarderChain; // This field is generally 0
DWORD Name; // This field is a pointer to the DLL name, and this pointer is also an RVA
DWORD FirstThunk; // RVA of the import address table (IAT), IAT
};IMAGE_IMPORT_DESCRIPTOR;
OriginalFirstThun: Contains the RVA pointing to the import entry table (INT). The Import Name Table is aIMAGE_THUNK_DATA
Array of structures.
TimeDateStamp: A 32-bit time stamp
ForwarderChain is generally 0
Name: Pointer to the DLL name. The characters contain the input DLL name.
FirstThunk: Contains the RVA pointing to IAT.
OriginalFirstThun and FirstThunk are similar and point to two essentially the sameIMAGE_THUNK_DATA
Structure, that is, INT and IAT.
IMAGE_THUNK_DATA
typedef struct _IMAGE_THUNK_DATA32{
union {
PBYTE ForwarderString; // Points to an RVA of a forwarder string
PDWORD Function; // The input function memory address
DWORD Ordinal; // The input API sequence number value
PIMAGE_IMPORT_BY_NAME AddressOfData; // Points to the RVA of IMAGE_IMPORT_BY_NAME
};u1;
};IMAGE_THUNK_DATA32;
Function: the input function memory address
Ordinal: the input API sequence number value
AddressOfData: points toIMAGE_IMPORT_BY_NAME
structure
IMAGE_THUNK_DATA occupies 4 bytes, whenIMAGE_THUNK_DATA
When the highest bit is 1, it indicates that the function is imported in the form of
sequence number is imported directly, and the value of the remaining 31 bits is the export sequence number of the dll function in the export table;
When the highest bit is 0, it indicates that the function is imported in the form of a function name string. At this time, the double word_IMAGE_THUNK_DATA
The value represents a RVA and points toIMAGE_IMPORT_BY_NAME
Structure.
The following figure identifies an executable file that imports some API functions from "USER32.dll".
That is to say inIMAGE_THUNK_DATA
When the high-order bits are different, the content pointed to by the same address may vary. The high-order bit is IAT, and the low-order bit is INT; both pointers point to the same content.IMAGE_IMPORT_BY_NAME
Here, the addresses of originalFirstThunk and FirstThunk are output simultaneously, and it can be seen that the output points to the same address,}}
Now it looks like the two addresses are the same, but after the DLL completes the function loading,FirstThunk
Will be rewritten by the PE loader
First search OriginalFirstThunk, if found, the loader iterates over each pointer in the array to find eachIMAGE_IMPORT_BY_NAME
The input function address pointed to by the structure, and then the actual entry address of the loader function is used to replace
The address pointed to by FirstThunkIMAGE_THUNK_DATA
Structure.
It can be understood that after the loading is completed, FirstThunk will be rewritten to point to the actual address of the loaded API.
After loading into memory, the content of the import table will be filled with the address of the new function by the operating system.
IMAGE_IMPORT_BY_NAME
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //Indicates the sequence number of this function in the export table of its DLL
CHAR Name[1]; //Function name
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Hint: The sequence number of this function in the output table of its DLL. It is used for quick query of functions and is not necessary.
Name: The function name containing the input function.
Code to get the export table
Import Name Address(INT)
printf("\nImport table:\n");
//Find the virtual address of Import Directory through NTHeaders
DWORD Rva_importdata = ReadNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if (Rva_importdata!=0)
{
//Convert RVA to FOA
DWORD importTableOffset = VA2Offset(Rva_importdata + imagebase, ReadNTHeaders, m_pSecHdr);
if (importTableOffset > 0)
{
HMODULE library = NULL;
PIMAGE_IMPORT_BY_NAME functionName = NULL;
int i = 0;
LPCSTR libraryName;
PIMAGE_IMPORT_DESCRIPTOR import_descrip = (PIMAGE_IMPORT_DESCRIPTOR)(importTableOffset + buffer);
//Print Import Name Table information
while (import_descrip->Name)
{
//Calculate the FOA of Name and directly print the dll name
libraryName = (LPCSTR)(VA2Offset(import_descrip->Name + imagebase, ReadNTHeaders, m_pSecHdr) + buffer);
printf("dll name:%s\n", libraryName);
//Load the dll using LoadLibray
library = LoadLibrary(libraryName);
if (library)
{
PIMAGE_THUNK_DATA originalFirstThunk = NULL, firstThunk = NULL;
//Calculate the FOA of the INT import name table
originalFirstThunk = (PIMAGE_THUNK_DATA)(VA2Offset(import_descrip->OriginalFirstThunk + imagebase, ReadNTHeaders, m_pSecHdr) + buffer);
firstThunk = (PIMAGE_THUNK_DATA)(VA2Offset(import_descrip->FirstThunk + imagebase, ReadNTHeaders, m_pSecHdr) + buffer);
//Determine if the most significant bit is 0, indicating input by name, taking the 64-bit system as an example
//Determine if the most significant bit of IMAGE_THUNK_DATA is 0, indicating the ordinal value of the API input
if (!(originalFirstThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG))
{
int j = 1;
//Determine if the most significant bit of the double-word value of PIMAGE_THUNK_DATA is 1, indicating input by index
while (originalFirstThunk->u1.AddressOfData != NULL)
{
//指向IMAGE_IMPORT_BY_NAME的RVA
functionName = (PIMAGE_IMPORT_BY_NAME)(VA2Offset(originalFirstThunk->u1.AddressOfData + imagebase, ReadNTHeaders, m_pSecHdr) + buffer);
printf("The function name is imported in the form of a string:%s\n", functionName->Name);
//The memory address of the input function
printf("The memory address of the function pointed to by originalFirstThunk of the input function:%x\n", originalFirstThunk->u1.Function);
printf("The memory address of the function pointed to by FirstThunk of the input function:%x\n", firstThunk->u1.Function);
//指向IMAGE_IMPORT_BY_NAME的RVA
printf("指向IMAGE_IMPORT_BY_NAME的RVA:%08x\n", firstThunk->u1.AddressOfData);
printf("\n\n");
++originalFirstThunk;
++firstThunk;
++j;
}
}
//以序号方式导入
else
{
int j = 1;
while (originalFirstThunk->u1.Ordinal != NULL)
{
DWORD dwOrdinal = originalFirstThunk->u1.Ordinal & ~IMAGE_ORDINAL_FLAG;
printf("序号方式导入:%d", dwOrdinal);
++originalFirstThunk;
++firstThunk;
++j;
}
}
}
++import_descrip;
}
}
}
Import Address Tables(IAT)
IMAGE_IMPORT_DESCRIPTOR
中我们知道FirstThunk
指向另外一个IMAGE_THUNK_DATA
也就是IAT,这里可以直接通过NTHeader调用DataDirectory[]来直接获取IAT的RVA地址,也可以和前面一样通过找到指向IMAGE_IMPORT_DESCRIPTOR
的指针来找到FirstThunk
.
//Import Address Tables 导入地址表
printf("\nIAT:\n");
//通过IMAGE_DIRECTORY_ENTRY_IAT序号来找到FirstThunk的RVA
//DWORD iatTable = ReadNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress;
if (Rva_importdata != 0)
{
//RVA转换成FOA
DWORD importTableOffset = VA2Offset(Rva_importdata + imagebase, ReadNTHeaders, m_pSecHdr);
if (importTableOffset > 0)
{
HMODULE library = NULL;
PIMAGE_IMPORT_BY_NAME functionName = NULL;
int i = 0;
LPCSTR libraryName;
PIMAGE_IMPORT_DESCRIPTOR import_descrip = (PIMAGE_IMPORT_DESCRIPTOR)(importTableOffset + buffer);
DWORD iatTable = import_descrip->FirstThunk;
DWORD iatTable2 = ReadNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress;
printf("iatTable:%x iatTable2:%x\n", iatTable, iatTable2);
PIMAGE_THUNK_DATA piat = (PIMAGE_THUNK_DATA)(VA2Offset((iatTable + imagebase), ReadNTHeaders, m_pSecHdr) + buffer);
//u1 is the naming of IMAGE_THUNK_DATA, where Function represents the memory address of the imported function
while (piat->u1.Function != NULL)
{
//Add buffer to output memory address
FARPROC fpAddress = (FARPROC)(buffer + piat->u1.Function);
printf("fpAddress:%x\n", fpAddress);
piat++;
}
}
}
Both addresses are the same
DLL Loading API
The LoadLibrary() function is used to load a specified DLL file into the process space and return a handle to the DLL. By calling the LoadLibrary() function, an application can dynamically link and load a DLL at runtime, thus enabling the use of functions and data within the DLL.
The GetProcAddress() function is used to locate and return the address of a specified function in a DLL loaded into the process space. By calling the GetProcAddress() function, an application can obtain the address of a function in a DLL at runtime and then call these functions through function pointers.
Hidden import table
Using a tool calleddumpbin.exe
The PE file can be easily viewed with the import/export tabledumpbin.exe /import name.exe
Here we can see the export DLLs needed by our program and the corresponding APIs
Some antivirus software will judge whether a sensitive function has been called through the export table
To implement the hidden import table, we first need to find the type definition of the sensitive function
For exampleHeapCreate
Create an API handle that can be used by the process
Then declare and define it in the code
To ensure that we can find the API address, we useLoadLibray()
to open the DLLs belonging to these APIs
{Generally, Windows itself loads some commonly used dlls like kernel32.dll, ntdll.dll, USER32.dll, etc.}
Then use GetProcAddress() to find the address of the corresponding API, and we can definitely find it
Now that we have the address of the API, we can directly call the function with the same functionality but not written to the import table API
As can be seen, after hiding the operation, USER32.dll is not exported, and the previously usedHeapCreate
,HeapAlloc
The API does not exist anymore, but Loadlibray and GetProcAddress will be imported
Summary
1. The main function of the import table is to save the relevant information of the function methods required by the PE file to use other DLLs.
2. The INT and IAT point to the same structure. When the Windows Loader has not finished loading the DLL, that is, when we see the addresses OriginalFirstThunk and FirstThunk pointing to the same address; after loading, the Windows Loader will write the function addresses to the Image Thunk Data structure and rewrite the FirstThunk to point to it.
3. The IAT Hook is achieved by modifying the function addresses in the import table of the PE file. When the PE file starts, it calls the functions in the import table, and at this point, the Hook is completed. This is done during the dynamic loading of the program, and it can be completed in memory.
4. The EAT Hook is more difficult to implement and more dangerous than the IAT Hook, because it can affect all processes in the system that call the export function of the PE file, while the IAT Hook only affects the calls within the current process.

评论已关闭