イメージファイルには、そのイメージファイルをロードする際に必要な DLL とその DLL からインポートする関数や変数の情報が含まれています。インポート関数や変数といちいち表現するのも面倒なので、便宜上インポートシンボルと言いますが、例えば、以下のコード:
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return MessageBoxA(NULL, "World!", "Hello", MB_OK); }上記のコードをコンパイルして出来上がった EXE ファイルでは下図のようなインポート情報が含まれることになります。
バインドというのは、イメージファイルのロードを早くするために、ロード前にイメージファイル内のインポートアドレステーブルをインポートシンボルの実アドレスで上書きしてしまう行為です。BIND ユーティリティや IMAGEHELP.DLL の BindImageEx() API などで行えます。
下図は上がバインド前、下がバインド後の EXE ファイルの内容です。0x9664 という RVA の値から MessageBoxA() API のアドレスである 0x77D3058A に変わっています。
インポート情報はインポートディレクトリテーブルから始まります。そしてインポートディレクトリテーブルのエントリ(レコード)をインポートデスクリプタと呼びます。
インポートデスクリプタはインポートしている DLL の数 + 1 だけ存在し、+ 1 の分は終了を表す全てのフィールドが 0 のレコードとなっています。
インポートデスクリプタは WinNT.H で以下のように定義されています。
WinNT.h
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) }; 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;
Characteristics | この値が 0 の場合はインポートディレクトリの終了を意味する特性となります。非 0 の場合は下の OriginalFirstThunk として使用されます。ただし、これを鵜呑みにすると痛い目を見ます。インポートネームテーブルは存在しなくても良いことを思い出してください。実質、このフィールドは無意味です。 | OriginalFirstThunk | バインドの項で説明したインポートネームテーブルへの RVA が格納されています。0 の場合は、インポートネームテーブルが存在しません。 |
---|---|
TimeDateStamp | バインドには古い形式と新しい形式があって、古い形式ではインポートシンボルがバインドされた日付のタイムスタンプが格納されます。新しい形式では -1 が格納されます。この値が 0 の場合はバインドされていません。 |
ForwarderChain | 古い形式でバインドされているときに使われる情報。 |
Name | インポートする DLL の名前の RVA が格納されています。 |
FirstThunk | インポートアドレステーブルへの RVA が格納されています。 |
インポートネームテーブルはバインドの項で説明したように、バインド前(つまり序数やヒント/名前へのRVA が格納されている状態)のインポートアドレステーブルのコピーです。リンク時に生成されます。インポートネームテーブルはバインド後やロード後もインポートシンボルのアドレスで上書きされるようなことはありません。
インポートアドレステーブルはロード前やバインド前ではインポートシンボルの序数やヒント/名前への RVA が格納されていて、ロード後やバインド後はインポートシンボルの実アドレスで上書きされる領域です。
で、インポートネームテーブルもインポートアドレステーブルも IMAGE_THUNK_DATA 構造体の配列となっていて、エントリ数はインポートデスクリプタが示す DLL からインポートしている関数の数 + 1 だけ存在します。+ 1 はインポートデスクリプタと同じく、終了を表す値が 0 のレコードです。
IMAGE_THUNK_DATA 構造体は WinNT.H で以下のように定義されていて、実態はポインタサイズ(32bit)の値の共用体です。64bit では DWORD から ULONGLONG になります。
WinNT.h
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32;
WinNT.h
#define IMAGE_ORDINAL_FLAG64 0x8000000000000000 #define IMAGE_ORDINAL_FLAG32 0x80000000 #define IMAGE_ORDINAL64(Ordinal) (Ordinal & 0xffff) #define IMAGE_ORDINAL32(Ordinal) (Ordinal & 0xffff) #define IMAGE_SNAP_BY_ORDINAL64(Ordinal) ((Ordinal & IMAGE_ORDINAL_FLAG64) != 0) #define IMAGE_SNAP_BY_ORDINAL32(Ordinal) ((Ordinal & IMAGE_ORDINAL_FLAG32) != 0)
WinNT.h
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
PE ファイルのオプショナルヘッダには DataDirectory というフィールドがありましました。この DataDirectory はインポート情報やエクスポート情報、リソースなど特別なデータへの RVA とサイズを保持しています。この DataDirectory のエントリがどの情報を指しているかは WinNT.H で以下のように定義されています。
WinNT.h
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
では実際に PE ファイルからインポート情報を取り出してみましょう。取り出すためのクラス PEFile を実装します。このクラスに徐々に機能を追加していきます。
PEFile.h
#ifndef __PEFILE_H_INCLUDED__ #define __PEFILE_H_INCLUDED__ #include <pshpack1.h> //! GetImportSymbols() で返される vector の要素の型 struct ImportSymbol { DWORD iat_entry_rva; //!< IAT エントリの RVA 。 void** iat_entry_p; //!< IAT エントリへのポインタ。 WORD hint; //!< ヒント union { struct { WORD import_by_name; //!< 非 0 の場合、名前によるインポート。 WORD ordinal; //!< import_by_name が 0 の場合、序数 }; const char* name; //!< 名前によるインポートの場合、その名前。 }; }; //! PE ファイルから情報を取得するためのクラス。 class PEFile { public: //! コンストラクタ。正常に開けたかどうかは GetLoadBase() で確認する。 explicit PEFile(const wchar_t* i_file_name); //! デストラクタ virtual ~PEFile(); //! ロードアドレスを取得する。 BYTE* GetLoadBase(); //! DOS ヘッダへのポインタを取得。 IMAGE_DOS_HEADER* GetDosHeaderP(); //! NT ヘッダへのポインタを取得。 IMAGE_NT_HEADERS32* GetNtHeadersP(); //! ファイルヘッダへのポインタを取得。 IMAGE_FILE_HEADER* GetFileHeaderP(); //! オプショナルヘッダへのポインタを取得。 IMAGE_OPTIONAL_HEADER32* GetOptionalHeaderP(); //! i_index 番目のセクションヘッダへのポインタを取得。 IMAGE_SECTION_HEADER* GetSectionHeader(int i_index); //! i_index 番目のデータディレクトリエントリが指すデータへのポインタを取得する。 BYTE* GetDirEntryDataP(int i_index); //! i_index 番目のデータディレクトリエントリが指すデータへのサイズを取得する。 DWORD GetDirEntryDataSize(int i_index); //! インポートディレクトリへのポインタを取得する。 IMAGE_IMPORT_DESCRIPTOR* GetImportDirP(); //! インポートしている DLL 名を取得する。 std::vector<const char*> GetImportDllNames(); //! 指定した DLL からインポートしているシンボルを取得する。 std::vector<ImportSymbol> GetImportSymbols(const char* i_dll_name); protected: //! イメージファイルをメモリにマッピングする。 static BYTE* MapImage(const wchar_t* i_file_name); //! RVA をポインタに変換するテンプレートメソッド template<class T> T GetDataP(DWORD i_rva) { return reinterpret_cast<T>(m_load_base + i_rva); } protected: bool m_need_to_free; //!< m_load_base を解放する必要があるか? BYTE* m_load_base; //!< イメージのロードされたアドレス IMAGE_DOS_HEADER* m_dos_header_p; //!< DOS ヘッダへのポインタ IMAGE_NT_HEADERS* m_nt_headers_p; //!< NT ヘッダへのポインタ IMAGE_SECTION_HEADER* m_section_table_p; //!< セクションテーブルへのポインタ }; #include <poppack.h> #endif
PEFile.cpp
//! イメージファイルをメモリにマッピングする。 BYTE* PEFile::MapImage(const wchar_t* i_file_name) { BYTE* retval = NULL; do { // ファイル全体を読み込む。 std::vector<BYTE> file_data; if( LoadFile(i_file_name, file_data) == false ) break; // DOS ヘッダの取得と確認。 IMAGE_DOS_HEADER* dos_header_p = reinterpret_cast<IMAGE_DOS_HEADER*>( &file_data[0] ); if( dos_header_p->e_magic != *reinterpret_cast<WORD*>("MZ") || dos_header_p->e_lfanew == 0 ) { _RPTF0(_CRT_WARN, "実行可能ファイルではありません。\n"); break; } // NT ヘッダの取得と確認。 IMAGE_NT_HEADERS32* nt_headers_p = reinterpret_cast<IMAGE_NT_HEADERS*>( &file_data[dos_header_p->e_lfanew] ); if( nt_headers_p->Signature != *reinterpret_cast<DWORD*>("PE\0\0") || nt_headers_p->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC ) { _RPTF0(_CRT_WARN, "PE ファイルではありません。\n"); break; } // セクションテーブルへのポインタを取得。 IMAGE_SECTION_HEADER* section_table_p = reinterpret_cast<IMAGE_SECTION_HEADER*>( nt_headers_p + 1 ); // SizeOfImage 分だけメモリを確保。 retval = reinterpret_cast<BYTE*>( VirtualAlloc( NULL, nt_headers_p->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ) ); if( retval == NULL ) { _RPTF1(_CRT_WARN, "メモリの割り当て[%dbytes]に失敗しました。\n", nt_headers_p->OptionalHeader.SizeOfImage ); break; } // ヘッダをコピー memcpy(retval, &file_data[0], nt_headers_p->OptionalHeader.SizeOfHeaders); // セクションデータをコピー for(DWORD i = 0; i < nt_headers_p->FileHeader.NumberOfSections; i++) { // セクションデータがある場合。 if( section_table_p[i].PointerToRawData ) { memcpy(&retval[section_table_p[i].VirtualAddress], &file_data[section_table_p[i].PointerToRawData], section_table_p[i].SizeOfRawData ); } } } while( 0 ); return retval; }
PEFile.cpp
//! コンストラクタ。正常に開けたかどうかは GetLoadBase() で確認する。 PEFile::PEFile(const wchar_t* i_file_name) : m_need_to_free(true), m_load_base(NULL), m_dos_header_p(NULL), m_nt_headers_p(NULL), m_section_table_p(NULL) { do { // イメージファイルをメモリにマップする。 m_load_base = MapImage(i_file_name); if( m_load_base == NULL ) { _RPTF1(_CRT_WARN, "[%S]のロードに失敗しました。", i_file_name); break; } _RPTF1(_CRT_WARN, "m_load_base: %p\n", m_load_base); // DOS ヘッダへのポインタを取得。 m_dos_header_p = reinterpret_cast<IMAGE_DOS_HEADER*>(m_load_base); // NT ヘッダへのポインタを取得。 m_nt_headers_p = reinterpret_cast<IMAGE_NT_HEADERS32*>( m_load_base + m_dos_header_p->e_lfanew ); // セクションテーブルへのポインタを取得。 m_section_table_p = reinterpret_cast<IMAGE_SECTION_HEADER*>( m_nt_headers_p + 1 ); } while( 0 ); }
PEFile.cpp
//! デストラクタ PEFile::~PEFile() { if( m_need_to_free && m_load_base ) { VirtualFree(m_load_base, 0, MEM_RELEASE); m_load_base = NULL; } }
PEFile.cpp
//! i_index 番目のデータディレクトリエントリが指すデータへのポインタを取得する。 BYTE* PEFile::GetDirEntryDataP(int i_index) { if( GetLoadBase() && 0 <= i_index && i_index < IMAGE_NUMBEROF_DIRECTORY_ENTRIES && GetOptionalHeaderP()->DataDirectory[i_index].VirtualAddress && GetOptionalHeaderP()->DataDirectory[i_index].Size ) { return m_load_base + GetOptionalHeaderP()->DataDirectory[i_index].VirtualAddress; } else { return NULL; } } //============================================================================= //! i_index 番目のデータディレクトリエントリが指すデータへのサイズを取得する。 DWORD PEFile::GetDirEntryDataSize(int i_index) { if( GetLoadBase() && 0 <= i_index && i_index < IMAGE_NUMBEROF_DIRECTORY_ENTRIES ) return GetOptionalHeaderP()->DataDirectory[i_index].Size; else return 0; }
PEFile.cpp
//! インポートディレクトリへのポインタを取得する。 IMAGE_IMPORT_DESCRIPTOR* PEFile::GetImportDirP() { return reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>( GetDirEntryDataP(IMAGE_DIRECTORY_ENTRY_IMPORT) ); }
PEFile.cpp
//! インポートしている DLL 名を取得する。 std::vector<const char*> PEFile::GetImportDllNames() { std::vector<const char*> retval; IMAGE_IMPORT_DESCRIPTOR* imp_descs_p = GetImportDirP(); if( imp_descs_p ) { for(int i = 0; imp_descs_p[i].FirstThunk != 0; i++) { retval.push_back(GetDataP<const char*>(imp_descs_p[i].Name)); } } return retval; }
PEFile.cpp
//! 指定した DLL からインポートしているシンボルを取得する。 std::vector<ImportSymbol> PEFile::GetImportSymbols(const char* i_dll_name) { std::vector<ImportSymbol> retval; IMAGE_IMPORT_DESCRIPTOR* imp_descs_p = GetImportDirP(); // インポートネームテーブルを持っているか、PEFile クラスが自前でロードした // イメージファイルのみ列挙可能。 if( imp_descs_p && (imp_descs_p[0].OriginalFirstThunk || m_need_to_free) ) { for(int i = 0; imp_descs_p[i].FirstThunk != 0; i++) { const char* dll_name = GetDataP<const char*>(imp_descs_p[i].Name); if( _stricmp(dll_name, i_dll_name) == 0 ) // 指定された DLL を発見! { // インポートアドレステーブルへのポインタを取得。 DWORD* iat_p = GetDataP<DWORD*>(imp_descs_p[i].FirstThunk); // インポートネームテーブルへのポインタを取得。 // INT が存在しない場合は代わりに IAT へのポインタを使用する。 DWORD* int_p = imp_descs_p[i].OriginalFirstThunk ? GetDataP<DWORD*>(imp_descs_p[i].OriginalFirstThunk) : iat_p; // インポートシンボルを列挙 for(int table_index = 0; int_p[table_index] != 0; table_index++) { // INT のレコードがイメージ内に収まっているか確認する。 if( int_p[table_index] < GetOptionalHeaderP()->SizeOfImage ) { ImportSymbol symbol = { 0 }; symbol.iat_entry_rva = imp_descs_p[i].FirstThunk + sizeof(DWORD) * table_index; symbol.iat_entry_p = reinterpret_cast<void**>(&iat_p[table_index]); // 序数によるインポート if( IMAGE_SNAP_BY_ORDINAL32(int_p[table_index]) ) { symbol.ordinal = static_cast<WORD>(IMAGE_ORDINAL32(int_p[table_index])); } // 名前によるインポート else { IMAGE_IMPORT_BY_NAME* by_name_p = GetDataP<IMAGE_IMPORT_BY_NAME*>(int_p[table_index]); symbol.hint = by_name_p->Hint; symbol.name = reinterpret_cast<char*>(by_name_p->Name); } retval.push_back(symbol); } } } } } return retval; }インポートディレクトリへのポインタを取得し、インポートシンボルの名前や序数、IAT のアドレスを取得しています。
EnumImp.cpp
#include <windows.h> #include <stdio.h> #include <vector> #include <string> #include "PEFile.h" int wmain(int argc, wchar_t** argv) { for(int i = 1; i < argc; i++) { PEFile pe_file(argv[i]); std::vector<const char*> dll_names = pe_file.GetImportDllNames(); printf("%S\n", argv[i]); for(size_t i = 0; i < dll_names.size(); i++) { printf(" %s\n", dll_names[i]); printf(" IAT RVA HINT NAME\n"); std::vector<ImportSymbol> import_symbols = pe_file.GetImportSymbols(dll_names[i]); for(size_t j = 0; j < import_symbols.size(); j++) { printf(" %08X ", import_symbols[j].iat_entry_rva); if( import_symbols[j].import_by_name ) printf("%04d %s\n", import_symbols[j].hint, import_symbols[j].name); else printf("Ordinal %d\n", import_symbols[j].ordinal); } printf("\n"); } } return 0; }
実行結果
>EnumImp.exe EnumImp.exe EnumImp.exe KERNEL32.dll IAT RVA HINT NAME 00010000 0052 CloseHandle 00010004 0693 ReadFile 00010008 0356 GetFileSizeEx 0001000C 0086 CreateFileW 00010010 0899 VirtualFree 00010014 0897 VirtualAlloc 00010018 0727 RtlUnwind 0001001C 0862 TerminateProcess 00010020 0322 GetCurrentProcess 00010024 0878 UnhandledExceptionFilter 00010028 0842 SetUnhandledExceptionFilter 0001002C 0569 IsDebuggerPresent 00010030 0679 RaiseException 00010034 0534 HeapFree 00010038 0489 GetVersionExA 0001003C 0528 HeapAlloc 00010040 0419 GetProcessHeap 00010044 0152 EnterCriticalSection 00010048 0593 LeaveCriticalSection 0001004C 0383 GetModuleHandleA 00010050 0416 GetProcAddress 00010054 0869 TlsGetValue 00010058 0867 TlsAlloc 0001005C 0870 TlsSetValue 00010060 0868 TlsFree 00010064 0556 InterlockedIncrement 00010068 0808 SetLastError 0001006C 0326 GetCurrentThreadId 00010070 0369 GetLastError 00010074 0552 InterlockedDecrement 00010078 0260 GetCPInfo 0001007C 0253 GetACP 00010080 0403 GetOEMCP 00010084 0575 IsValidCodePage 00010088 0185 ExitProcess 0001008C 0932 WriteFile 00010090 0441 GetStdHandle 00010094 0381 GetModuleFileNameA 00010098 0382 GetModuleFileNameW 0001009C 0246 FreeEnvironmentStringsA 000100A0 0629 MultiByteToWideChar 000100A4 0341 GetEnvironmentStrings 000100A8 0247 FreeEnvironmentStringsW 000100AC 0343 GetEnvironmentStringsW 000100B0 0272 GetCommandLineA 000100B4 0273 GetCommandLineW 000100B8 0804 SetHandleCount 000100BC 0358 GetFileType 000100C0 0439 GetStartupInfoA 000100C4 0129 DeleteCriticalSection 000100C8 0532 HeapDestroy 000100CC 0530 HeapCreate 000100D0 0675 QueryPerformanceCounter 000100D4 0479 GetTickCount 000100D8 0323 GetCurrentProcessId 000100DC 0458 GetSystemTimeAsFileTime 000100E0 0854 Sleep 000100E4 0916 WideCharToMultiByte 000100E8 0538 HeapReAlloc 000100EC 0540 HeapSize 000100F0 0580 LCMapStringA 000100F4 0581 LCMapStringW 000100F8 0442 GetStringTypeA 000100FC 0445 GetStringTypeW 00010100 0372 GetLocaleInfoA 00010104 0594 LoadLibraryA 00010108 0547 InitializeCriticalSection 0001010C 0290 GetConsoleCP 00010110 0307 GetConsoleMode 00010114 0238 FlushFileBuffers 00010118 0795 SetFilePointer 0001011C 0921 WriteConsoleA 00010120 0309 GetConsoleOutputCP 00010124 0931 WriteConsoleW 00010128 0823 SetStdHandle 0001012C 0083 CreateFileA