DEV Community

wireless90
wireless90

Posted on

Exploring the Export Table [Windows PE Internals]

Previous Windows PE Internals Writeups

  1. Creating a Windows Project in Visual Studio
  2. Getting a Handle to a Dynamically Linked Library
  3. Validating the MZ Signature
  4. Validating the PE Signature
  5. Dissecting the PE Header

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.

image

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;
Enter fullscreen mode Exit fullscreen mode

If we take a look at the PE File Format,

image

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

image

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.

image

Thus to get the export table directory, we would do

PIMAGE_DATA_DIRECTORY imageExportDataDirectory = &(imageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
Enter fullscreen mode Exit fullscreen mode

Our Export Data Directory contains,
image
just as seen from

image.

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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++)
{
}
Enter fullscreen mode Exit fullscreen mode

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]);
}
Enter fullscreen mode Exit fullscreen mode

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)
     {
     }
}
Enter fullscreen mode Exit fullscreen mode

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]);
     }
}
Enter fullscreen mode Exit fullscreen mode

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);
     }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)