仮想関数呼び出しに対応しました。
SEXYHOOK 0.6
http://code.google.com/p/sexyhook/downloads/list
注意:gccでは仮想関数のフックに失敗します...今のところVCのみ対応です
//仮想/純粋仮想メソッドの定義 class Parent { public: virtual int f() { return 1; } virtual int g() =0; }; class Child : public Parent { public: virtual int f() { return 2; } virtual int g() { return 3; } }; int cf,cg,pf,pg; Child child; printf("まだフックしていない\r\n"); cf = child.f(); cg = child.g(); pf = ((Parent*)&child)->f(); pg = ((Parent*)&child)->g(); printf("child.f : %d\r\n",cf); printf("child.g : %d\r\n",cg); printf("((Parent*)&child)->f : %d\r\n",pf); printf("((Parent*)&child)->g : %d\r\n",pg); { //ここからフック SEXYHOOK_CLASS_HOOK_0_BEGIN(int,Child::g) { return 103; //Child::g } SEXYHOOK_CLASS_END_VCALL(child); //thisを渡す printf("Child.gをフック\r\n"); cf = child.f(); cg = child.g(); pf = ((Parent*)&child)->f(); pg = ((Parent*)&child)->g(); printf("child.f : %d\r\n",cf); printf("child.g : %d\r\n",cg); printf("((Parent*)&child)->f : %d\r\n",pf); printf("((Parent*)&child)->g : %d\r\n",pg); SEXYHOOK_ASSERT(cg == 103); SEXYHOOK_ASSERT(pg == 103); }
仮想関数をフックする場合は、 SEXYHOOK_CLASS_END_VCALL(child); と定義してください。
普通のメソッドをフックする場合もSEXYHOOK_CLASS_END_VCALL(child) とつけても害はないので、どんどん定義しましょう。
仮想関数の呼び出しはこのようになります。
Child::f は、関数の内部ではなく、 vcallになります。
lea ecx,[child] //thisポインタを積む call Child::f //Child::fの実態は関数ではなく、thisからのジャンプテーブル Child::f: (vcall) //thisポインタからのジャンプテーブル mov eax,dword ptr [ecx] jmp dword ptr [eax + 0] //実態 Child::f() { return 1; } ||<< マシン語はこんな感じみたいです。 >|asm| vcall: 00402BA0 mov eax,dword ptr [ecx] 00402BA2 jmp dword ptr [eax] 8B 01 FF 20 or (2番目virtualメソッドの場合) 004025D0 mov eax,dword ptr [ecx] 004025D2 jmp dword ptr [eax+4] 8B 01 FF 60 04 ↑ 4バイト目が 60 の場合、次の1バイトが +4 バイト等をしている数字
SEXYHOOKは、 飛び先が vcallであれば、 ユーザからもらった クラスのthisポインタを使って正式なアドレスを計算します。
//フックされる関数の実領域を求める. uintptr_t overraideFunctionAddr = 0; if (*((unsigned char*)inFunctionAddress+0) == 0xe9) { //フック関数も ILT経由で飛んでくる場合 //0xe9 call [4バイト相対アドレス] uintptr_t jmpaddress = *((uintptr_t*)((unsigned char*)inFunctionAddress+1)); overraideFunctionAddr = (((uintptr_t)inFunctionAddress) + jmpaddress) + 5; //+5は e9 00 00 00 00 (ILTのサイズ) //仮想関数の vcallだった場合... if ( *((unsigned char*)overraideFunctionAddr+0) == 0x8B && *((unsigned char*)overraideFunctionAddr+1) == 0x01 && *((unsigned char*)overraideFunctionAddr+2) == 0xFF ) { int plusAddress = 0; if (*((unsigned char*)overraideFunctionAddr+3) == 0x20) { //[[this] + 0] にジャンプ plusAddress = 0; } else if (*((unsigned char*)overraideFunctionAddr+3) == 0x60) { //[[this] + ?] にジャンプ plusAddress = (int) *((unsigned char*)overraideFunctionAddr+4); //4バイト目の1バイト分が加算する値 } else { //[[this] + ?] にジャンプを計算出来ませんでした... SEXYHOOK_BREAKPOINT; } //C言語のおせっかいで、ポインタは型分プラスしてしまうので、ポインタのサイズで割っとく. plusAddress = plusAddress / sizeof(void*); //このような関数に一時的に飛ばされている場合... // vcall: // 00402BA0 mov eax,dword ptr [ecx] // 00402BA2 jmp dword ptr [eax] //8B 01 FF 20 // // or // //004025D0 mov eax,dword ptr [ecx] //004025D2 jmp dword ptr [eax+4] //8B 01 FF 60 04 if ( inVCallThisPointer == NULL ) { //vcallのフックには、 thisポインタが必要です。 //SEXYHOOK_CLASS_END_VCALL(thisClass) を利用してください。 SEXYHOOK_BREAKPOINT; } /* //こういう演算をしたい inVCallThisPointer = &this; _asm { mov ecx,inVCallThisPointer; mov ecx,[ecx]; mov ecx,[ecx]; mov overraideFunctionAddr,ecx; } or _asm { mov ecx,inVCallThisPointer; mov ecx,[ecx]; mov ecx,[ecx+4]; //+? は定義された関数分 virtualの数だけ増えるよ mov overraideFunctionAddr,ecx; } */ //多分こんな感じ,,,泣けてくるキャストだ. overraideFunctionAddr = (uintptr_t) *((void**)*((void***)inVCallThisPointer) + plusAddress); //そこにあるのは 関数の本体 jmp への命令のはず. if (*((unsigned char*)overraideFunctionAddr+0) != 0xe9) { //vcallの解析に失敗しました... SEXYHOOK_BREAKPOINT; } //ついでなので関数の中を書き換えるため、関数の実体へのアドレスを求める. jmpaddress = *((uintptr_t*)((unsigned char*)overraideFunctionAddr+1)); overraideFunctionAddr = (((uintptr_t)overraideFunctionAddr) + jmpaddress) + 5; //+5は e9 00 00 00 00 (ILTのサイズ) } }
void*** のキャストなんてドン引きですね。orz
こんな感じです。
余計に 64bit対応から遠ざかった気がしますが、気にしないことにしましょう。