IMPORT_TABLES Import table

0 22
Most EXE files in PE files only have import tables and no export tables, unless...

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
IMPORT_TABLES Import table
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
image
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
image
image

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

image

// 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
image
To distinguish between number export and name export, define myFunc as 1 and Add as 2 in Source.def
image
Then the program executes and judges the export method of specific methods in the export table of petest.dll
image
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_DESCRIPTORStructure, the imported DLL file is associated with
IMAGE_IMPORT_DESCRIPTORIt 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_DATAArray 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_DATAStructure, 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_NAMEstructure

IMAGE_THUNK_DATA occupies 4 bytes, whenIMAGE_THUNK_DATAWhen 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_DATAThe value represents a RVA and points toIMAGE_IMPORT_BY_NAMEStructure.

The following figure identifies an executable file that imports some API functions from "USER32.dll".
That is to say inIMAGE_THUNK_DATAWhen 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
image

Here, the addresses of originalFirstThunk and FirstThunk are output simultaneously, and it can be seen that the output points to the same address,}}
image
Now it looks like the two addresses are the same, but after the DLL completes the function loading,FirstThunkWill be rewritten by the PE loader
First search OriginalFirstThunk, if found, the loader iterates over each pointer in the array to find each
IMAGE_IMPORT_BY_NAMEThe 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_DATAStructure.

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

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;

                }

            }

        }

image

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
image

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.exeThe PE file can be easily viewed with the import/export table
dumpbin.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
image
To implement the hidden import table, we first need to find the type definition of the sensitive function
For exampleHeapCreateCreate an API handle that can be used by the process
image
Then declare and define it in the code
image
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.}
image
Then use GetProcAddress() to find the address of the corresponding API, and we can definitely find it
image
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,HeapAllocThe API does not exist anymore, but Loadlibray and GetProcAddress will be imported
image

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.

你可能想看:
最后修改时间:
admin
上一篇 2025年03月30日 05:25
下一篇 2025年03月30日 05:48

评论已关闭