呼び出し元への帰り方:back to the 呼び出し元
2ページ目で少し触れましたが、フックした関数からどうやって、呼び出し元に戻るかを解説します。例えば、次のようなadd関数をフックした場合した場合です。
//定義 int add(int a,int b) { return a + b; } //関数のフックのテスト { //add関数を書き換えて引き算にする。 SEXYHOOK_FUNC_HOOK_2_BEGIN(int,SEXYHOOK_CDECL,add,int a,int b) { return a - b; } SEXYHOOK_FUNC_END(); int cc = add(10,20); printf("\r\n関数のフックのテスト\r\n"); printf("cc: %d\r\n",cc); SEXYHOOK_ASSERT(cc == -10); }
この場合の関数呼び出し図は、このようになります。
//最初の呼び出し 394: int cc = add(10,20); 00402137 push 14h 00402139 push 0Ah 0040213B call @ILT+165(add) (004010aa)----ILT経由でadd関数呼び出し | 00402140 add esp,8 <<<<<<<<< <<< | <<<<<<<<<<<<<<--------------------- 戻り値 00402143 mov dword ptr [cc],eax | | | | | | //ILT | | @ILT+165(?add@@YAHHH@Z): | | 004010AA jmp add (00401470) <------------ | |add関数の実体呼び出し | | | | | | | 9: //関数のテスト | | 10: int add(int a,int b) | | 11: { / | //強引に書き換えてフックルーチンへ飛ばす | 00401470 jmp `main'::`83'::SEXYHOOKFunc388::HookFunction (00402a5e) | ... | | 00401477 inc ebp //ここは実行されない | | 00401478 or al,5Dh //壊れたアセンブラ | | 0040147A ret //ここは実行されない | | 12 } | | | | | | | | //フックルーチンのインナークラス | | class SEXYHOOKFunc388 : public SEXYHOOKFuncBase | | { | | //中略 | | //足し算を引き算に書き換え | | 389: { | | 00402A5E push ebp //←ここに飛ぶ <-- | 00402A5F mov ebp,esp | 390: return a - b; | 00402A61 mov eax,dword ptr [a] | 00402A64 sub eax,dword ptr [b] | 391: } | 00402A67 pop ebp | 00402A68 ret //←ここの ret 命令で、 | //最初に add命令を呼び出したところ(00402140)に戻る. >--------------| //フック関数と 呼び出し規約と引数をそろえているため、 //スタックは破壊されない。 //中略 }
参考までに通常の呼び出しはこんな感じです。
394: int cc = add(10,20); 00402137 push 14h 00402139 push 0Ah 0040213B call @ILT+165(add) (004010aa)----ILT経由でadd関数呼び出し | 00402140 add esp,8 <<<<<<<<< <<< | <<<<<<<<<<<<<<--------------------- 戻り値 00402143 mov dword ptr [cc],eax | | | | | | //ILT | | @ILT+165(?add@@YAHHH@Z): | | 004010AA jmp add (00401470) <------------ | |add関数の実体呼び出し | | | | | | | | | 9: //関数のテスト | | 10: int add(int a,int b) | | 11: { | | 00401470 push ebp <---- | 00401471 mov ebp,esp | 12: return a + b; | 00401473 mov eax,dword ptr [a] | 00401476 add eax,dword ptr [b] | 13: } | 00401479 pop ebp | 0040147A ret //最初にadd関数を呼び出し場所(0040213B)に戻る>>--------------
フックルーチンのretを利用して、呼び出し元に戻っています。フックしたいルーチンと、フックするルーチンの呼び出し規約をそろえるのは、これがやりたいためです。フックしたいルーチンと、フックするルーチンの呼び出し規約が違えば、フックルーチンのretで戻ったときにスタックが壊れてしまいます。
APIフック
SEXYHOOKでは、APIフックは関数フックとは別の機構で行っています。SEXYHOOKAPIBaseクラスが担当しています。これについては、一般的に行われるWindowsのAPIフックの作法どおりなので、特に変なことは行っていません。
このルーチンは、SEXYHOOKの前に作ったオブジェクトリークチェッカー「obcheck」からそのまま流用したものです。obcheckで該当クラスを作るときに、次の2つのサイトが大変参考になりましたので、この場をお借りして紹介させていただきます。
オブジェクトリークチェッカー「obcheck」はオブジェクトリークを検出してくれるツールです。よろしければご利用ください。
まとめ
SEXYHOOKがどのようにして関数、クラスメソッド、APIをフックしているかを解説しました。SEXYHOOK自体は2009年の夏ぐらいから構想と作成に取り掛かり、途中の停滞期を過ぎて、2010年1月の後半に公開できました。
テストを作成するためには接合部を作り出すことが必要であるというアイディアは、レガシーコード改善ガイド(Working Effectively With Legacy Code)からいただきました。この本に出会っていなければ、SEXYHOOKは誕生しませんでした。著者と翻訳者の方ありがとうございます。
SEXYHOOKを作ることができたのも、いろいろなサイトに書かれていた貴重な資料、アイディアがあったからです。貴重な資料をネットに公開していただいた皆様にお礼を述べさせていただきます。
バグの指摘やアドバイスを教えていただいたcppllの皆様、おかげでSEXYHOOKはさらにSEXYになれました。ありがとうございます。うまく動作しないときの愚痴を聞いてくれたtwitterの方たち、どうもすみません。
最後に、内容に間違い、バグなどありましたら、SVN/wikiを修正していただいて結構です。お気軽にどうぞ。ついでによろしければ、twitterで@super_rtiまでご連絡ください。
この文章やドキュメントを英訳してくれる方を募集しています。一緒にcode projectに殴りこみに行きましょう。
happy hacking!
参考資料
- SEXYHOOK
- Visual C++で使用した場合のLINEマクロのバグ:マイクロソフト サポート オンライン
- 『レガシーコード改善ガイド』 マイケル・C・フェザーズ著 ウルシステムズ株式会社監訳、株式会社翔泳社、2009年7月
- 『Working Effectively With Legacy Code』 Michael Feathers著、Prentice Hall PTR、2004年10月
- obcheck オブジェクトリークチェッカー
- cppll(メーリングリスト)
- rtilabs
brute_cast参考
マシン語関係
仮想関数関係
- 仮想関数テーブル : wikipedia
- 仮想関数テーブルを出力するには?
APIフック