SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

「SEXYHOOK」 とある関数の接合部

SEXYHOOKの実装部
とある関数の接合部(2)

関数、クラスメソッド、APIをフックする方法

  • X ポスト
  • このエントリーをはてなブックマークに追加

ダウンロード sexyhook0.8 (80.0 KB)

 キモとなるのは、この3重(void***)キャストでしょうか。

overraideFunctionAddr = (uintptr_t) *((void**)*((void***)inVCallThisPointer) + plusAddress);

//アセンブリでは、さくっとかけるポインタ処理をC言語で汎用的に書くのは大変です。
lea  ecx,[child]
mov  eax,dword ptr [ecx]
jmp  dword ptr [eax+4]

 このように取得したポインタもVSのバージョンによってはILTのジャンプテーブルだったりすることがあるので、検証し、そうであればアドレスを再計算しています。

 これらの計算をするのには、クラスのインスタンス(thisポインタ)が必要になります。クラスのインスタンスを渡してもらうため、SEXYHOOK_CLASS_END_VCALL(クラスのインスタンス)を導入しました。

 さらに問題だったのがgccで当初は、-Wno-pmf-conversionsオプションをつけないと仮想関数へのポインタを取得できない問題がありました。強引に取得しようとすると、アドレスではなく vtableからのindexを返してきます

vtable(仮想関数テーブル)についての詳細な説明は、wikipedia等をご覧ください。

//アドレスを求めてみる。
{
        printf("&Child::aaa: %p\r\n", &Child::aaa);
        printf("&Child::f:   %p\r\n", &Child::f);
        printf("&Child::g:   %p\r\n", &Child::g);
}
返り値
Microsoft Visual C++ gcc
&Child::aaa: 00401127 &Child::aaa: 0x804871c
&Child::f: 0040116D &Child::f: 0x1 ← !?
&Child::g: 00401186 &Child::g: 0x5 ← !?

 また、-Wno-pmf-conversionsオプションをつけたとしても、自身の型(この場合、(int(Child::)())かvoid*以外へキャストしようとすると、またvtableからのindexに落ちてしまうという性質があり、そのほかのルーチンとの整合性を保つのが非常に困難でした

vtableは、クラスのインスタンス(thisポインタ)から計算を行えば仮想関数の場所を求められるようです。

参考:仮想関数テーブルを出力するには?

sexyhookでは、色々試して、以下のコードに行き着きました。

#ifdef __GNUC__
       
//gccでは仮想関数のポインタを取得しようとすると、 vtable からの index を返してしまう。
       
if ( (uintptr_t)inFunctionAddress < 100 )
       
{
               
//クラスのインスタンス(thisポインタ)が渡されていれば、indexから実体の場所を計算可能。
               
               
if (inVCallThisPointer == NULL)
               
{
                       
//thisがないなら計算不可能なので、とりあえずとめる.
                        SEXYHOOK_BREAKPOINT
;
               
}
               
//thisがあれば計算してアドレスを求める.
               
//参考: http://d.hatena.ne.jp/Seasons/20090208/1234115944
                uintptr_t
* vtable = (uintptr_t*) (*((uintptr_t*)inVCallThisPointer));
               
//とりあえず、 (index - 1) / sizeof(void*) でアドレスが求まるみたい.
                uintptr_t index
= ((uintptr_t)inFunctionAddress - 1) / sizeof(void*);

               
//vtable から index を計算する.
                inFunctionAddress
= (void*) (vtable[index] );
       
}
#endif


クラスのインスタンス(thisポインタ)からvtableを求めるには、 以下のような計算うようです。

uintptr_t* vtable = (uintptr_t*) (*((uintptr_t*)クラスのインスタンス));


これで、vtableが求まりました。

次に、この vtable からの index を計算するのですが、これも色々試してみてとりあえず、このようになりました。

uintptr_t index = ((uintptr_t)キャストして変換されたvtableからのindex - 1) / sizeof(void*)


上に書いた例で、gcc で仮想関数へのポインタを取得すると、&Child::f が 0x01 、&Child::gが 0x5 になった例がありましたが、これらは、上記の式により、 &Child::fは (0x01 - 1) / 4 = 0 、&Child::gは (0x05 - 1) / 4 = 1 となります。

vtableへのindexの計算
仮想関数 キャストしたときの値 計算結果
&Child::f: 0x01 (0x01 - 1) / 4 = 0
&Child::g: 0x05 (0x05 - 1) / 4 = 1

これらを利用して、 関数の実体のアドレスを計算します。

inFunctionAddress = (void*) (vtable[index] );


これで仮想関数のアドレスを gcc でも取得することができました。

仮想関数へのポインタは、コンパイラ依存の不思議がいっぱいの超ダークサイドです。それでも、できうる限り、利用者が面倒な指定を行わなくても使えるようにしていきたいと思っています。

次のページ
呼び出し元への帰り方:back to the 呼び出し元

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
「SEXYHOOK」 とある関数の接合部連載記事一覧
この記事の著者

rti(あーるてぃーあい)

働いたら負けだと思っていた元ニートのプログラマ歌って踊れてさくらたんにもハァハァできます。love:C++,アセンブラ,PHP,javascript好きのOO厨房低レイヤープログラムやネットワークサーバからサーバサイドプログラム、ソフトウェア設計、インフラ設計運用までと幅広くやってます。多分設計信者。 あんまり得意ではないけど 人工知能(学習エンジン)とかシステム...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4883 2010/03/03 17:59

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング