基本的にWindowsのプログラムはウィンドウを作ったり文字を描画する為にWin32APIというOSから提供されている関数郡を用いますが、
今回はそのAPIの呼び出しをフック(横取り)する方法について考えたいと思います。
今回は自分のプログラムのMessageBox関数をフックする場合について考えていきます。
まずMessageBoxの代わりに呼び出される関数を用意します。
int WINAPI FakeMessageBox(HWND,LPCTSTR,LPCTSTR,UINT)
{
//したい処理
}
APIは__stdcall(呼び出された関数がスタックを巻き戻す方式)という呼び出し規約ですが、C/C++言語では__cdecl(呼び出した側がスタックを巻き戻す方式)という
呼び出し規約を用いていますので__stdcallを示すためにWINAPIを付けなければなりません。
ちなみにDelphiやBCCなどのBorland製品は__fastcall(一部をレジスタで渡す)、GCCはなんかよく分からない呼び出し規約だそうです。
APIのアドレスは.idata(インポートセクション)というところで管理されています。つまりそのアドレスを自分が用意した関数のアドレスに
書き換えることでAPIフックを実現します。
まず.idataセクションのアドレスを取得します。それにはWin32ではImageDirectoryEntryToData()という便利な関数が用意されてい
るのでこれを用います。この関数を使うためにはimagehlp.hとimagehlp.libをリンクする必要があります。
DWORD dwBase;
PIMAGE_IMPORT_DESCRIPTOR pImgDesc;
dwBase = (DWORD)GetModuleHandle(NULL); //自分のプロセスの先頭アドレスを取得
pImgDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData((HMODULE)dwBase,
TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&dwIdataSize);
これで.idataの先頭アドレスを取得出来ました。
つぎにインポートしているモジュール名を検索します。MessageBoxの場合はuser32.dllです。
pImgDesc->Nameにモジュール名へのベースアドレスからのオフセットが入っていますのでこれで検索します。
/*モジュール名を検索*/
while(pImgDesc->Name)
{
lpModule = (char*)(dwBase+pImgDesc->Name);
if(!stricmp(lpModule,"user32.dll"))break; //見つかった
pImgDesc++;
}
if(!pImgDesc->Name)return false; //見つからなかった
次にAPIのエクスポート名とアドレスの格納されたテーブルを探します。
関数名とアドレスの格納されたテーブルをそれぞれINT(ImportNameTable)、IAT(ImportAddressTable)と言い、
テーブルへのオフセットはそれぞれpImgDesc->OriginalFirstThunk、pImgDesc->FirstThunkに格納されています。
ちなみにAPIのアドレスが分かっていてAPI名が必要ない場合はINTは必要ありません。
MessageBoxの場合は文字コードによってMessageBoxAとMessageBoxWの二つの関数が用意されていますので
両方を探して書き換える必要があります。
//MessageBoxAを探す時
PIMAGE_THUNK_DATA pIAT,pINT;
pIAT = (PIMAGE_THUNK_DATA)(dwBase+pImgDesc->FirstThunk);
pINT = (PIMAGE_THUNK_DATA)(dwBase+pImgDesc->OriginalFirstThunk);
while(pIAT->u1.Function)
{
if(IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))continue;
pImportName = (PIMAGE_IMPORT_BY_NAME)
(dwBase+(DWORD)pINT->u1.AddressOfData);
if(!stricmp((const char*)pImportName->Name,"MessageBoxA"))
{
//書き換える処理
}
else if(!stricmp((const char*)pImportName->Name,"MessageBoxW"))
{
//書き換える処理
}
pIAT++;pINT++;
}
これでMessageBoxを指すアドレステーブルが判明しました。次は書き換えです。
pIAT->u1.FunctionはMessageBoxへのポインタですから、ポインタを書き換える処理をします。
ただし、ページ属性が読み込み専用になっていますのでVirtualProtectによって書き込み属性に変更しないとWindowsXP SP2
ではエラーが出ると思います。
VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect);
pIAT->u1.Function = (DWORD*)pfnFake; //アドレスを書き換え
VirtualProtect(&pIAT->u1.Function,sizeof(DWORD),dwOldProtect,&dwOldProtect);
これでMessageBoxを呼び出したときに自分の指定した関数が呼び出されると思います。ただし、書き換え直前にMessageBoxを呼び出していた場合
レジスタにまだMessageBoxへのポインタが残っていてこれを使いまわす時がありますので、書き換え直後には失敗する可能性もあります。
明示的リンクの場合はGetProcAddressもフックしないと失敗します。
また、他のアプリケーションをフックする場合は、DLLを用いる必要があります。
特定のアプリケーションの場合はDLL Injectionというような手法が有名です。自分で探してください。