PEフォーマットについて~DLLのExport/Importを列挙~

前回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単位で各データへのポインタを持っています。これらの関係性を図示すると以下のようになります。

pe-format-export-list

これらの情報から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] AcquireSRWLockExclusive 
  1 [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;

pe-format-import-list

個数が明示的に記録されているのではなく、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メンバが有効な値になるようですが、実際にはどうなるか今のところ確認していません。今後の課題ということで今回は保留にしておきます。

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

コメント

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