17
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 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