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:
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:
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):
Then I can step into the WTSOpenServerW function to see the location of its IAT entry:
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:
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.





I think I got it working. I'm not sure if there's a better way to optimize
GetPtrFromRVAfunction 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.)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.)
And lastly, in case you don't want to link to
Dbghelp.libjust to use its couple functions, these are their C implementations: