PEフォーマットについて~インポート関数の列挙~

今回は依存する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をちゃんとしたアドレスとして計算して辿っていくと、関数名を記録している領域や、関数のアドレスを格納する領域へたどり着くことが出来ます。

pe-format-import-func-list

これらの情報をアクセスしてみたコードが以下のようになります。

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による参照ってよくわからないなとかいくつもの構造体を辿って目的の情報にアクセスするといった面倒さにも慣れてきて、当初よりは楽に読めるようになってきたかなと思います。

データ解析 プログラミング
すらりんをフォローする
すらりん日記

コメント

タイトルとURLをコピーしました