Can PE file sections be moved in memory relative to base address when the image is loaded?

1k views Asked by At

I'm trying to access a delayed loaded function address in the Import Address Table for an arbitrary process.

My assumptions went like this:

First, I need to see where it is located in the image itself relative to the base address:

DWORD_PTR dwFuncOffset = get_IAT_entry_offset_for_imported_function(
    L"path-to\\TargetProc.exe", "WTSAPI32.dll", "WTSOpenServerW");
wprintf(L"Offset is 0x%p\n", dwFuncOffset);

Here's some abbreviated version of the lookup in the PE header. I removed most error checks to make it readable:

#include <delayimp.h>
#include <Dbghelp.h>
#pragma comment(lib, "Dbghelp.lib")


PIMAGE_SECTION_HEADER getEnclosingSectionHeader(DWORD_PTR rva, PIMAGE_NT_HEADERS pNTHeader)
{
    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNTHeader);

    for (WORD i = 0 ; i < pNTHeader->FileHeader.NumberOfSections; i++, section++)
    {
        // Is the RVA within this section?
        if((rva >= section->VirtualAddress) && 
                (rva < (section->VirtualAddress + section->Misc.VirtualSize)))
        {
            return section;
        }
    }

    return 0;
}

LPVOID GetPtrFromRVA(DWORD_PTR rva, PIMAGE_NT_HEADERS pNTHeader, DWORD_PTR imageBase)
{
    PIMAGE_SECTION_HEADER pSectionHdr = getEnclosingSectionHeader(rva, pNTHeader);
    if (!pSectionHdr)
        return 0;

    INT_PTR delta = (INT_PTR)(pSectionHdr->VirtualAddress - pSectionHdr->PointerToRawData);
    return (PVOID)(imageBase + rva - delta);
}


DWORD_PTR get_IAT_entry_offset_for_imported_function(LPCTSTR pImageFilePath, LPCSTR pImportDllName, LPCSTR pImportFuncName)
{
    HANDLE hFile = INVALID_HANDLE_VALUE;
    HANDLE hOpenFileMapping = NULL;
    const BYTE* lpBaseAddress = NULL;

    __try
    {
        hFile = CreateFile(pImageFilePath, 
                GENERIC_READ,
                FILE_SHARE_READ, NULL, OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL, NULL);
        hOpenFileMapping = ::CreateFileMapping(hFile,
                NULL, PAGE_READONLY, 0, 0, NULL);

        lpBaseAddress = (const BYTE*)::MapViewOfFile(hOpenFileMapping,
                FILE_MAP_READ, 0, 0, 0);

        if(!lpBaseAddress)
            return 0;

        PIMAGE_NT_HEADERS pNtHeader = ::ImageNtHeader((PVOID)lpBaseAddress);

        _ASSERT(pNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC);  //32-bit only here

        IMAGE_OPTIONAL_HEADER32* pIOH32 = &reinterpret_cast<PIMAGE_NT_HEADERS32>(pNtHeader)->OptionalHeader;
        PIMAGE_DATA_DIRECTORY pDataDirectories = pDataDirectories = pIOH32->DataDirectory;

        IMAGE_DATA_DIRECTORY* pDLoadTbl = &pDataDirectories[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT];

        ImgDelayDescr *pImportDescriptor = (ImgDelayDescr*)GetPtrFromRVA(
            pDLoadTbl->VirtualAddress, pNtHeader, (DWORD_PTR)lpBaseAddress);

        //Go through all DLLs
        for(; pImportDescriptor->rvaIAT; pImportDescriptor++)
        {
            //Get DLL name
            LPCSTR pStrDllName = (LPCSTR)GetPtrFromRVA(pImportDescriptor->rvaDLLName,
                pNtHeader, (DWORD_PTR)lpBaseAddress);

            //Look for specific import dll
            if(lstrcmpiA(pStrDllName, pImportDllName) != 0)
                continue;

            IMAGE_THUNK_DATA32 *pITD_IAT = (IMAGE_THUNK_DATA32*)
                GetPtrFromRVA(pImportDescriptor->rvaIAT, pNtHeader, (DWORD_PTR)lpBaseAddress);

            IMAGE_THUNK_DATA32 *pITD_INT = (IMAGE_THUNK_DATA32*)
                GetPtrFromRVA(pImportDescriptor->rvaINT, pNtHeader, (DWORD_PTR)lpBaseAddress);

            //Go through all imported functions from this DLL
            for(; pITD_INT->u1.AddressOfData != 0; pITD_IAT++, pITD_INT++)
            {
                if(IMAGE_SNAP_BY_ORDINAL32(pITD_INT->u1.Ordinal))
                    continue;

                IMAGE_IMPORT_BY_NAME* pIIBY = (IMAGE_IMPORT_BY_NAME*)
                    GetPtrFromRVA(pITD_INT->u1.AddressOfData, pNtHeader, (DWORD_PTR)lpBaseAddress);
                if(!pIIBY)
                    continue;

                //Pick only specific imported function
                if(lstrcmpiA((LPCSTR)pIIBY->Name, pImportFuncName) != 0)
                    continue;

                //Get this function's offset in IAT relative to base address
                return (DWORD_PTR)pITD_IAT - (DWORD_PTR)lpBaseAddress;
            }
        }
    }
    __finally
    {
        ::UnmapViewOfFile(lpBaseAddress);
        ::CloseHandle(hOpenFileMapping);
        ::CloseHandle(hFile);
    }

    return 0;   //failed
}

Then I build the TargetProc.exe as a simple console project with WTSAPI32.dll set for delayed loading:

enter image description here

TargetProc.exe has only this code:

#include "stdafx.h"
#include <Windows.h>

#include <Wtsapi32.h>
#pragma comment(lib, "Wtsapi32.lib")


int _tmain(int argc, _TCHAR* argv[])
{
    //Get base address for this image
    void* pBaseAddr = (void*)::GetModuleHandle(NULL);

    ::WTSOpenServerW(NULL);     //Set up for delayed loading

    return 0;
}

I then run my first project that gives me that WTSOpenServerW function's IAT entry offset from the base of TargetProc.exe is:

Offset is 0x00007670

which I can verify with a debugger:

enter image description here


Then the second stage is to check it.

So if I run my TargetProc.exe in Visual Studio, I can first get its base address (which happened to be 0x890000):

enter image description here

Then I can step into the WTSOpenServerW function to see the location of its IAT entry:

enter image description here

Skip that jump, it's added here only in the debugger build.

And this is where it actually reads the address of the WTSOpenServerW function from its IAT entry for the jmp instruction:

enter image description here

I get its IAT entry at address 0x008AB070, which happens to be at 0x1B070 byte offset from the base address (i.e. 0x008AB070 - 0x890000 = 0x1B070), instead of my expected 0x7670 that I calculated above.

So what am I doing wrong in my calculations?

PS. PE header structure reference 1 and reference 2.

1

There are 1 answers

0
c00000fd On

I think I got it working. I'm not sure if there's a better way to optimize GetPtrFromRVA function calls though, that could eat up some CPU clocks for a larger PE file.

So the short answer to my original question is yes, PE file sections can be relocated in memory in relation to their position in the PE file when the file is mapped for execution.

And here are two ways to read the Import Address Table (in my case for delayed loaded DLLs): from an image file and from the current process memory. (There's also a third way of getting it from another running process by reading its virtual memory. This guy shows how. In that case my approach though would be to inject my DLL into a process and use parsePEheader_DelayLoad_FromMem() method.)

wprintf(L"Memory:----------------------\n");
parsePEheader_DelayLoad_FromMem((void*)::GetModuleHandle(NULL), TRUE);

wprintf(L"File:----------------------\n");
WCHAR buff[MAX_PATH];
::GetModuleFileName(NULL, buff, _countof(buff));
parsePEheader_DelayLoad_FromFile(buff);

And then the code itself: (Note that there's also an old style PE header format, used by Visual Studio 6.0, which I had to account for as well.)

BOOL parsePEheader_DelayLoad_FromFile(LPCTSTR pImageFilePath)
{
    HANDLE hFile = INVALID_HANDLE_VALUE;
    HANDLE hOpenFileMapping = NULL;
    void* lpBaseAddress = NULL;

    __try
    {
        hFile = CreateFile(pImageFilePath, 
                GENERIC_READ,
                FILE_SHARE_READ, NULL, OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL, NULL);
        hOpenFileMapping = ::CreateFileMapping(hFile,
                NULL, PAGE_READONLY, 0, 0, NULL);

        lpBaseAddress = (void*)::MapViewOfFile(hOpenFileMapping,
                FILE_MAP_READ, 0, 0, 0);

        if(!lpBaseAddress)
            return FALSE;

        return parsePEheader_DelayLoad_FromMem(lpBaseAddress, FALSE);
    }
    __finally
    {
        ::UnmapViewOfFile(lpBaseAddress);
        ::CloseHandle(hOpenFileMapping);
        ::CloseHandle(hFile);
    }

    return FALSE;
}


BOOL parsePEheader_DelayLoad_FromMem(void* lpBaseAddress, BOOL bImage)
{
    __try
    {
        PIMAGE_SECTION_HEADER pDummy;
        ULONG uiDummy;
        ImgDelayDescr *pImportDescriptor = (ImgDelayDescr*)ImageDirectoryEntryToDataEx(
            lpBaseAddress, bImage, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, &uiDummy, &pDummy);

        if(pImportDescriptor)
        {
            const char* base = (char*)lpBaseAddress;

            IMAGE_NT_HEADERS* pNtHeader = 
                (IMAGE_NT_HEADERS*)((BYTE*)lpBaseAddress + 
                ((IMAGE_DOS_HEADER*)lpBaseAddress)->e_lfanew);

            DWORD_PTR uiPreferredBase = pNtHeader->OptionalHeader.ImageBase;

            //Go thru all DLLs
            while(pImportDescriptor->rvaIAT)
            {
                PIMAGE_THUNK_DATA nameTable = 0;
                PIMAGE_THUNK_DATA funcTable = 0;

                const char* dllName;
                BOOL bOldStyle;

                if(pImportDescriptor->grAttrs & 1)
                {
                    bOldStyle = FALSE;

                    if(!bImage)
                    {
                        nameTable = (PIMAGE_THUNK_DATA)
                            GetPtrFromRVA(pImportDescriptor->rvaINT, pNtHeader, (DWORD_PTR)lpBaseAddress);

                        funcTable = (PIMAGE_THUNK_DATA)
                            GetPtrFromRVA(pImportDescriptor->rvaIAT, pNtHeader, (DWORD_PTR)lpBaseAddress);

                        dllName = (const char*)
                            GetPtrFromRVA(pImportDescriptor->rvaDLLName, pNtHeader, (DWORD_PTR)lpBaseAddress);
                    }
                    else
                    {
                        nameTable = (PIMAGE_THUNK_DATA)(base + pImportDescriptor->rvaINT);
                        funcTable = (PIMAGE_THUNK_DATA)(base + pImportDescriptor->rvaIAT);

                        dllName = base + pImportDescriptor->rvaDLLName;
                    }
                }
                else
                {
                    //Old style PE file, compiled with VC++ 6.0
                    //Cannot be 64-bit
                    _ASSERT(sizeof(DWORD_PTR) == sizeof(DWORD));
                    bOldStyle = TRUE;

                    if(!bImage)
                    {
                        nameTable = (PIMAGE_THUNK_DATA)
                            GetPtrFromRVA((DWORD_PTR)pImportDescriptor->rvaINT - uiPreferredBase, pNtHeader, (DWORD_PTR)lpBaseAddress);

                        funcTable = (PIMAGE_THUNK_DATA)
                            GetPtrFromRVA((DWORD_PTR)pImportDescriptor->rvaIAT - uiPreferredBase, pNtHeader, (DWORD_PTR)lpBaseAddress);

                        dllName = (LPCSTR)((DWORD_PTR)lpBaseAddress + (DWORD_PTR)pImportDescriptor->rvaDLLName - uiPreferredBase);
                    }
                    else
                    {
                        nameTable = (PIMAGE_THUNK_DATA)(base + pImportDescriptor->rvaINT - uiPreferredBase);
                        funcTable = (PIMAGE_THUNK_DATA)(base + pImportDescriptor->rvaIAT - uiPreferredBase);

                        dllName = base + pImportDescriptor->rvaDLLName - uiPreferredBase;
                    }
                }


                printf("Delay Loaded DLL: %s\n", dllName);

                while(nameTable->u1.AddressOfData)
                {
                    // check whether this is imported by ordinal only
                    if(nameTable->u1.Ordinal & IMAGE_ORDINAL_FLAG)
                    {
                        WORD ordinal = static_cast<WORD>(nameTable->u1.Ordinal & (~IMAGE_ORDINAL_FLAG));

                        printf("ordinal: %u", ordinal);
                    }
                    else
                    {
                        PIMAGE_IMPORT_BY_NAME funcNameStruct;
                        if(!bImage)
                        {
                            if(!bOldStyle)
                            {
                                funcNameStruct = (PIMAGE_IMPORT_BY_NAME)
                                    GetPtrFromRVA((DWORD_PTR)nameTable->u1.AddressOfData, pNtHeader, (DWORD_PTR)lpBaseAddress);
                            }
                            else
                            {
                                funcNameStruct = (PIMAGE_IMPORT_BY_NAME)
                                    GetPtrFromRVA((DWORD_PTR)nameTable->u1.AddressOfData - uiPreferredBase, pNtHeader, (DWORD_PTR)lpBaseAddress);
                            }
                        }
                        else
                        {
                            if(!bOldStyle)
                            {
                                funcNameStruct = (PIMAGE_IMPORT_BY_NAME)
                                    (base + (DWORD_PTR)nameTable->u1.AddressOfData);
                            }
                            else
                            {
                                funcNameStruct = (PIMAGE_IMPORT_BY_NAME)
                                    (base + (DWORD_PTR)nameTable->u1.AddressOfData - uiPreferredBase);
                            }
                        }

                        char* pFuncName = (char*)funcNameStruct->Name;

                        printf("func: %s", pFuncName);
                    }

                    printf("\tAddr: [0x%p]=0x%p\n", 
                        funcTable, *(DWORD_PTR*)funcTable);

                    nameTable++;
                    funcTable++;
                }

                pImportDescriptor++;
            }
        }
        else
        {
            wprintf(L"There's no Delay loaded DLLs\n");
        }

        return TRUE;
    }
    __except(1)
    {
        wprintf(L"\nException! Bad address, not a PE file, or something goofed up...\n");
        return FALSE;
    }
}

And lastly, in case you don't want to link to Dbghelp.lib just to use its couple functions, these are their C implementations:

PVOID WINAPI ImageDirectoryEntryToDataEx( PVOID base, BOOLEAN image, USHORT dir, PULONG size, PIMAGE_SECTION_HEADER *section )
{
    const IMAGE_NT_HEADERS *nt;
    DWORD addr;

    *size = 0;
    if (section)
        *section = NULL;

    if (!(nt = RtlImageNtHeader( (HMODULE)base ))) 
        return NULL;

    if (dir >= nt->OptionalHeader.NumberOfRvaAndSizes) 
        return NULL;
    if (!(addr = nt->OptionalHeader.DataDirectory[dir].VirtualAddress)) 
        return NULL;

    *size = nt->OptionalHeader.DataDirectory[dir].Size;
    if (image || addr < nt->OptionalHeader.SizeOfHeaders) 
        return (char *)base + addr;

    return RtlImageRvaToVa( nt, (HMODULE)base, addr, section );
}

PVOID WINAPI RtlImageRvaToVa( const IMAGE_NT_HEADERS *nt, HMODULE module, DWORD rva, IMAGE_SECTION_HEADER **section )
{
    IMAGE_SECTION_HEADER *sec;

    if (section && *section)
    {
        sec = *section;
        if ((sec->VirtualAddress <= rva) && (sec->VirtualAddress + sec->SizeOfRawData > rva))
            goto lbl_use_it;
    }

    if (!(sec = RtlImageRvaToSection( nt, module, rva ))) 
        return NULL;

lbl_use_it:

    if (section) 
        *section = sec;

    return (char *)module + sec->PointerToRawData + (rva - sec->VirtualAddress);
}

PIMAGE_SECTION_HEADER WINAPI RtlImageRvaToSection( const IMAGE_NT_HEADERS *nt, HMODULE module, DWORD rva )
{
    int i;
    const IMAGE_SECTION_HEADER *sec;

    sec = (const IMAGE_SECTION_HEADER*)(
        (const char*)&nt->OptionalHeader + nt->FileHeader.SizeOfOptionalHeader);

    for (i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++)
    {
        if ((sec->VirtualAddress <= rva) && (sec->VirtualAddress + sec->SizeOfRawData > rva))
            return (PIMAGE_SECTION_HEADER)sec;
    }

    return NULL;
}

PIMAGE_NT_HEADERS WINAPI RtlImageNtHeader(HMODULE hModule)
{
    IMAGE_NT_HEADERS *ret;

    __try
    {
        IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)hModule;

        ret = NULL;
        if (dos->e_magic == IMAGE_DOS_SIGNATURE)
        {
            ret = (IMAGE_NT_HEADERS *)((char *)dos + dos->e_lfanew);
            if (ret->Signature != IMAGE_NT_SIGNATURE) 
                ret = NULL;
        }
    }
    __except(1)
    {
        return NULL;
    }

    return ret;
}