detoursが復元なしで元関数を呼び出す仕組み

以前、単純なJMP方式のフックとして紹介したやり方は、元関数を呼び出すために一時アンフックするという実装だった。
図解するとこう。
1.初期状態
2.ExitProcessフック実施後。関数の先頭5バイトがNewExitProcessへのJMPに改竄される。
3.ExitProcessが呼び出された。即座にNewExitProcessにJMPする。

4.NewExitProcessでフックを解除する。これによりExitProcessの先頭5バイトは復元される。
5.NewExitProcessでExitProcessを呼び出す。ExitProcessは実行を完了しNewExitProcessに制御を返す*1
6.NewExitProcessでフックを実施する。

でも問題があって、アンフックしている最中に別スレッドがExitProcessを呼び出すと、当然ながらNewExitProcessを経由しない。また、アンフック中に呼び出されることを阻止する方法もない(と思っている)。したがって2スレッド以上の環境を想定すると、NewExitProcessを100%呼び出す事は保証できない。


一方でDetoursを利用する場合、アンフックすることなくフック前関数を呼び出すことができる。
図解するとこう。
1.初期状態
2.ExitProcessフック実施後。関数の先頭5バイトがNewExitProcessへのJMPに改竄されるとともに、本来のバイト列がOldExitProcess(Trampoline領域)の先頭にコピーされる。OldExitProcessには、そのバイト列の直後にExitProcess+NへのJMPコードが配置される。
3.ExitProcessが呼び出された。即座にNewExitProcessにJMPする。

4.NewExitProcessでOldExitProcessを呼び出す。
5.本来のバイト列を通過後、ExitProcess+NへJMPする。

こうすることでフックを解除せずに元の関数を呼び出している。


ここでポイントなのは ExitProcess+N の部分。Nは可変サイズである。
単純なJMP方式の場合、必ず5バイト(JMP XX XX XX XX)改竄したが、これは本来のバイトを本来の場所に戻すから可能な事。detours方式の場合、本来のバイト列を本来の場所で処理しないため(original codeの場所を見てね)、機械的に5バイトで処理すると意図した命令にならない可能性がある。

たとえば以下はSeAccessCheckの先頭数バイトを抜き出したもの。

; W2K SP4
nt!SeAccessCheck:
804fac2e 55              push    ebp
804fac2f 8bec            mov     ebp,esp
804fac31 53              push    ebx
804fac32 33db            xor     ebx,ebx

; XP SP2
nt!SeAccessCheck:
8056d2f0 8bff            mov     edi,edi
8056d2f2 55              push    ebp
8056d2f3 8bec            mov     ebp,esp
8056d2f5 53              push    ebx

; Vista
nt!SeAccessCheck:
8186da06 8bff            mov     edi,edi
8186da08 55              push    ebp
8186da09 8bec            mov     ebp,esp
8186da0b 83ec0c          sub     esp,0Ch

このうち、W2K SP2 のものは5バイト切り出し、Trampoline領域に復元しても意図した命令にはならない。

55              push    ebp
8bec            mov     ebp,esp
53              push    ebx
33e9            xor     ebp,ecx
; e9 がJMPとして解釈されない! 

そのため、この方式を採用するには逆アセンブラを実装し、命令を破壊しないようにコピーしなければいけない。detoursの場合、disasm.cppがこの部分に当たる。これを実装しないと汎用性のある形にはならないことになる。


結論 ⇒detoursをカーネルモードに移植するのは結構手間*2

*1:しまった! ExitProcessは制御を返さない orz

*2:私はやらない。