読者です 読者をやめる 読者になる 読者になる

PE ファイルについて (8) - インポート 基本編

C++ Portable Executable Win32

第 8 回。
前回は DLL からのエクスポートについてやりました。
今回は対となるインポートについて。

インポートはたくさんある

前回の最後に

インポートはエクスポートの 3 倍くらい面倒くさいですよ。

http://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 の配列です。

わからんから絵で描いて

こんな感じでどうですか。
f:id:aetos382:20160113024710p:plain

コード

関数が名前でインポートされているか序数でインポートされているかは、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;
}

おわりに

次回はインポートのバインドについてです。