今回は依存するDLLの名前だけでなく関数の情報を取得する部分までやってみたいと思います。前回でインポートDLLの名前までは取得できたので、IMAGE_IMPORT_DESCRIPTORの情報まではたどり着けた前提で話を進めていきます。
IMAGE_IMPORT_DESCRIPTOR構造体は以下のようなものです。
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) } DUMMYUNIONNAME; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) } IMAGE_IMPORT_DESCRIPTOR;
インポート関数の情報は、この構造体のFirstThunkメンバが示す情報(IATテーブル)とOriginalFirstThunkメンバが示す情報(関数名のテーブル情報)にアクセスすることで取得できます。これらのデータはRVA(Relative Virtual Address)で記録されています。
これらの関係を図示すると以下のようになります。RVAをちゃんとしたアドレスとして計算して辿っていくと、関数名を記録している領域や、関数のアドレスを格納する領域へたどり着くことが出来ます。
これらの情報をアクセスしてみたコードが以下のようになります。
printf( "DLL Dependency\n" ); while( pImport->Characteristics != 0 ) { char* dllName = (char*)baseAddr + pImport->Name; printf( "%s\n", dllName ); // 名前用・関数用の情報保持領域のアドレスを求める(1) IMAGE_THUNK_DATA* pThunkName = (IMAGE_THUNK_DATA*)((char*)baseAddr+pImport->OriginalFirstThunk); IMAGE_THUNK_DATA* pThunkFunc = (IMAGE_THUNK_DATA*)((char*)baseAddr+pImport->FirstThunk); while( pThunkFunc->u1.Function != 0 ) { IMAGE_IMPORT_BY_NAME* importName = (IMAGE_IMPORT_BY_NAME*)((char*)baseAddr + pThunkName->u1.AddressOfData); printf( " 0x%p : %s\n", pThunkFunc->u1.Function, importName->Name ); ++pThunkName; ++pThunkFunc; } ++pImport; }
実行結果は以下のようになります。前回同様適当な自作DLLを読み込ませてみたときの結果です。
DLL Dependency KERNEL32.dll 0x7530B24F : OutputDebugStringA 0x752E3450 : GetModuleHandleW 0x752E48F0 : GetModuleFileNameW 0x752E3468 : FreeLibrary 0x752E43FA : VirtualQuery 0x752E14B9 : GetProcessHeap 0x752E1499 : HeapFree 0x779AE046 : HeapAlloc 0x752FEE78 : GetTickCount64 0x752E34A9 : GetSystemTimeAsFileTime 0x752E1420 : GetCurrentThreadId 0x752E16F5 : QueryPerformanceCounter 0x752E16DD : WideCharToMultiByte 0x752E18FE : MultiByteToWideChar 0x752E48CB : LoadLibraryW 0x752E59EB : lstrlenA 0x752E48FD : LoadLibraryExW 0x752E1222 : GetProcAddress 0x752E11C0 : GetLastError 0x752E5846 : RaiseException 0x752E49FD : IsDebuggerPresent 0x779B9DD5 : DecodePointer 0x779C107B : EncodePointer 0x752E51D5 : IsProcessorFeaturePresent MSVCR110D.dll 0x0FE18EF0 : __crtUnhandledException 0x0FE224A0 : _crt_debugger_hook 0x0FE27360 : _wsplitpath_s 0x0FE25AB0 : _wmakepath_s 0x0FD65EE0 : wcscpy_s 0x0FE22700 : _except_handler4_common 0x0FDFC2B0 : __clean_type_info_names_internal 0x0FE15220 : _onexit 0x0FE15090 : __dllonexit 0x0FE17310 : _calloc_dbg 0x0FE13710 : _CrtDbgReportW 0x0FD3AD30 : _unlock 0x0FD3AA90 : _lock 0x0FD39D50 : _initterm_e 0x0FD39D20 : _initterm 0x0FE16740 : _CrtSetCheckCount 0x0FE17540 : _free_dbg 0x0FE17E80 : _malloc_dbg 0x0FD399A0 : _amsg_exit 0x0FE1C170 : __CppXcptFilter 0x0FE20880 : _CRT_RTC_INITW 0x0FE18ED0 : __crtTerminateProcess
まとめ
PEヘッダの情報から、依存するDLLやその関数の情報までデータを辿ることができました。これでPEヘッダ編もひとまずは終わりと出来そうです。RVAによる参照ってよくわからないなとかいくつもの構造体を辿って目的の情報にアクセスするといった面倒さにも慣れてきて、当初よりは楽に読めるようになってきたかなと思います。