関数はどうやって呼び出されるか?
フックの肝の部分はこれでおしまいなのですが、書き換えを行っているSEXYHOOKFuncBase::FunctionHookFunction関数の上の方はまだ説明していません。
この上のほうで何をやっているのかですが、フックルーチンと書き換える関数の位置の特定を行っています。
現在のアーキテクチャでは、実行されるプログラムはメモリのどこかにマッピングされるはずなので、どこかに関数の実態があり、それを呼び出しているはずです。この呼び出しには大きく3つのパターンがあるようです。
以下の例をご覧ください。
//呼び出し側 95: int a = add(1,2); 0040145D push 2 0040145F push 1 00401461 call @ILT+70(add) (0040104b) ← 00401466 add esp,8 00401469 mov dword ptr [a],eax //呼び出される関数 6: int add(int a,int b) 7: { 00401220 push ebp 00401221 mov ebp,esp 8: return a + b; 00401223 mov eax,dword ptr [a] 00401226 add eax,dword ptr [b] 9: } 00401229 pop ebp 0040122A ret
教科書どおりにいうなら、引数をまずスタックに積んで、関数をcallし、結果はeaxレジスタで戻されるという感じでしょうか。しかし、よく見るとcallされたアドレスとadd関数が始まっているアドレスが違います。callされたのは、0040104bアドレスで、add関数が始まっているのは、00401220アドレスです。
関数呼び出し1:ILTを経由する場合
一度ジャンプテーブル(ILT)を経由して関数が呼ばれるときがあります。先ほどのadd関数は、このようにILTが途中に入っています。
//呼び出し側 95: int a = add(1,2); 0040145D push 2 0040145F push 1 00401461 call @ILT+70(add) (0040104b) 00401466 add esp,8 00401469 mov dword ptr [a],eax ↓ @ILT+70(?add@@YAHHH@Z): 0040104B jmp add (00401220) ↓ //呼び出される関数 6: int add(int a,int b) 7: { 00401220 push ebp 00401221 mov ebp,esp 8: return a + b; 00401223 mov eax,dword ptr [a] 00401226 add eax,dword ptr [b] 9: } 00401229 pop ebp 0040122A ret
関数のポインタvoid*p=&add;で取得されるアドレスは、ILTの方の0040104Bになります。
SEXYHOOKでは、必ず関数本体を上書きしたいので、このような処理が行われていればアドレスを再計算し、関数本来の位置を見つけています。
ILTを経由しているか否かの判定は結構いい加減で、(void*)&add等で取得した関数ポインタの先頭が0xe9(JMP命令)で始まっていれば、ILTを経由していると判断してアドレスを再計算しています。
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のサイズ) } else { //即、プログラム領域に飛んでくる場合 overraideFunctionAddr = (uintptr_t)inFunctionAddress; }