諸注意
自分用なのでもしかしたら間違ってるかもしれない…
IATの概要
IATの場所
インポートセクションのインポートテーブルは IMAGE_IMPORT_DESCRIPTOR
構造体の配列として表現されていて、インポートしているDLLごとに用意されてる( IMAGE_IMPORT_DESCRIPTOR
の Name
メンバを見れば何の名前のDLLの IMAGe_IMPORT_DESCRIPTOR
なのかわかる)。
その中の FirstThunk
メンバがIATのアドレスを保持している。
IATの状態について
IATはプログラムがロードされる前と後で入ってるものが違う。
- ロード前:
IMAGE_THUNK_DATA
構造体の配列でILTと同じ - ロード後:
IMAGE_IMPORT_DESCRIPTOR->Name
のDLLからの各関数のアドレス
Visual Studio を立ち上げる
新しいプロジェクトの作成をしたあと、以下の 空のプロジェクト を選ぶ。
次に、下記の所でx64用に変更する。
次に、ALT-Enter
を押してソリューションのプロパティ画面を表示し、リンカー > システム
の所でサブシステムを Windows ...
に変更する。
続いて同じプロパティ画面で、詳細
を見て、文字セットを マルチバイト文字セットを使用する
に変更する(これしないと文字化けした)。
これでOK
して閉じた後、C++コードを以下のようにして追加する。
これで準備が整った。
使ってるDLLの列挙
いきなりIATを列挙するのはキツイので、まずはインポートしてるDLLを列挙してみる。
IATの先ほどの内容を振り返ると、DLL事にインポートテーブルがあり、インポートテーブル( IMAGE_IMPORT_DESCRIPTOR
)の Name
メンバにそのDLLの名前がある。これを全て列挙する。
# include <windows.h>
# include <dbghelp.h>
# include <stdlib.h>
# include <iostream>
# include <sstream>
# pragma comment(lib, "dbghelp.lib")
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
) {
ULONG cbSize;
TCHAR szMyPath[MAX_PATH];
HMODULE hModule;
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = NULL;
GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
hModule = GetModuleHandle((LPCSTR)szMyPath);
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &cbSize);
if (pImageImportDescriptor == NULL)
MessageBox(NULL, TEXT("error"), TEXT("null"), MB_OK);
else {
while (pImageImportDescriptor->Name != NULL) {
LPCSTR pszDllName = (LPCSTR)((PBYTE)(hModule)+pImageImportDescriptor->Name);
MessageBox(NULL, pszDllName, TEXT("DLL name"), MB_OK);
pImageImportDescriptor++;
}
}
CloseHandle(hModule);
return 0;
}
ここで、最初の方に
GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
hModule = GetModuleHandle((LPCSTR)szMyPath);
があるのは、実は先ほどFirstChunkにはIATへのアドレスがあるといったが、これは具体的には相対アドレスであり、最初の図の OptionalHeader
の ImageBase
からの相対アドレスになっている。なんで相対アドレスなのかというと、ImageBase
はプログラムがメモリ上にロードされる際の先頭の位置であり、ここがいつも固定ではないため、ここからの相対アドレスとして全部指定するようになってる。
所々、(LBYTE)hModule + ...
のような計算があるのはそういう理由。
実際の処理として大事で押さえておけばいい所は以下の2点だけ、
// IMAGE_IMPORT_DESCRIPTORのポインタの取得
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &cbSize);
// IMAGE_IMPORT_DESCRIPTORのNameメンバを取得
LPCSTR pszDllName = (LPCSTR)((PBYTE)(hModule)+pImageImportDescriptor->Name);
これをでは、Visual Studio上で、Ctrl-b
でビルドし、ビルドすると、
こんな感じでどこに実行ファイルができたか書いてあるので、これを実行する。
こんな感じで何度も MessageBox
でDLLの名前が列挙されるはず。
IATの列挙(本題)
とりあえず、一番最初の Kernel32.dll
からインポートされている関数のアドレスをIATから列挙したい。
ただ、アドレスだけ表示されても Kernel32.dll
の何の関数かわからないので、関数名も列挙したい。
しかし、よく考えると、プログラムをロード後のIATにはアドレスはあっても関数の名前を表すメンバは存在しない。それなので関数名の方は、最初の図を思い出して、FirstThunk
ではなく、OriginalFirstThunk
が指しているILT(IMAGE_THUNK_DATAの集まり)の方の Name
メンバから取ってくる。
実際のコードが以下。
# include <windows.h>
# include <dbghelp.h>
# include <stdlib.h>
# include <iostream>
# include <sstream>
# pragma comment(lib, "dbghelp.lib")
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
) {
ULONG cbSize;
TCHAR szMyPath[MAX_PATH];
HMODULE hModule;
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = NULL;
PIMAGE_THUNK_DATA pFirstThunk = NULL;
PIMAGE_THUNK_DATA pOriginalFirstThunk = NULL;
GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
hModule = GetModuleHandle((LPCSTR)szMyPath);
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &cbSize);
pFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->FirstThunk);
pOriginalFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->OriginalFirstThunk);
LPCSTR pszDllName = (LPCSTR)((PBYTE)hModule + pImageImportDescriptor->Name);
MessageBox(NULL, pszDllName, "will show imports func addr from this dll", MB_OK);
while(pFirstThunk->u1.Function != NULL) {
std::stringstream ss;
ss << "0x" << std::hex << pFirstThunk->u1.Function << std::endl;
PIMAGE_IMPORT_BY_NAME pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PBYTE)hModule + pOriginalFirstThunk->u1.AddressOfData);
LPCSTR pszFuncName = (LPCSTR)&pImageImportByName->Name[0];
MessageBox(NULL, ss.str().c_str(), pszFuncName, MB_OK);
pFirstThunk++;
pOriginalFirstThunk++;
}
CloseHandle(hModule);
return 0;
}
注意するべきなのは、IATはロード後はアドレスが入っているので、IMAGE_THUNK_DATA
型を使うのは変なのでは? と思うが、まぁプログラム上でこうしてるだけで、実際にIATの方から関数名を見つけようとしてもできなかったので、これであってる。
流れを把握する上で押さえておくべき行は以下。
// IMAGE_IMPORT_DESCRIPTORのFirstThunkとOriginalFirstThunkへのポインタを取得
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &cbSize);
pFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->FirstThunk);
pOriginalFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->OriginalFirstThunk);
// IMAGE_IMPORT_DESCRIPTORのNameからDLL名を取得(今回は最初のKernel32.dllだけ)
LPCSTR pszDllName = (LPCSTR)((PBYTE)hModule + pImageImportDescriptor->Name);
// これがIATのアドレス(ロード後の各関数の配置されてるアドレス)
pFirstThunk->u1.Function
// ILTのIMAGE_THUNK_DATA構造体のAddressOfDataメンバにIMAGE_IMPORT_BY_NAMEという構造体があり、
// そのName属性が関数名になってる。(ILTに直接関数名があるわけではないので注意)
PIMAGE_IMPORT_BY_NAME pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PBYTE)hModule + pOriginalFirstThunk->u1.AddressOfData);
これで再びビルドして実行すると、
こんな感じで関数とそのアドレスが列挙されるので、IATが列挙されていることを確認できる。