用 Thunk 實現 COM 的掛鉤

李馬發表於2009-07-03

原文連結:http://www.titilima.cn/show-557-1.html

COM 的掛鉤其實已經是一個很古老的話題了,其核心技術就是替換 COM 物件虛表中相應位置的函式指標,從而達到掛鉤的效果。順便說一句,這個方法和核心的 SSDT 掛鉤是十分類似的。其相應的實現程式碼也十分簡單,如下所示:

需要指出的是,這裡要使用 VirtualProtect 改變虛表的頁面屬性,就像掛鉤 SSDT 時要改變 cr0 的保護屬性一樣。
整個的掛鉤過程及使用類似於這個樣子:

這種掛鉤的方式有一個侷限性,就是掛鉤函式 HookQueryInterface 不能作為一個非 static 的類成員函式來實現。與之類似,Win32 的 WNDPROC 也無法使用非 static 的類成員函式來封裝,實乃一大憾事。

當然,我們可以通過非常規的方法來解決這個問題,比如 thunk。

在開始實現我的 thunk 之前,先來看看一個 COM 方法呼叫的過程,考慮如下程式碼:

這個呼叫過程所對應的彙編程式碼為:

push        2
mov         eax,dword ptr [pa]
; vptr
mov         ecx,dword ptr [eax]
; this
mov         edx,dword ptr [pa]
push        edx
mov         eax,dword ptr [ecx]
call        eax

也就是說,一個 COM 方法呼叫的壓棧順序為:

  1. 由右至左的各個引數,也就是 STDCALL 呼叫約定的壓棧順序;
  2. this 指標;
  3. 當然,還有 call 的返回地址,這個壓棧是在 call 指令內部完成的。

從上面可以看出來,為了把一個 COM 呼叫重定向到我們自己的類成員函式中,需要做以下工作:

  1. 保留原 COM 方法的各個引數;
  2. 保留原 COM 物件的 this 指標;
  3. 加入我們自己類物件的 this 指標;
  4. 保留 call 原有的返回地址。

簡單說來,這個重定向的過程是將堆疊中插入另外一個 this 指標,僅此而已。

明確了這個操作的步驟,我們可以寫出如下的 thunk 程式碼,這段程式碼將被放到目標 COM 物件的虛表中。

; 彈出 call 的返回地址
pop eax
; 加入自己的 this 指標
push this
; 重新壓入 call 的返回地址
push eax
; 跳至掛鉤函式之中
jmp addr

相應地,我們為這個 thunk 定義一個結構:

以及一個用於儲存掛鉤資訊的結構:

最後,就可以實現這個升級版的掛鉤函式了,如下:

測試程式碼如下,使用 B 類中的 hook_foo 掛鉤了上文中的 A::foo。

其中 member_cast 用於非 static 成員的型別轉換,可以參考《獲取成員函式的指標》一文,再次感謝 likunkun 所提供的優雅解決方案。
點此下載全部示例程式碼

相關文章