31
Exploring the Export Table [Windows PE Internals]
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
. 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]);

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
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;
}
31