Export Address Table , Export Forwarding and DLL side-loading

The Export Address Table (EAT) is a component of the Portable Executable (PE) file format, which is the executable file format used by Windows for 32-bit and 64-bit versions of the operating system. The PE format is utilized for executable files, object code, DLLs (Dynamic Link Libraries), and others.

The Export Address Table is specifically related to the exports section of a PE file. When a dynamic-link library (DLL) or executable file exports functions or symbols, it means that these functions or symbols are made available for use by other programs or modules. The Export Address Table contains the addresses of these exported functions or symbols.

Here's a brief overview of how the Export Address Table works:

Export Directory: The PE format includes an export directory that contains information about the exported symbols. This directory is typically located in the data directory of the PE file.

Export Address Table (EAT): The EAT is an array of addresses that correspond to the exported functions or symbols. Each entry in the EAT points to the memory address where the exported function resides.

When a program or module dynamically links to a DLL, it can use the information in the EAT to locate and invoke the exported functions. The EAT essentially serves as a lookup table for the addresses of exported functions, making it possible for other programs to use these functions without having to know their exact memory addresses.

It's important to note that the PE file format is a complex structure with various sections and tables that play crucial roles in the execution of programs on the Windows operating system. The Export Address Table is just one component of this larger structure.

The Export table address can be located in PE headers

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

and for 64bit PE files

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

The Virtual Address of Export directory can be obtained from OptionalHeader. using the field

An array of IMAGE_DATA_DIRECTORY structures named DataDirectory is defined, with a size of IMAGE_NUMBEROF_DIRECTORY_ENTRIES. The index number of the directory entry IMAGE_DIRECTORY_ENTRY_EXPORT is then utilized to access the Export table address.

Within the IMAGE_DATA_DIRECTORY structure, two fields are present: VirtualAddress, which provides the Relative Virtual Address (RVA) in memory of the Export table address, and the size field.

typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

In order to access the Export table address in the loaded binary, add IMAGE_DATA_DIRECTORY.VirtualAddress to the BaseAddress of the binary. This action leads to the actual Export Address Table.

typedef struct _IMAGE_EXPORT_DIRECTORY
  {
    DWORD Characteristics;
    DWORD TimeDateStamp;
    WORD MajorVersion;
    WORD MinorVersion;
    DWORD Name;
    DWORD Base;
    DWORD NumberOfFunctions;
    DWORD NumberOfNames;
    PDWORD *AddressOfFunctions;
    PDWORD *AddressOfNames;
    PWORD *AddressOfNameOrdinals;

  }
IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

lets try to explore a basic skeleton of IMAGE_EXPORT_DIRECTORY which hosts a simple function PlainFuncName

#define WIN32_LEAN_AND_MEAN
#include 

BOOL WINAPI PlainFuncName( void * lpParams)

{
    return TRUE;
}

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,
    DWORD fdwReason,
    LPVOID lpvReserved )
{
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH:
            break;

        case DLL_THREAD_ATTACH:
            break;

        case DLL_THREAD_DETACH:
            break;

        case DLL_PROCESS_DETACH:
            if (lpvReserved != nullptr)
            {
                break;
            }
            break;
    }
    return TRUE;
}

To mark exports in the dll we will use DEF file

A .def file, short for Module-Definition File, is a text file that contains instructions for the linker. It provides additional information about the DLL (Dynamic Link Library) or executable file being built, including details such as exported functions, imported functions, and other settings.

; DEF File 
EXPORTS
  PlainFuncName 

For compilation we will use MSVC Link

link filename.obj /DLL /nodefaultlib /noentry /def:filename.def

now lets try to examine how the structure looks in hex

Pretty basic one .

  • Name - Corresponds to the name of the DLL .
  • AddressOfFunctions - Address of Subroutines
  • AddressOfNames - Address of Names of the functions
  • AddressOfNameOrdinals - Address of Ordinals ( discussed later )

DEF format primer

In a .def (Module-Definition) file for a DLL (Dynamic Link Library) in the MSVC (Microsoft Visual C++) compiler, you can control various aspects related to exported functions. Here's a summary of what you can do with exported functions in a .def file:

1. Export Function Names:

LIBRARY MyLibrary
EXPORTS
    MyFunction1
    MyFunction2

This exports MyFunction1 and MyFunction2 from the DLL. These functions will be accessible to other modules that link against the DLL.

2. Specify Ordinals:

LIBRARY MyLibrary
EXPORTS
    MyFunction1 @1
    MyFunction2 @2

You can assign explicit ordinals to exported functions. This sets the ordinals for MyFunction1 and MyFunction2 to 1 and 2, respectively.

3. Decorate Names:

LIBRARY MyLibrary
EXPORTS
    MyFunction1 @1
    "?DecoratedFunction2@@YAXXZ" @2

You can specify the decorated names for functions, which can be useful when interfacing with functions written in languages other than C or C++.

3. Forwarding Functions:

LIBRARY MyLibrary
EXPORTS
    MyFunction1 = OtherLibrary.MyFunction1

You can use a .def file to create forwarding entries, redirecting calls from one function to another. This forwards calls to MyFunction1 in your DLL to OtherLibrary.MyFunction1.

Exports by Ordinal

One of the features of EAT (Export Address Table) is the creation of exports by ordinal instead of a name. It can be easily defined in a DEF file using

EXPORTS
    PlainFuncName @1 NONAME

In the PE file:

  • NumberOfNames
  • AddressOfNames
  • AddressOfNameOrdinals

will all be set to 0; only AddressOfFunctions RVA would be present.

An export with an ordinal can be called using the following code:

#include 
#include 

int main() {
    // Load the DLL
    HMODULE hDll = LoadLibrary(L"YourDllName.dll");

    if (hDll != NULL) {
        // Ordinal value of the function you want to call
        // Replace 1 with the actual ordinal value
        DWORD ordinalValue = 1;

        // Get the function pointer by ordinal
        FARPROC functionPointer = GetProcAddress(hDll, MAKEINTRESOURCEA(ordinalValue));

        if (functionPointer != NULL) {
            // Define the function signature
            typedef void(__stdcall* MyFunctionType)();

            // Cast the function pointer to the correct type
            MyFunctionType myFunction = (MyFunctionType)functionPointer;

            // Call the function
            myFunction();
        } else {
            printf("GetProcAddress failed with error code: %lu\n", GetLastError());
        }

        // Unload the DLL
        FreeLibrary(hDll);
    } else {
        printf("LoadLibrary failed with error code: %lu\n", GetLastError());
    }

    return 0;
}

Export Forwarding

One of the hallmarks of the export address table in Windows PE is the ability to forward exports in another DLL.

Export forwarding in Portable Executable (PE) files refers to a mechanism by which an exported function from a dynamic-link library (DLL) can be redirected to another DLL. It allows one DLL to act as a proxy or intermediary, forwarding the exported functionality to another DLL. This can be used for various purposes, including version compatibility, flexibility in choosing implementations, or modularizing code. However it has been abused by APT to take leverage of legit programs to execute malicious code

Loading Process:

  • When the application starts, it searches for the required DLL.
  • If the DLL is not found in the application's directory, the system directory, or the PATH, it will search the current directory and other directories in the specified order.
  • If a malicious DLL with the same name is found in one of these directories, it will be loaded instead of the legitimate DLL.
  • A legitimate application calls a function in a DLL, and it is proxied to the legitimate DLL. Moreover, malicious code is executed.

All of this is made possible using Export Forwarding

EXPORTS
    MyFunction1 = user32.MessageBeep 

The above DEF file will create an export named MyFunction1 and forward it to the user32.dll MessageBeep function. Any call to MyFunction1 will eventually land on MessageBeep().

The changes we see in the Export Address table is that the AddressOfFunctions value now points to a pointer to an ASCII string, which is the dll.functionname (i.e., user32.MessageBeep).

Wrapping up

Here is a small script to parse any forwarder exports in a dll

import pefile

def parse_dll_forward_exports(file_path):
    try:
        pe = pefile.PE(file_path)

        if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
            export_directory = pe.DIRECTORY_ENTRY_EXPORT
            for export in export_directory.symbols:
                if export.forwarder:
                    print(f"Function: {export.name} (Forwarded to: {export.forwarder})")

    except Exception as e:
        print(f"Error parsing PE file: {e}")

if __name__ == "__main__":
    # Replace 'your_dll_file.dll' with the path to your PE file
    dll_file_path = 'your_dll_file.dll'
    parse_dll_forward_exports(dll_file_path)

MalwareID