LoginSignup
4
5

More than 5 years have passed since last update.

PEファイルのバイナリ構造(7) インポート関数の列挙

Last updated at Posted at 2017-11-12

インポート関数

エクスポート関数に対して、インポート関数は幾つか種類が存在しています。
具体的には、

定義名 説明
1 IMAGE_DIRECTORY_ENTRY_IMPORT インポートディレクトリ
11 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 境界インポートディレクトリ
12 IMAGE_DIRECTORY_ENTRY_IAT インポートアドレステーブル
13 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 遅延インポートテーブル

の4つとなりますが、ここでは他のファイルとの依存関係を確認できるインポートと遅延インポートを対象とします。

IMAGE_DIRECTORY_ENTRY_IMPORT

セクションに関しては、エクスポート関数の定義位置の取得で行ったのとほぼ同じ手順で、配列の添え字だけ「IMAGE_DIRECTORY_ENTRY_IMPORT」として確認します。

IMAGE_DATA_DIRECTORY& tData = tNT.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
DWORD nOffset = tData.VirtualAddress;

IMAGE_IMPORT_DESCRIPTOR

エクスポートの時と同じ手順でセクションまで確認後、IMAGE_IMPORT_DESCRIPTORのポインタへとキャストします。

IMAGE_IMPORT_DESCRIPTOR* pImport = (IMAGE_IMPORT_DESCRIPTOR*)&(pFile[nOffset - delta]);

IMAGE_IMPORT_DESCRIPTORは以下の様に定義されています。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;
    DWORD   Name;
    DWORD   FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;

今回使用するのは以下の2つ。

メンバ
Characteristics 配列の終端フラグ。0であれば終了。
Name インポートしているモジュール名。
OriginalFirstThunk インポート関数の名前テーブルへのアドレス。

この2つはunionで定義されている上に同じサイズですので、全く同じ物になります。

実際の情報はIMAGE_IMPORT_DESCRIPTORの配列として格納されていますので、終端フラグを確認しながら列挙します。
また、インポートしているモジュールの名前もここで取得できますが、例によってANSI文字列ですので変換しておきます。

for (int i = 0; pImport[i].Characteristics != 0; ++i)
{
    IMAGE_IMPORT_DESCRIPTOR& tImport = pImport[i];
    CString szName = CA2T((char*)&(pFile[tImport.Name - delta]));
    _tprintf(TEXT("\n%s"),(LPCTSTR)szName);
}

IMAGE_THUNK_DATA

次に実際の関数名を取得していきますが、こちらは32bitと64bitで別の構造体となっていますので、モジュールのアーキテクチャに対応した方を使って列挙していきます。

今回使用するのは以下の3つとなります。

メンバ
u1.AddressOfData インポート関数の名前情報が含まれる構造体へのアドレス情報。0ならば終了。
u1.Function インポート関数のへのアドレス情報。こちらも0ならば終了。
u1.Ordinal インポート関数の序数。

AddressOfDataには次の「IMAGE_IMPORT_BY_NAME」へのアドレスが含まれています。
ただ名前が無い場合もあるので、以下の方法で名前があるかどうかを確認してから情報を取得します。

インポート関数では名前でのインポートと序数でのインポート(名前無しのインポート)の2つがありますが、その判定にはOrdinalを使用します。
具体的には先頭のビットが立っているかどうかで判定できるのですが、それを補助する為にIMAGE_SNAP_BY_ORDINAL32、IMAGE_SNAP_BY_ORDINAL64というマクロが用意されています。
これらの判定用のビットを落とす為にIMAGE_ORDINAL32、IMAGE_ORDINAL64というマクロも用意されていますので、正しい序数を取得する為にそちらを使用します。

IMAGE_IMPORT_BY_NAME

インポート関数の名前はこちらの構造体で取得します。

メンバ
Name インポート関数の名前。ANSIで保存。
Hint インポート関数の序数。

上記を踏まえたサンプルコードは以下の様な形になります。

32bitの場合

IMAGE_THUNK_DATA32* pINT = (IMAGE_THUNK_DATA32*)&(pFile[tImport.OriginalFirstThunk - delta]);
IMAGE_THUNK_DATA32* pIAT = (IMAGE_THUNK_DATA32*)&(pFile[tImport.FirstThunk - delta]);
for (int j = 0; pINT[j].u1.AddressOfData != 0 && pIAT[j].u1.Function != 0; ++j)
{
    if (IMAGE_SNAP_BY_ORDINAL32(pINT[j].u1.Ordinal))
    {
        _tprintf(TEXT("\n\t(NONAME)[%d]"), (int)IMAGE_ORDINAL32(pINT[j].u1.Ordinal));
    }
    else
    {
        IMAGE_IMPORT_BY_NAME& tName = (*(IMAGE_IMPORT_BY_NAME*)&(pFile[pINT[j].u1.AddressOfData - delta]));
        _tprintf(TEXT("\n\t%s[%d]"), (LPCTSTR)CA2T(tName.Name), tName.Hint);
    }
}

64bitの場合

IMAGE_THUNK_DATA64* pINT = (IMAGE_THUNK_DATA64*)&(pFile[tImport.OriginalFirstThunk - delta]);
IMAGE_THUNK_DATA64* pIAT = (IMAGE_THUNK_DATA64*)&(pFile[tImport.FirstThunk - delta]);
for (int j = 0; pINT[j].u1.AddressOfData != 0 && pIAT[j].u1.Function != 0; ++j)
{
    if (IMAGE_SNAP_BY_ORDINAL64(pINT[j].u1.Ordinal))
    {
        _tprintf(TEXT("\n\t(NONAME)[%d]"), (int)IMAGE_ORDINAL64(pINT[j].u1.Ordinal));
    }
    else
    {
        IMAGE_IMPORT_BY_NAME& tName = (*(IMAGE_IMPORT_BY_NAME*)&(pFile[pINT[j].u1.AddressOfData - delta]));
        _tprintf(TEXT("\n\t%s[%d]"), (LPCTSTR)CA2T(tName.Name), tName.Hint);
    }
}

これでインポートしているモジュールの列挙は完了です。
しかし、ここには遅延ロードしているモジュールの名前が存在していません。

と言う事で次回は遅延ロードしている関数の情報を取得したいと思います。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5