前回PEフォーマットについて概要を説明したので、今回はその情報にアクセスしてDLLのExport/Importしている情報を列挙してみようと思います。
まずは、IMAGE_DATA_DIRECTORY配列にアクセスしてIMAGE_EXPORT_DIRECTORYや IMAGE_IMPORT_DESCRIPTOR配列の位置を求める必要があります。
DLLがExportしている関数一覧
IMAGE_EXPORT_DIRECTORY配列は以下のような構造体です.
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; // RVA from base of image DWORD AddressOfNames; // RVA from base of image DWORD AddressOfNameOrdinals; // RVA from base of image } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
この構造体の情報から、関数名や関数のアドレス情報といったものにアクセスすることが出来ます。AddressOf*** というメンバがRVA単位で各データへのポインタを持っています。これらの関係性を図示すると以下のようになります。
これらの情報からDLLがエクスポートしている関数の一覧を出すコードは以下のようになります。下記のコードではLoadLibrary関数が返すモジュールハンドルがDLLのベースアドレスとなる点を利用してデータ構造にアクセスしています。ファイルから処理する場合には、この部分をファイルから読み込んだデータのバッファ先頭とすればよいです。
BOOL DispExportList() { HMODULE hMod = LoadLibraryA( "Kernel32.dll" ); ULONG size = 0; PIMAGE_EXPORT_DIRECTORY pExport = NULL; void* baseAddr = hMod; // HMODULEの値がベースアドレスとなっている. pExport = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToData( hMod, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &size ); void* EAT = (char*)baseAddr + pExport->AddressOfFunctions; // ExportAddressTable void* ENT = (char*)baseAddr + pExport->AddressOfNames; // ExportNameTable printf( "IMAGE_EXPORT_DIRECTORY\n" ); printf( " Name=%s\n", (char*)baseAddr + pExport->Name ); printf( " Base=%p\n", pExport->Base ); printf( " NumberOfFunctions = %d\n", pExport->NumberOfFunctions ); printf( " NumberOfNames = %d\n", pExport->NumberOfNames ); printf( " AddressOfFunctions = RVA:%08X (%p)\n", pExport->AddressOfFunctions, EAT ); printf( " AddressOfNames = RVA:%08X (%p)\n", pExport->AddressOfNames, ENT ); printf( " AddressOfNameOrdinals = RVA:%08X\n", pExport->AddressOfNameOrdinals ); printf( "ExportFunctions\n" ); DWORD numFunc = pExport->NumberOfFunctions; PIMAGE_THUNK_DATA32 func = (PIMAGE_THUNK_DATA32)( EAT ); DWORD* nameTableRVA = (DWORD*)( ENT ); WORD* ordinal = (WORD*)( (char*)baseAddr + pExport->AddressOfNameOrdinals ); for( DWORD i = 0; i < numFunc; ++i ) { char* funcName = (char*)baseAddr + nameTableRVA[i]; printf( " %d [%p] %s\n", i, func[i].u1.Function, funcName, ordinal[i]+pExport->Base ); } return TRUE; }
実行結果はこんな感じになります。
IMAGE_EXPORT_DIRECTORY Name=KERNEL32.dll Base=00000001 NumberOfFunctions = 1364 NumberOfNames = 1364 AddressOfFunctions = RVA:000BFA28 (7538FA28) AddressOfNames = RVA:000C0F78 (75390F78) AddressOfNameOrdinals = RVA:000C24C8 ExportFunctions 0 [00013358] AcquireSRWLockExclusive1 [000C9E34] AcquireSRWLockShared 2 [000C98B5] ActivateActCtx 3 [000C98D6] AddAtomA (省略) 1359 [000218CA] lstrcpynA 1360 [0003D4F6] lstrcpynW 1361 [000159EB] lstrlen 1362 [000159EB] lstrlenA 1363 [000116D0] lstrlenW
DLLがImportしているDLL一覧
今度は逆にDLLが依存している他のDLLへの情報についてリストアップしてみます。使用する構造体は IMAGE_IMPORT_DESCRIPTOR です。これもまたOptionalHeaderから辿ることが出来ますが、プログラム上ではimagehlpのユーティリティを使って取得してしまいます。この構造体は以下のような形をしています。この構造体が複数個繋がって、依存するDLLの情報を表現しています。
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;
個数が明示的に記録されているのではなく、Characteristicsメンバが0となるとき終端を意味する構造となっています。この構造体のNameメンバがRVA単位で依存するDLLの名前を示しています。
これにアクセスしてリストアップするコードを以下に示します。
BOOL DispImportList() { HMODULE hMod = LoadLibraryA( "SampleDLL01.dll" ); ULONG size = 0; PIMAGE_IMPORT_DESCRIPTOR pImport = NULL; void* baseAddr = hMod; // HMODULEの値がベースアドレスとなっている. pImport = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData( hMod, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size ); printf( "DLL Dependency\n" ); while( pImport->Characteristics != 0 ) { char* dllName = (char*)baseAddr + pImport->Name; printf( "%s\n", dllName ); ++pImport; } return TRUE; }
実行結果はこのようになります。自作の適当なDLLを読み込ませてみました(しかもデバッグ版)。単純に加算関数しか実装していないので最低限度の依存になっています。
DLL Dependency KERNEL32.dll MSVCR110D.dll
さらに依存する関数そのものにアクセスする場合には、FirstThunkメンバを参照して関数のリストを取得します。DLLエクスポート転送が使用される場合には ForwarderChainメンバが有効な値になるようですが、実際にはどうなるか今のところ確認していません。今後の課題ということで今回は保留にしておきます。