Reverse engineers know that the easiest way to break an application down to understand it’s internal operations involves using the import table and, of course, understanding assembly to some degree. In an effort to add a layer of indirection and in pursuit of understanding how the internal Windows routines operated I rewrote the famous, or infamous depending on the usage, GetProcAddress and GetModuleHandle routines. Feel free to nab the snippets below and follow along to see how you could go about implementing your own. Both of these functions work on x86/x64.
uintptr_t HdnGetProcAddress( void *hModule, const wchar_t *wAPIName ) { #if defined( _WIN32 ) unsigned char *lpBase = reinterpret_cast< unsigned char * >( hModule ); IMAGE_DOS_HEADER *idhDosHeader = reinterpret_cast< IMAGE_DOS_HEADER * >( lpBase ); if ( idhDosHeader->e_magic == 0x5A4D ) { #if defined( _M_IX86 ) IMAGE_NT_HEADERS32 *inhNtHeader = reinterpret_cast< IMAGE_NT_HEADERS32 *>( lpBase + idhDosHeader->e_lfanew ); #elif defined( _M_AMD64 ) IMAGE_NT_HEADERS64 *inhNtHeader = reinterpret_cast< IMAGE_NT_HEADERS64 * >( lpBase + idhDosHeader->e_lfanew ); #endif if ( inhNtHeader->Signature == 0x4550 ) { IMAGE_EXPORT_DIRECTORY *iedExportDirectory = reinterpret_cast< IMAGE_EXPORT_DIRECTORY * >( lpBase + inhNtHeader->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].VirtualAddress ); for ( register unsigned int uiIter = 0; uiIter < iedExportDirectory->NumberOfNames; ++uiIter ) { char *szNames = reinterpret_cast< char * >( lpBase + reinterpret_cast< unsigned long * >( lpBase + iedExportDirectory->AddressOfNames )[ uiIter ] ); if ( !CompareStrings( szNames, wAPIName ) ) { unsigned short usOrdinal = reinterpret_cast< unsigned short * >( lpBase + iedExportDirectory->AddressOfNameOrdinals )[ uiIter ]; return reinterpret_cast< uintptr_t >( lpBase + reinterpret_cast< unsigned long * >( lpBase + iedExportDirectory->AddressOfFunctions )[ usOrdinal ] ); } } } } #endif return 0; }
GetModuleHandle
void *HdnGetModuleBase( wchar_t *wModuleName ) { #if defined( _WIN64 ) #define PEBOffset 0x60 #define LdrOffset 0x18 #define ListOffset 0x10 unsigned long long pPeb = __readgsqword( PEBOffset ); #elif defined( _WIN32 ) #define PEBOffset 0x30 #define LdrOffset 0x0C #define ListOffset 0x0C unsigned long pPeb = __readfsdword( PEBOffset ); #endif pPeb = *reinterpret_cast< decltype( pPeb ) * >( pPeb + LdrOffset ); PLDR_DATA_TABLE_ENTRY pModuleList = *reinterpret_cast< PLDR_DATA_TABLE_ENTRY * >( pPeb + ListOffset ); while ( pModuleList->BaseAddress ) { if( !wcscmp( pModuleList->BaseDllName.Buffer, wModuleName ) ) return pModuleList->BaseAddress; pModuleList = reinterpret_cast< PLDR_DATA_TABLE_ENTRY >( pModuleList->InLoadOrderLinks.Flink ); } return nullptr; }
Using the PEB in the above example is one of the easiest methods to get any loaded module base address. As a side note, HdnGetProcAddress relies on kernel32.dll being loaded prior to use.
As always, drop me a comment, suggestion, and/or feedback.