CodeZine(コードジン)

特集ページ一覧

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

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

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2010/03/02 14:00

ダウンロード 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 でも取得することができました。

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


  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

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

著者プロフィール

  • rti(あーるてぃーあい)

    働いたら負けだと思っていた元ニートのプログラマ 歌って踊れてさくらたんにもハァハァできます。 love:C++,アセンブラ,PHP,javascript好きのOO厨房 低レイヤープログラムやネットワークサーバからサーバサイドプログラム、ソフトウェア設計、インフラ設計運用までと幅広くやってます。...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5