Parsing Export Function from PE Manually

Here is the next post that I promissed to create a simple list of the export function manually with C code.

Why parsing manually of function is good to understand because most of the malware nowadays is using manual dll loading to evade the static and dynamic analysis. The malware will use encrypted fucntion name which will be matched later with the exported function to gain the memory of the function for invocation

Following our code in the previous post that we have been able to extract the export directory RVA and the Size. So in order to really able to read the EXPORT directory we need to be able to calculate the RVA to actual file offset. RVA is the relative virtual address which we cannot reference directly as file offset because RVA is usefull when the PE has been loaded into the memory.

Below is a simple RVA to file raw offset

int Rva2Offset(int noOfSection, unsigned int rva) {
      
    for (int i = 0; i < noOfSection; i++) {
        unsigned int x = sections[i].VirtualAddress + sections[i].SizeOfRawData;

        //To check which section the RVA falls into
        if (x >= rva) {
            //calculate the file offset 
            return rva - sections[i].VirtualAddress + sections[1].PointerToRawData;
        }
    }

    return -1;
}

There are two operation to be done in the above function, The function will determine what PE Section the RVA falls into, it checks for every section of virtual address + size of virtual raw data of each section that must be greater than the supplied RVA. The second operation is to calculate the actual file offset with this formula, FIleOffset = RVA – section Virtual address + section Pointer to raw data;

We can use the result of the above calculation as file offset which we can supply to fseek function.

int export_offset = Rva2Offset(numOfSection, exportDirectoryRVA);

 //Get the RVA of the EXPORT Array of API
    DWORD pointer_to_exportName_RVA;
    fseek(file, export_offset + 0x20, SEEK_SET);
    fread(&pointer_to_exportName_RVA, 4, 1, file);
    printf("pointer_to_exportName_RVA = %x\n", pointer_to_exportName_RVA);

Below is the code output.

Again, we need to remember that the A3074 is pointer an address that leads us to function name array. We must convert it again into a file offset using the function Rva2Offset.

   //Get the RVA of the EXPORT Array of API
    DWORD pointer_to_exportName_RVA;
    fseek(file, export_offset + 0x20, SEEK_SET);
    fread(&pointer_to_exportName_RVA, 4, 1, file);
    printf("pointer_to_exportName_RVA = %x\n", pointer_to_exportName_RVA);

    //Get the file offset from the RVA of EXPORT array pointer
    int export_name_array_offset = Rva2Offset(numOfSection, pointer_to_exportName_RVA);
    fseek(file, export_name_array_offset, SEEK_SET);
    
    //Read the RVA to Array 
    DWORD pointerToArrayRVA;
    fread(&pointerToArrayRVA, 4, 1, file);
    printf("pointerToArrayRVA = %x\n", pointerToArrayRVA);

We can follow the A4829 RVA to lead us to the function name array. But dont forget to convert the RVA back to file offset again

//Read the Actual file offset of the array
    int offsetToFirstAddressOfAPIArray = Rva2Offset(numOfSection, pointerToArrayRVA);
    fseek(file, offsetToFirstAddressOfAPIArray, SEEK_SET);

After we get the file offset (A3629) then we can use fseek to guide us to array sequence as below

Below is the code on how I enumerate all the function name in the export list

int arrayOffset = 0;
   
    for (int ii = 1; ii <= numberOfAPIName; ii++) {
        int szNameLen = 0;
        
        //Counting the number of char of the API Name
        char c = '0';
        while (c != '\0') {
            fread(&c, 1, 1, file);
            szNameLen++;
        }

        //Revert the file offset as much as the length of API name for API name read with fread
        fseek(file, -szNameLen, SEEK_CUR);
        
        char* szName = (char*)calloc(szNameLen + 1, 1);

        //Read the API name
        fread(szName, szNameLen, 1, file);
        printf("%d API name : %s\n",ii, szName);

        //set the file offset to jump 
        arrayOffset += szNameLen;
        fseek(file, offsetToFirstAddressOfAPIArray + arrayOffset, SEEK_SET);
    }

Bonus Code

Below is the complete code that I develop to parse the PE header manually and especially to read the export function of PE.

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Windows.h>


typedef struct {
    unsigned char Name[8];
    unsigned int VirtualSize;
    unsigned int VirtualAddress;
    unsigned int SizeOfRawData;
    unsigned int PointerToRawData;
    unsigned int PointerToRelocations;
    unsigned int PointerToLineNumbers;
    unsigned short NumberOfRelocations;
    unsigned short NumberOfLineNumbers;
    unsigned int Characteristics;
} sectionHeader;

sectionHeader* sections;

int Rva2Offset(int noOfSection, unsigned int rva) {
      
    for (int i = 0; i < noOfSection; i++) {
        unsigned int x = sections[i].VirtualAddress + sections[i].SizeOfRawData;

        //To check which section the RVA falls into
        if (x >= rva) {
            //calculate the file offset 
            return rva - sections[i].VirtualAddress + sections[1].PointerToRawData;
        }
    }

    return -1;
}

void main()
{
    FILE* file = fopen("C:\\Users\\pentest\\Desktop\\Work\\user32.dll", "rb");

    long peheaderoffset;
    // read the offset of the PE header which is located at offset 0x3c
    fseek(file, 0x3c, SEEK_SET);
    fread(&peheaderoffset, 4, 1, file); //read the offset --> e_lfanew

    char PEHeader[4];  // PE header: contains normally 'P','E',0,0
    fseek(file, peheaderoffset, SEEK_SET);
    fread(&PEHeader, 4, 1, file);

    short machine_rio;
    fseek(file, peheaderoffset + 4, SEEK_SET);
    fread(&machine_rio, 2, 1, file);
    printf("machine_rio = %x\n", machine_rio);

    short numOfSection;
    fseek(file, peheaderoffset + 4 + 2 , SEEK_SET);
    fread(&numOfSection, 2, 1, file);
    printf("numOfSection = %x\n", numOfSection);

    long timedatastamp;
    fseek(file, peheaderoffset + 4 + 4, SEEK_SET);
    fread(&timedatastamp, 4, 1, file);
    printf("timedatastamp = %x\n", timedatastamp);

    long pointerToSymbolTable;
    fseek(file, peheaderoffset + 4 + 8, SEEK_SET);
    fread(&pointerToSymbolTable, 4, 1, file);
    printf("pointerToSymbolTable = %x\n", pointerToSymbolTable);
    
    long numberOfSymbols;
    fseek(file, peheaderoffset + 4 + 0x0c, SEEK_SET);
    fread(&numberOfSymbols, 4, 1, file);
    printf("numberOfSymbols = %x\n", pointerToSymbolTable);

    //Get sizeofOptionalHeader from File Header
    short sizeofOptionalHeader;
    fseek(file, peheaderoffset + 4 + 16, SEEK_SET);
    fread(&sizeofOptionalHeader, 2, 1, file);
    printf("sizeofOptionalHeader = %x\n", sizeofOptionalHeader);

    //Get Characteristic from File Header
    short characteristic;
    fseek(file, peheaderoffset + 4 + 18, SEEK_SET);
    fread(&characteristic, 2, 1, file);
    printf("characteristic = %x\n", characteristic);

    //Get The magic from optional hdr
    short magic;
    fseek(file, peheaderoffset + 0x18, SEEK_SET);
    fread(&magic, 2, 1, file);
    printf("magic = %x\n", magic);

    //Get the Export Directory RVA
    DWORD exportDirectoryRVA;
    fseek(file, peheaderoffset + 0x18 + 0x70, SEEK_SET);
    fread(&exportDirectoryRVA, 4, 1, file);
    printf("exportDirectoryRVA = %x\n", exportDirectoryRVA);

    DWORD exportDirectorySize;
    fseek(file, peheaderoffset + 0x18 + 0x70 + 4, SEEK_SET);
    fread(&exportDirectorySize, 4, 1, file);
    printf("exportPointerSize = %x\n", exportDirectorySize);

 
    //120 is offset from last read exportPointerSize
    fseek(file, 120, SEEK_CUR);

    
    sections = (sectionHeader*)malloc(numOfSection * sizeof(sectionHeader));

    //Collecting the details of section Header
    for (int i = 0; i < numOfSection; i++) {
        fread(sections[i].Name, 8, 1, file); //Read Image section name
        fread(&sections[i].VirtualSize, 4, 1, file);
        fread(&sections[i].VirtualAddress, 4, 1, file);
        fread(&sections[i].SizeOfRawData, 4, 1, file);
        fread(&sections[i].PointerToRawData, 4, 1, file);
        fread(&sections[i].PointerToRelocations, 4, 1, file);
        fread(&sections[i].PointerToLineNumbers, 4, 1, file);
        fread(&sections[i].NumberOfRelocations, 2, 1, file);
        fread(&sections[i].NumberOfLineNumbers, 2, 1, file);
        fread(&sections[i].Characteristics, 4, 1, file);
    }

    int export_offset = Rva2Offset(numOfSection, exportDirectoryRVA);

    
    //Getting number of exported API
    unsigned int numberOfAPIName;
    fseek(file, export_offset + 0x18, SEEK_SET);
    fread(&numberOfAPIName, 4, 1, file);
    printf("numberOfAPIName = %x\n", numberOfAPIName);

    //Get the RVA of the EXPORT Array of API
    DWORD pointer_to_exportName_RVA;
    fseek(file, export_offset + 0x20, SEEK_SET);
    fread(&pointer_to_exportName_RVA, 4, 1, file);
    printf("pointer_to_exportName_RVA = %x\n", pointer_to_exportName_RVA);

    //Get the file offset from the RVA of EXPORT array pointer
    int export_name_array_offset = Rva2Offset(numOfSection, pointer_to_exportName_RVA);
    fseek(file, export_name_array_offset, SEEK_SET);
    
    //Read the RVA to Array 
    DWORD pointerToArrayRVA;
    fread(&pointerToArrayRVA, 4, 1, file);
    printf("pointerToArrayRVA = %x\n", pointerToArrayRVA);

    //Read the Actual file offset of the array
    int offsetToFirstAddressOfAPIArray = Rva2Offset(numOfSection, pointerToArrayRVA);
    fseek(file, offsetToFirstAddressOfAPIArray, SEEK_SET);

    int arrayOffset = 0;
   
    for (int ii = 1; ii <= numberOfAPIName; ii++) {
        int szNameLen = 0;
        

        //Counting the number of char of the API Name
        char c = '0';
        while (c != '\0') {
            fread(&c, 1, 1, file);
            szNameLen++;
        }

        //Revert the file offset as much as the length of API name for API name read with fread
        fseek(file, -szNameLen, SEEK_CUR);
        
        char* szName = (char*)calloc(szNameLen + 1, 1);

        //Read the API name
        fread(szName, szNameLen, 1, file);
        printf("%d API name : %s\n",ii, szName);

        //set the file offset to jump 
        arrayOffset += szNameLen;
        fseek(file, offsetToFirstAddressOfAPIArray + arrayOffset, SEEK_SET);
    }
   

}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s