Windows
PortableExecutable
PE

PEファイルのバイナリ構造(6) エクスポート関数の列挙

IMAGE_EXPORT_DIRECTORY

この構造体にはそのモジュールが外部に公開している関数の情報が保存されています。
具体的にはGetProcAddress()で使用する情報ですね。

こちらで重要なのは以下のメンバとなります。

メンバ
NumberOfNames 名前を持つ関数の文字列のアドレス情報が入ってる配列の大きさ(ややこしい!)
NumberOfFunctions 関数のアドレス情報が入っている配列の大きさ
AddressOfNames 名前を持つ関数の文字列のアドレス情報が入ってる配列の先頭アドレス
AddressOfFunctions 関数のアドレス情報が入っている配列の先頭アドレス
AddressOfNameOrdinals 関数の序数のアドレス情報が入っている配列の先頭アドレス

少し解りにくいのですが、

AddressOfNamesの配列に入っているアドレスで関数名を、AddressOfNameOrdinalsで序数(DEFファイルの@1の部分)を確認する
配列の大きさはNumberOfNamesで確認。

AddressOfFunctionsで実際の関数のアドレスを取得する。
配列の大きさはNumberOfFunctionsで確認。

となります。

つまり、名前と序数は1対1で対応していますが、関数の定義はそうとは限らないと言う事です。
これは序数が必ず連続しているとは限らない為です。

ということで、序数と名前とのテーブルを作った後、関数の状態を順番に確認してやると、このモジュールが外部に公開している関数の一覧が取得できます。

ちなみに名前はANSIで保存されているので、Unicodeでコーディングしている場合は変換してやる必要があります。

ちょっと雑ですがサンプルのコードはこちら。

int nName = tExport.NumberOfNames;
int nCount = tExport.NumberOfFunctions;

char* name = (char*)&(pFile[tExport.Name - delta]);
DWORD* dwName = (DWORD*)&(pFile[tExport.AddressOfNames - delta]);
DWORD* dwFunc = (DWORD*)&(pFile[tExport.AddressOfFunctions - delta]);
WORD* dwOrdinal = (WORD*)&(pFile[tExport.AddressOfNameOrdinals - delta]);

std::map<DWORD, CString> mapIndexName;
for (int i = 0; i < nName; ++i)
{
    mapIndexName[dwOrdinal[i]] = CA2T((char*)&(pFile[dwName[i] - delta]));
}
for (int i = 0; i < nCount; ++i)
{
    if (dwFunc[i] == 0)
    {
        continue;
    }
    CString szName = (mapIndexName.find(i) == mapIndexName.end()) ? TEXT("(NONAME)") : mapIndexName[i];
    _tprintf(
        TEXT("Export Named Function %s:%d [%x]\n"),
        (LPCTSTR)szName,
        i + 1,  //  序数は1ベース
        dwFunc[i]
    );
}

名前と序数は必要最小限の配列として確保されていますが、関数ポインタに関しては序数の最大値分までずらっと確保されていますので、序数はあまり飛ばさないほうがモジュールがコンパクトになるかもしれません。

まあ1000個確保しても知れてはいますが。

次はインポート関数の列挙を行います。