Exploring the Export Table [Windows PE Internals]

Previous Windows PE Internals Writeups

Previously

In the previous article, we learnt about the contents of the PE Header.

Let's now take a look at the Optional Header, more particularly, the Export Table.

Let's Begin

An export table contains functions that have been exported and could be used by other programs. We will be focusing on the exported functions of the library user32.dll.

In the previous article, we managed to retrieve the IMAGE_NT_HEADERS.

We can see that this structure contains the FileHeader, OptionalHeader and Signature.

We are interested in the OptionalHeader.

PIMAGE_OPTIONAL_HEADER imageOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&imageNtHeaders->OptionalHeader;

If we take a look at the PE File Format,

we can see that the OptionalHeader consist of a segment called DataDirectories. We want to get the ExportTable Data Directory.

There is a DataDirectory array in the OptionalHeader. We can then obtain by using the Export Table Index Macro called IMAGE_DIRECTORY_ENTRY_EXPORT defined inside winnt.h.

Thus to get the export table directory, we would do

PIMAGE_DATA_DIRECTORY imageExportDataDirectory = &(imageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);

Our Export Data Directory contains,
image
just as seen from

The field VirtualAddress is relative to the PE Base address.

As we learnt from the previous article, the Relative Virtual Address(RVA) has to be added to the pe base address to get to the actual structure.

In order to obtain the Export Directory structure, we would thus do,

PIMAGE_EXPORT_DIRECTORY imageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((unsigned char*)peBase + imageExportDataDirectory->VirtualAddress);

Now what we are interested is in the ExportAddressTable, NameOrdinalsArray and NameAddressArray.

They can be retrived via

DWORD numberOfNames = imageExportDirectory->NumberOfNames;
PDWORD exportAddressTable = (PDWORD)((unsigned char*)peBase + imageExportDirectory->AddressOfFunctions);
PWORD nameOrdinalsPointer = (PWORD)((unsigned char*)peBase + imageExportDirectory->AddressOfNameOrdinals);
PDWORD exportNamePointerTable = (PDWORD)((unsigned char*)peBase + imageExportDirectory->AddressOfNames);

Now how do we work with these? This can be pretty confusing.
Our objective for this article, is to find the pointer to the MessageBoxA function through the export address table, and invoke it.

Since we know the numberOfNames Let's first create a loop.

int nameIndex = 0;
for (nameIndex = 0; nameIndex < numberOfNames; nameIndex++)
{
}

The exportNamePointerTable above contains an array of RVAs to function names.

We can get the name of through the RVA from the exportNamePointerTable.

int nameIndex = 0;
for (nameIndex = 0; nameIndex < numberOfNames; nameIndex++)
{
        char* name = (char*)((unsigned char*)peBase + exportNamePointerTable[nameIndex]);
}

We can check if it is the function that we want.

int nameIndex = 0;
for (nameIndex = 0; nameIndex < numberOfNames; nameIndex++)
{
     char* name = (char*)((unsigned char*)peBase + exportNamePointerTable[nameIndex]);
     if (strcmp("MessageBoxA", name) == 0)
     {
     }
}

Now for the tricky part, the we need to get the correct index to get the address of the function from our exportAddressTable. The index is called, an ordinal. This ordinal is retrieved by using the nameIndex as an index to the nameOrdinalsPointer above.

int nameIndex = 0;
for (nameIndex = 0; nameIndex < numberOfNames; nameIndex++)
{
     char* name = (char*)((unsigned char*)peBase + exportNamePointerTable[nameIndex]);
     if (strcmp("MessageBoxA", name) == 0)
     {
            WORD ordinal = nameOrdinalsPointer[nameIndex];
            PDWORD targetFunctionAddress = (PDWORD)((unsigned char*)peBase + exportAddressTable[ordinal]);
     }
}

Next, we can create a function pointer that follows the signature of the function that we want to invoke.

int nameIndex = 0;
for (nameIndex = 0; nameIndex < numberOfNames; nameIndex++)
{
     char* name = (char*)((unsigned char*)peBase + exportNamePointerTable[nameIndex]);
     if (strcmp("MessageBoxA", name) == 0)
     {
            WORD ordinal = nameOrdinalsPointer[nameIndex];
            PDWORD targetFunctionAddress = (PDWORD)((unsigned char*)peBase + exportAddressTable[ordinal]);

            typedef int (WINAPI* MyFunction)(HWND, LPCSTR, LPCSTR, UINT);
            MyFunction myFunction = (MyFunction)targetFunctionAddress;
            (*myFunction)(0, "asd", 0, 0);
     }
}

The overall code looks like,

#include <Windows.h>

int  WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR     lpCmdLine,
    int       nCmdShow
)
{
    HMODULE peBase = GetModuleHandleA("user32.dll");

    if (peBase == NULL)
    {
        MessageBoxA(0, "Can't load user32.dll", "Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    PIMAGE_DOS_HEADER imageDosHeader = (PIMAGE_DOS_HEADER)peBase;

    if (imageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        MessageBoxA(0, "user32.dll has the wrong Image Dos Signature!", "Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    PIMAGE_NT_HEADERS imageNtHeaders = (PIMAGE_NT_HEADERS)((unsigned char*)imageDosHeader + imageDosHeader->e_lfanew);


    if (imageNtHeaders->Signature != IMAGE_NT_SIGNATURE)
    {
        MessageBoxA(0, "user32.dll has the wrong PE Signature!", "Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    PIMAGE_OPTIONAL_HEADER imageOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&imageNtHeaders->OptionalHeader;

    PIMAGE_DATA_DIRECTORY imageExportDataDirectory = &(imageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);

    PIMAGE_EXPORT_DIRECTORY imageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((unsigned char*)peBase + imageExportDataDirectory->VirtualAddress);

    DWORD numberOfNames = imageExportDirectory->NumberOfNames;

    PDWORD exportAddressTable = (PDWORD)((unsigned char*)peBase + imageExportDirectory->AddressOfFunctions);
    PWORD nameOrdinalsPointer = (PWORD)((unsigned char*)peBase + imageExportDirectory->AddressOfNameOrdinals);
    PDWORD exportNamePointerTable = (PDWORD)((unsigned char*)peBase + imageExportDirectory->AddressOfNames);

    char buffer[1024 * 20] = { 0 };
    int nameIndex = 0;
    for (nameIndex = 0; nameIndex < numberOfNames; nameIndex++)
    {
        char* name = (char*)((unsigned char*)peBase + exportNamePointerTable[nameIndex]);
        if (strcmp("MessageBoxA", name) == 0)
        {
            WORD ordinal = nameOrdinalsPointer[nameIndex];
            PDWORD targetFunctionAddress = (PDWORD)((unsigned char*)peBase + exportAddressTable[ordinal]);

            typedef int (WINAPI* MyFunction)(HWND, LPCSTR, LPCSTR, UINT);
            MyFunction myFunction = (MyFunction)targetFunctionAddress;
            (*myFunction)(0, "asd", 0, 0);
        }
    }

    return 0;
}

17