Manually loading PE imports results error when parsing PIMAGE_IMPORT_DESCRIPTOR

Viewed 112

Greetings overflowers,

Almost completed my associates degree in cybersecurity. Trying to manually load imports from a dll that's in byte code. The virtual address and size of PIMAGE_DATA_DIRECTORY shows the correct values. After parsing PIMAGE_IMPORT_DESCRIPTOR, the values don't get parsed correctly.

PIMAGE_NT_HEADERS32 ntHeader = (PIMAGE_NT_HEADERS32)((DWORD)dllFile32 + ((PIMAGE_DOS_HEADER)(DWORD)dllFile32)->e_lfanew);
PIMAGE_DATA_DIRECTORY importDir = &NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

if (importDir->VirtualAddress > 0 && importDir->Size > sizeof(IMAGE_IMPORT_DESCRIPTOR))
{
    PIMAGE_IMPORT_DESCRIPTOR iid = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)dllFile32 + importDir->VirtualAddress);
    printf("    VirtualAddress: %08X\n", importDir->VirtualAddress);
    printf("              Size: %08X\n", importDir->Size);
    printf("OriginalFirstThunk: %08X\n", iid->OriginalFirstThunk);
    printf("     TimeDateStamp: %08X\n", iid->TimeDateStamp);
    printf("    ForwarderChain: %08X\n", iid->ForwarderChain);
}
    VirtualAddress: 0000507C - CORRECT
              Size: 0000003C - CORRECT
OriginalFirstThunk: 00000000 - INCORRECT
     TimeDateStamp: 56413F2E - INCORRECT
    ForwarderChain: 74737973 - INCORRECT

enter image description here

enter image description here

1 Answers

In PE Format, the data of each section has two different address. The first is the File Offset in the file. You can use this address to access the section data in the file when the file is not mapped that is, when PE is in disk. The second is the RVA (Relative Virtual Address). This address should be used when the OS mapped the PE file into memory.

In your case, you just copy the Dll to a buffer in memory and don't map the section datas to the specified RVAs. Therefore, you cannot work with RVAs. Instead, you should use the file offsets.

An utility function to convert the RVAs to File Offsets can be written as the following.

UINT RvaToFileOffset(BYTE* baseAddress, UINT rva) {
    PIMAGE_NT_HEADERS lpNtHeaders = ImageNtHeader(baseAddress);
    PIMAGE_FILE_HEADER lpFileHeader = &lpNtHeaders->FileHeader;
    BYTE* lpOptionalHeader = &lpNtHeaders->OptionalHeader;
    PIMAGE_SECTION_HEADER lpSections = (PIMAGE_SECTION_HEADER)(lpOptionalHeader + lpFileHeader->SizeOfOptionalHeader);

    for (int i = 0; i < lpFileHeader->NumberOfSections; ++i)
    {
        IMAGE_SECTION_HEADER section = lpSections[i];
        if (rva >= section.VirtualAddress && rva <= section.VirtualAddress + section.Misc.VirtualSize)
            return section.PointerToRawData + rva - section.VirtualAddress;
    }
    return -1; // Invalid RVA
}

Here is a test program.

#include <stdio.h> 
#include <windows.h>
#include <ImageHlp.h>

#pragma comment(lib,"imagehlp.lib")

// Helper function to be used for converting RVAs to File Offsets.
UINT RvaToFileOffset(BYTE * base, UINT rva);

int main(int argc, char* argv[]) {
    PIMAGE_DOS_HEADER lpDosHeader;
    PIMAGE_NT_HEADERS lpNtHeaders;
    PIMAGE_OPTIONAL_HEADER lpOptionalHeader;
    PIMAGE_IMPORT_DESCRIPTOR lpImportDescriptor; 

    if (argc != 2)
    {
        printf("You didn't specified a PE file.\n");
        printf("Usage: ImportsParser.exe <Full path of PE File>\n");
        return -1;
    }
    HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        return -1;
    HANDLE hMemoryMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (!hMemoryMap)
        return -2;
    PBYTE baseAddress = (PBYTE)MapViewOfFile(hMemoryMap, FILE_MAP_READ, 0, 0, 0);
    if (!baseAddress)
        return -3;
    lpDosHeader = (PIMAGE_DOS_HEADER)baseAddress;
    lpNtHeaders = (PIMAGE_NT_HEADERS)(baseAddress + lpDosHeader->e_lfanew);
    lpOptionalHeader = (PIMAGE_OPTIONAL_HEADER)(&(lpNtHeaders->OptionalHeader));
    PIMAGE_DATA_DIRECTORY lpImportDirectoryEntry = &lpOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    lpImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(baseAddress +
        RvaToFileOffset(baseAddress, lpImportDirectoryEntry->VirtualAddress));
    
    printf("    VirtualAddress: %08X\n", lpImportDirectoryEntry->VirtualAddress);
    printf("              Size: %08X\n", lpImportDirectoryEntry->Size);
    while (lpImportDescriptor->FirstThunk)
    { 
        char* szDllName = baseAddress + RvaToFileOffset(baseAddress, lpImportDescriptor->Name);
        printf("DLL Name: %s\n", szDllName);
        printf("OriginalFirstThunk: %08X\n", lpImportDescriptor->OriginalFirstThunk);
        printf("     TimeDateStamp: %08X\n", lpImportDescriptor->TimeDateStamp);
        printf("    ForwarderChain: %08X\n", lpImportDescriptor->ForwarderChain); 
        printf("\n");
        lpImportDescriptor++;
    }
    getchar();
    return 0;
}
UINT RvaToFileOffset(BYTE * baseAddress, UINT rva) {
    PIMAGE_NT_HEADERS lpNtHeaders = ImageNtHeader(baseAddress);
    PIMAGE_FILE_HEADER lpFileHeader = &lpNtHeaders->FileHeader;
    BYTE* lpOptionalHeader = &lpNtHeaders->OptionalHeader;
    PIMAGE_SECTION_HEADER lpSections = (PIMAGE_SECTION_HEADER)(lpOptionalHeader + lpFileHeader->SizeOfOptionalHeader);

    for (int i = 0; i < lpFileHeader->NumberOfSections; ++i)
    {
        IMAGE_SECTION_HEADER section = lpSections[i];
        if (rva >= section.VirtualAddress && rva <= section.VirtualAddress + section.Misc.VirtualSize)
            return section.PointerToRawData + rva - section.VirtualAddress;
    }
    return -1; // Invalid RVA
}
Related