関数呼び出し2:直接関数が呼ばれる場合
直接関数本体が呼び出されるケースもあります。以下の例は飛んだ先がいきなり関数の内部です。
95: time_t t = time(NULL); 0040145D push 0 0040145F call time (00402880) ;とんだ先がいきなり関数の中身 00401464 add esp,4 00401467 mov dword ptr [t],eax --- time.c -------------------------------------------------------------------- time: 00402880 push ebp 00402881 mov ebp,esp 00402883 sub esp,0D8h 00402889 lea eax,[loct] 0040288C push eax 0040288D call dword ptr [__imp__GetLocalTime@4 (0042a290)] 00402893 lea ecx,[gmt]
関数呼び出し3:vcall仮想関数
仮想関数の呼び出しはさらに特殊です。
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; } }; Child child; child.f();
このとき、Child::fが呼ばれるまでには複雑な工程をたどります。Microsoft Visual C++ではこのようになります。
lea ecx,[child] //クラスのインスタンス(thisポインタ)をecxに積む call Child::f //Child::fの実態は関数ではなく、クラスのインスタンスからのジャンプテーブル
このようにして取得したアドレスはChild::fではなく、vcallのアドレスになります。
void* p = SEXYHOOK_DARKCAST( &Child::f );
vcallはこのようなアセンブリコードです。
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 バイト等をしている数字
そうやって、jmpした先がChild::fの関数の実体です。
一度vcallを挟んでいる点と、クラスのインスタンスから相対アドレスを求めている点が大きく異なります。これを追跡するコードは以下のようになりました。
//仮想関数の 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) { //ついでなので関数の中を書き換えるため、関数の実体へのアドレスを求める. uintptr_t jmpaddress = *((uintptr_t*)((unsigned char*)overraideFunctionAddr+1)); overraideFunctionAddr = (((uintptr_t)overraideFunctionAddr) + jmpaddress) + 5; //+5は e9 00 00 00 00 (ILTのサイズ) } else if (*((unsigned char*)overraideFunctionAddr+0) == 0x55) { //push ebx overraideFunctionAddr = overraideFunctionAddr; } else { //vcallの解析に失敗しました... SEXYHOOK_BREAKPOINT; } }