Export table of PE file (IMAGE_EXPORT_DIRECTORY)

catalogue

Export table positioning and parsing (manual)

The code locates the exported table and prints its data

Find the address of the exported function by function name

Find the function address by exporting the function sequence number

Move export table

The export table in PE usually exists in the dynamic link library file. Some exes also have export tables. The main function of export tables is to export the functions existing in PE to the outside, so that others can use these functions to realize code reuse. The existence of export tables makes it easy for program developers to know how many functions can be used in PE

During PE loading, the Windows loader loads the DLL related to the process into the corresponding virtual address space. It will traverse the virtual address space where the DLL is located according to the name or number pointed by INT related to the dynamic link library registered in the import table, and find the export table structure through the function name or number, so as to determine the starting address VA of the export function in the virtual address space, And overwrite the IAT related items of the import table

The section where the exported data is located is usually named. edata, which contains information about symbols that can be accessed by other EXE programs, such as export functions and resources. These symbols usually appear in DLLs, but DLLs can also contain import symbols, and export symbols can also be found in some EXE programs

Export table positioning and parsing (manual)

IMAGE_EXPORT_DIRECTORY

Structure and members have the following meanings:

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory

typedef struct _IMAGE_EXPORT_DIRECTORY {              
    DWORD   Characteristics;        // not used      
    DWORD   TimeDateStamp;          // time stamp      
    WORD    MajorVersion;           // not used      
    WORD    MinorVersion;           // not used      
    DWORD   Name;                   // The file name string RVA that points to the exported table    
    DWORD   Base;                   // The starting sequence number of the exported function      
    DWORD   NumberOfFunctions;      // Number of all exported functions      
    DWORD   NumberOfNames;          // Number of functions exported by function name      
    DWORD   AddressOfFunctions;     // Export function address table RVA              
    DWORD   AddressOfNames;         // Export function name table RVA              
    DWORD   AddressOfNameOrdinals;  // Export function sequence number table RVA               
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;              

Locate export table:

IMAGE_OPTIONAL_HEADER ->   DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT(0)].VirtualAddress + ImageBase. (if parsing in a file, you only need to convert RVA to FOA and add the current file header address to locate the export table)

View the default properties of PE file through WinHex tool

The above analysis shows that:

Memory alignment = 1000h

File alignment = 200h

Export table RVA = 0x00116640

Convert to FOA=   0x000C8E40( Explanation of RVA and FOA conversion)

Member meaning:

Characteristics; / / not used

TimeDateStamp; / / timestamp

MajorVersion; / / not used

MinorVersion; / / not used

Name; / / refers to the file name string of the exported table. The value is RVA

Base; / / export the function start sequence number. (the default export sequence number starts from 1, but the function export sequence number can be customized through the xxx.def file)

NumberOfFunctions; / / the number of all exported functions. This value is not necessarily the number of exported functions. The calculation formula is: export Max Sn - export min Sn + 1

I wrote this DLL and tested it myself. The actual exported functions are 4. The sequence numbers are 2,4,5,6. (6 - 2 + 1 is the result of NumberOfFunctions)

NumberOfNames; / / the number of functions exported with the function name. (you can define that the function does not export the name through the xxx.def file)

The test DLL. DEF is defined as follows:

LIBRARY
  EXPORTS
    Add     @2
    Sub     @6
    Mul     @4 NONAME
    Div     @5

AddressOfFunctions; / / export the function address table. The value is RVA. This member is equivalent to pointing to an array with an element size of 4 bytes. The number of elements is provided by NumberOfFunctions. Each element is the RVA of the exported function address (the real address of the function is the ImageBase)

AddressOfNames; / / export the function name table. The value is RVA. This member is equivalent to pointing to an array with an element size of 4 bytes. The number of elements is provided by NumberOfNames. Each element is the RVA of the exported function name

AddressOfNameOrdinals; / / export the function sequence number table. This value is RVA. This member is equivalent to pointing to an array with an element size of 2 bytes. The number of elements is provided by NumberOfNames. The element value plus Base is the function export sequence number. This value is required to find the function address through the function name

The full solution of the above analysis is shown in the figure below:

The export table is relatively simple. You only need to understand the structure and meaning of the three tables, and it is easy to understand their corresponding relationship

The code locates the exported table and prints its data

Read file code

PVOID FileToMem(IN PCHAR szFilePath, OUT LPDWORD dwFileSize)
{
	//Open file
	FILE* pFile = fopen(szFilePath, "rb");
	if (!pFile)
	{
		printf("FileToMem fopen Fail \r\n");
		return NULL;
	}
 
	//Get file length
	fseek(pFile, 0, SEEK_END);			//End of SEEK_END file
	DWORD Size = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);			//Start of SEEK_SET file
 
	//Request to store file data buffer
	PCHAR pFileBuffer = (PCHAR)malloc(Size);
	if (!pFileBuffer)
	{
		printf("FileToMem malloc Fail \r\n");
		fclose(pFile);
		return NULL;
	}
 
	//Read file data
	fread(pFileBuffer, Size, 1, pFile);
 
	//Determine whether it is an executable file
	if (*(PSHORT)pFileBuffer != IMAGE_DOS_SIGNATURE)
	{
		printf("Error: MZ \r\n");
		fclose(pFile);
		free(pFileBuffer);
		return NULL;
	}
 
	if (*(PDWORD)(pFileBuffer + *(PDWORD)(pFileBuffer + 0x3C)) != IMAGE_NT_SIGNATURE)
	{
		printf("Error: PE \r\n");
		fclose(pFile);
		free(pFileBuffer);
		return NULL;
	}
 
	if (dwFileSize)
	{
		*dwFileSize = Size;
	}
 
	fclose(pFile);
 
	return pFileBuffer;
}

Output file code

VOID MemToFile(IN PCHAR szFilePath, IN PVOID pFileBuffer, IN DWORD dwFileSize)
{
	//Open file
	FILE* pFile = fopen(szFilePath, "wb");
	if (!pFile)
	{
		printf("MemToFile fopen Fail \r\n");
		return;
	}
 
	//output file
	fwrite(pFileBuffer, dwFileSize, 1, pFile);
 
	fclose(pFile);
}

Locate and export table data

VOID PrintExportInfo()
{
	//Read file binary data
	DWORD dwFileSize = 0;
	PCHAR pFileBuffer = FileToMem(FILE_PATH_IN, &dwFileSize);
	if (!pFileBuffer)
	{
		return;
	}

	//Positioning structure
	PIMAGE_DOS_HEADER        pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS        pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
	PIMAGE_FILE_HEADER		 pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
	PIMAGE_OPTIONAL_HEADER   pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);

	//Judge whether the PE file has an export table
	if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
	{
		printf("Should PE The file does not have an export table \r\n");
		return;
	}
	PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));

	//Basic export table information
	printf("IMAGE_EXPORT_DIRECTORY.Characteristics  -> [0x%08x] \r\n", pExp->Characteristics);
	printf("IMAGE_EXPORT_DIRECTORY.TimeDateStamp  -> [0x%08x] \r\n", pExp->TimeDateStamp);
	printf("IMAGE_EXPORT_DIRECTORY.MajorVersion  -> [0x%04x] \r\n", pExp->MajorVersion);
	printf("IMAGE_EXPORT_DIRECTORY.MinorVersion  -> [0x%04x] \r\n", pExp->MinorVersion);
	printf("IMAGE_EXPORT_DIRECTORY.Name -> [%s] \r\n", pFileBuffer + RvaToFoa(pFileBuffer, pExp->Name));
	printf("IMAGE_EXPORT_DIRECTORY.Base  -> [0x%08x] \r\n", pExp->Base);
	printf("IMAGE_EXPORT_DIRECTORY.NumberOfFunctions  -> [0x%08x] \r\n", pExp->NumberOfFunctions);
	printf("IMAGE_EXPORT_DIRECTORY.NumberOfNames  -> [0x%08x] \r\n", pExp->NumberOfNames);

	//Function address table
	printf("IMAGE_EXPORT_DIRECTORY.AddressOfFunctions  -> [0x%08x] \r\n", pExp->AddressOfFunctions);
	LPDWORD pAddFunc = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions));
	for (size_t i = 0; i < pExp->NumberOfFunctions; i++)
	{
		printf("AddressOfFunctions[%d] -> [0x%08x] \r\n", i, pAddFunc[i]);
	}

	//Function name table
	printf("IMAGE_EXPORT_DIRECTORY.AddressOfNames  -> [0x%08x] \r\n", pExp->AddressOfNames);
	LPDWORD pAddName = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNames));
	for (size_t i = 0; i < pExp->NumberOfNames; i++)
	{
		printf("AddressOfNames[%d] -> [%s] \r\n", i, pFileBuffer + RvaToFoa(pFileBuffer, pAddName[i]));
	}

	//Function sequence number table
	printf("IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals  -> [0x%08x] \r\n", pExp->AddressOfNameOrdinals);
	LPWORD pAddOrdi = (LPWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNameOrdinals));
	for (size_t i = 0; i < pExp->NumberOfNames; i++)
	{
		printf("AddressOfNameOrdinals[%d] -> [0x%04x] \r\n", i, pAddOrdi[i]);
	}

	//The PE tool is parsed as follows
	pAddFunc = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions));
	pAddName = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNames));
	pAddOrdi = (LPWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNameOrdinals));
	size_t i, j;
	BOOL bFlag = FALSE;

	for (i = 0; i < pExp->NumberOfFunctions; i++)
	{
		//If the value in the function address table is empty, skip this cycle directly
		if (pAddFunc[i] == NULL)
		{
			printf("Ordinal[%04x] RVA[0x%08x] Name[NULL] \r\n", i + pExp->Base, pAddFunc[i]);
			continue;
		}

		//Judge export method
		bFlag = FALSE;

		for ( j = 0; j < pExp->NumberOfNames; j++)
		{
			if (pAddOrdi[j] == i)
			{
				//Export by function name
				bFlag = TRUE;
				printf("Ordinal[%04x] RVA[0x%08x] Name[%s] \r\n", pAddOrdi[j] + pExp->Base, pAddFunc[pAddOrdi[j]], pFileBuffer + RvaToFoa(pFileBuffer, pAddName[j]));
				break;
			}
		}

		if (!bFlag)
		{
			//Export by serial number
			printf("Ordinal[%04x] RVA[0x%08x] Name[NULL] \r\n", i + pExp->Base, pAddFunc[i]);
		}

	}

}

Find the address of the exported function by function name

  • Locate the function name table (RVA) through the export table structure, locate and export the function name through the value (RVA) in the function name table. The function name table can be regarded as a 4-byte array, and search whether there is a corresponding function name by traversing the array
  • If the corresponding function name is found in the function name table, take the index value in the function name table as the subscript to find the corresponding value in the function sequence number table
  • The value in the function sequence number table is used as the subscript in the function address table to obtain the exported function address (RVA)

//Find the address of the exported function by function name
PVOID GetFunctionAddrByName(PCHAR pFileBuffer, PCHAR szName)
{
  //Positioning structure
  PIMAGE_DOS_HEADER        pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
  PIMAGE_NT_HEADERS        pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
  PIMAGE_FILE_HEADER     pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
  PIMAGE_OPTIONAL_HEADER   pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);

  //Judge whether the PE file has an export table
  if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
  {
    printf("Should PE The file does not have an export table \r\n");
    return;
  }
  PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));

  //Location address table, name table, serial number table
  LPDWORD pAddFunc = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions));
  LPDWORD pAddName = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNames));
  LPWORD pAddOrdi = (LPWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNameOrdinals));

  for (size_t i = 0; i < pExp->NumberOfNames; i++)
  {
    if (strcmp(szName,(PCSTR)(pFileBuffer + RvaToFoa(pFileBuffer, pAddName[i]))) == 0)
    {
      //This value + ImageBase is the real function address
      return pAddFunc[pAddOrdi[i]];
    }
  }

  return NULL;
}

Find the function address by exporting the function sequence number

  • Query sequence number - IMAGE_EXPORT_DIRECTORY.Base = Index
  • Index is used as the subscript in the function address table to get the exported function address
//Find the function address by exporting the function sequence number
PVOID GetFunctionAddrByOrdinals(PCHAR pFileBuffer, DWORD dwOrdinal)
{
  //Positioning structure
  PIMAGE_DOS_HEADER        pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
  PIMAGE_NT_HEADERS        pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
  PIMAGE_FILE_HEADER     pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
  PIMAGE_OPTIONAL_HEADER   pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);

  //Judge whether the PE file has an export table
  if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
  {
    printf("Should PE The file does not have an export table \r\n");
    return;
  }
  PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));

  //Location address table, name table, serial number table
  LPDWORD pAddFunc = (LPDWORD)(pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions));
  
  //Judge whether the serial number is normal
  if (dwOrdinal - pExp->Base > pExp->NumberOfFunctions)
  {
    return NULL;
  }

  return pAddFunc[dwOrdinal - pExp->Base];
}

Move export table

  • New section Used to store new imported tables
  • Copy the default export table data to the starting position of the new section and correct it IMAGE_OPTION_HEADER VirtualAddress in directory entry
  • Fixed Name member (RVA) in export table structure
  • Copy the exported function address table. Just copy it directly, and modify AddressOfFunctions
  • Copy the exported function name table. Correct AddressOfNames. Copy the default function name through NumberOfNames loop and correct its corresponding RVA
  • Copy the exported function sequence number table. Just copy it directly, and correct AddressOfNameOrdinals
VOID MoveExportTable()
{
	//Read file binary data
	DWORD dwFileSize = 0;
	PCHAR pFileBuffer = FileToMem(FILE_PATH_IN, &dwFileSize);
	if (!pFileBuffer)
	{
		return;
	}

	//New section storage export table
	pFileBuffer = AddNewSection(pFileBuffer, 0x2000, &dwFileSize);

	//Positioning structure
	PIMAGE_DOS_HEADER        pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS        pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
	PIMAGE_FILE_HEADER		 pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
	PIMAGE_OPTIONAL_HEADER   pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
	PIMAGE_SECTION_HEADER    pSec = (PIMAGE_SECTION_HEADER)((PUCHAR)pOpo + pFil->SizeOfOptionalHeader);

	//Judge whether the PE file has an export table
	if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
	{
		printf("Should PE The file does not have an export table \r\n");
		return;
	}
	PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));

	//Export address table, name table, sequence number table, size table
	DWORD dwSizeOfAddrFunc = 4 * pExp->NumberOfFunctions;
	DWORD dwSizeOfAddrName = 4 * pExp->NumberOfNames;
	DWORD dwSizeOfAddrOrdi = 2 * pExp->NumberOfNames;

	//Fix directory entry pointing in Optional PE header
	PUCHAR pCurrent = pFileBuffer + pSec[pFil->NumberOfSections - 1].PointerToRawData;
	pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);

	//Copy export table structure
	PIMAGE_EXPORT_DIRECTORY pNewExp = (PIMAGE_EXPORT_DIRECTORY)pCurrent;
	memcpy(pCurrent, pExp, sizeof(IMAGE_SECTION_HEADER));
	pCurrent += sizeof(IMAGE_SECTION_HEADER);

	//Copy Name member in exported table structure	
	DWORD dw = RvaToFoa(pFileBuffer, pExp->Name);
	dw += (DWORD)pFileBuffer;
	PUCHAR pOldDllName = pFileBuffer + RvaToFoa(pFileBuffer, pExp->Name);
	DWORD dwDllNameLength = strlen(pOldDllName);
	memcpy(pCurrent, pOldDllName, dwDllNameLength);
	//Fix Name pointing in exported table structure
	pNewExp->Name = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
	pCurrent = pCurrent + dwDllNameLength + 1;

	//Copy address table data
	PUCHAR pOldAddrFunc = pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfFunctions);
	memcpy(pCurrent, pOldAddrFunc, dwSizeOfAddrFunc);
	//Fix AddressOfFunctions pointing in exported table structure
	pNewExp->AddressOfFunctions = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
	pCurrent += dwSizeOfAddrFunc;

	//Copy name table data
	PDWORD pOldAddrName = pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNames);
	memcpy(pCurrent, pOldAddrName, dwSizeOfAddrName);
	//Fix AddressOfNames pointing in exported table structure
	pNewExp->AddressOfNames = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
	//Points to the current export name table
	PDWORD pNewAddrName = pCurrent;
	pCurrent += dwSizeOfAddrName;
	for (size_t i = 0; i < pExp->NumberOfNames; i++)
	{
		//Traverse and fix the export table name table pointing to

		//The RVA in the default function address table is converted to FOA and the address in the file is obtained
		PUCHAR pOldNameAddr = pFileBuffer + RvaToFoa(pFileBuffer, pOldAddrName[i]);
		//Gets the length of the function name
		DWORD dwFunNameSize = strlen(pOldNameAddr) + 1;
		//Copy function name
		memcpy(pCurrent, pOldNameAddr, dwFunNameSize);
		//Fixed the address in the newly exported name table
		pNewAddrName[i] = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);

		pCurrent += dwFunNameSize;
	}

	//Copy serial number table data
	PUCHAR pOldAddrOrdi = pFileBuffer + RvaToFoa(pFileBuffer, pExp->AddressOfNameOrdinals);
	memcpy(pCurrent, pOldAddrOrdi, dwSizeOfAddrOrdi);
	//Fix AddressOfNameOrdinals pointing in exported table structure
	pNewExp->AddressOfNameOrdinals = FoaToRva(pFileBuffer, pCurrent - pFileBuffer);
	pCurrent += dwSizeOfAddrOrdi;

	//Output binary data to a file
	MemToFile(FILE_PATH_OUT, pFileBuffer, dwFileSize);
}

View exported table location after move

No error in parsing data after moving

Function address obtained successfully

Tags: C C++ security security hole

Posted on Fri, 29 Oct 2021 09:29:32 -0400 by neojordan