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
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.)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.lib
just to use its couple functions, these are their C implementations: