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