第 8 回。
前回は DLL からのエクスポートについてやりました。
今回は対となるインポートについて。
- インポートはたくさんある
- IMAGE_IMPORT_DESCRIPTOR
- IMAGE_THUNK_DATA
- IMAGE_IMPORT_BY_NAME
- IAT と INT
- わからんから絵で描いて
- コード
- おわりに
インポートはたくさんある
前回の最後に
インポートはエクスポートの 3 倍くらい面倒くさいですよ。
https://tech.blog.aerie.jp/entry/2016/01/12/145455#まとめ
と書きました。
というのは何故かと言うと…第 5 回を振り返ってみてください。
16 個あるデータ ディレクトリの意味を列挙したテーブルがありますね。
一部抜粋してみましょう。
添字 | 名前 | 意味 |
---|---|---|
1 | IMAGE_DIRECTORY_ENTRY_IMPORT | インポート情報 |
11 | IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | バインドされたインポート情報 |
12 | IMAGE_DIRECTORY_ENTRY_IAT | インポート アドレス テーブル |
13 | IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | 遅延インポート情報 |
なんと、インポート関連の情報だけで 4 つもあるんです!
エクスポートは 1 つしかなかったのに。
まずは基本編ということで、最初の IMAGE_DIRECTORY_ENTRY_IMPORT についてやりましょう。
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; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
この構造体にアクセスする方法は、エクスポートの時と同様ですので省略します。
ではメンバー解説。
Characteristics
インポート情報は IMAGE_IMPORT_DESCRIPTOR 構造体の配列になっています。
リンクしている DLL の数だけ、IMAGE_IMPORT_DESCRIPTOR 構造体が連なっています。
Characteristics は、その終端を識別するものです。
このメンバーが 0 の場合、配列の終端であることを意味します。
OriginalFirstThunk
Import Name Table(INT)への RVA です。
TimeDateStamp
通常は 0 が入ります。
他の値が入るケースについてはバインドの時に詳しく説明します。
ForwarderChain
通常は 0 が入ります。
他の値が入るケースについてはバインドの時に詳しく説明します。
Name
インポートしている DLL のファイル名を指す RVA です。
FirstChunk
Import Address Table(IAT)への RVA です。
IMAGE_THUNK_DATA
IMAGE_IMPORT_DESCRIPTOR の OriginalFirstThunk と FirstThunk は、いずれも IMAGE_THUNK_DATA 構造体の配列への RVA です。
この構造体は 32bit か 64bit かで若干異なります。
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32; typedef struct _IMAGE_THUNK_DATA64 { union { ULONGLONG ForwarderString; // PBYTE ULONGLONG Function; // PDWORD ULONGLONG Ordinal; ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA64; typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;
例によってメンバー解説なのですが、ご覧の通り、こいつは構造体と言っても実態は共用体で、メンバーは 1 つしかないも同然です。
ですから、どのメンバーでアクセスしても同じ値が得られるので、使い分けはさほど重要ではないかもしれません。
また、ドキュメントもないので、推測を含みます。コメントはあてになりませんしね…
ForwarderString
おそらく、バインドで使用するものだと思います。
バインドの時に詳しく説明します。
Function
IAT で使用されます。
関数ポインターが入ります。
RVA ではなく VA だという点に注意してください。
Ordinal
INT で使用されます。
この関数が序数でインポートされている場合に、関数の序数が入ります。
最上位ビットが 1 にセットされます。
AddressOfData
INT で使用されます。
この関数が名前でインポートされている場合に、IMAGE_IMPORT_BY_NAME 構造体への RVA が入ります。
IMAGE_IMPORT_BY_NAME
構造体がたくさん出てきて目が回りそうですね。
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; CHAR Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Hint
対象の DLL がエクスポートする関数の名前配列のインデックスです。
エクスポート編でやった AddressOfNames の配列のインデックスと同じです。
Name
DLL 名を保持する可変長配列です。
IAT と INT
IAT は Import Address Table の略、INT は Import Name Table の略です。
どちらも IMAGE_THUNK_DATA 構造体の配列で、1 対 1 に対応しています。
ファイル中では、両方に INT の内容が入っています。
実行のためにロードされると、IAT の方は INT の内容を元に関数のアドレスを取得して、その内容で更新されます。
ちなみに、IAT は全 DLL の分が一か所にまとめられていて、データ ディレクトリの IMAGE_DIRECTORY_ENTRY_IAT は、この IAT 領域を指しています。
中身は VA の配列です。
わからんから絵で描いて
こんな感じでどうですか。
コード
関数が名前でインポートされているか序数でインポートされているかは、Ordinal の最上位ビットが立っているかどうかで判別できますが、これを取得するために IMAGE_SNAP_BY_ORDINAL というマクロが定義されています。
また、最上位ビットをマスクして序数値を取得するために、IMAGE_ORDINAL というマクロがあります。
ULONG uSize = 0; auto pImport = static_cast<IMAGE_IMPORT_DESCRIPTOR *>( ImageDirectoryEntryToData(pvBase, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &uSize)); while (pImport->Characteristics != 0) { auto pName = static_cast<LPCSTR>(RvaToVa(pvBase, pImport->Name, TRUE)); printf_s("%s\n", pName); auto pINT = static_cast<IMAGE_THUNK_DATA *>(RvaToVa(pvBase, pImport->OriginalFirstThunk, TRUE)); auto pIAT = static_cast<IMAGE_THUNK_DATA *>(RvaToVa(pvBase, pImport->FirstThunk, TRUE)); while (pINT->u1.AddressOfData != 0 && pIAT->u1.Function != 0) { if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal)) { int ordinal = IMAGE_ORDINAL(pINT->u1.Ordinal); printf_s("\t @%u\n", ordinal); } else { auto pFuncName = static_cast<IMAGE_IMPORT_BY_NAME *>(RvaToVa(pvBase, pINT->u1.AddressOfData, TRUE)); printf_s("\t%4u %s\n", pFuncName->Hint, pFuncName->Name); } auto pFunction = reinterpret_cast<FARPROC>(pIAT->u1.Function); ++pINT; ++pIAT; } ++pImport; }
おわりに
次回はインポートのバインドについてです。