COM 的掛鉤其實(shí)已經(jīng)是一個(gè)很古老的話題了,其核心技術(shù)就是替換 COM 對(duì)象虛表中相應(yīng)位置的函數(shù)指針,從而達(dá)到掛鉤的效果。順便說(shuō)一句,這個(gè)方法和內(nèi)核的 SSDT 掛鉤是十分類(lèi)似的。其相應(yīng)的實(shí)現(xiàn)代碼也十分簡(jiǎn)單,如下所示:
C++代碼
-
typedef
?
struct
?_tagHookHelper?{ ??
-
????
PVOID
*?vptr; ??
-
}?HOOKHELPER,?*PHOOKHELPER; ??
-
??
-
PVOID
?WINAPI?LSetComHook( ??
-
????IUnknown*?unk, ??
-
????
int
?index, ??
-
????
PVOID
?pfnHook) ??
-
{ ??
-
????PHOOKHELPER?p?=?(PHOOKHELPER)unk; ??
-
????
PVOID
?ret?=?p->vptr[index]; ??
-
??
-
????
DWORD
?dwOldProtect; ??
-
????VirtualProtect(&p->vptr[index],?
sizeof
(
PVOID
),?PAGE_READWRITE, ??
-
????????&dwOldProtect); ??
-
????p->vptr[index]?=?pfnHook; ??
-
????VirtualProtect(&p->vptr[index],?
sizeof
(
PVOID
),?dwOldProtect,?NULL); ??
-
????
return
?ret; ??
-
}??
需要指出的是,這里要使用 VirtualProtect 改變虛表的頁(yè)面屬性,就像掛鉤 SSDT 時(shí)要改變 cr0 的保護(hù)屬性一樣。
整個(gè)的掛鉤過(guò)程及使用類(lèi)似于這個(gè)樣子:
C++代碼
-
typedef
?
HRESULT
?(STDCALL?*?QIPtr)(IUnknown*?This,?REFIID?riid,?
PVOID
*?ppv); ??
-
??
-
QIPtr?g_pfnQueryInterface?=?NULL; ??
-
??
-
HREUSLT?STDCALL?HookQueryInterface(IUnknown*?This,?REFIID?riid,?
PVOID
*?ppv) ??
-
{ ??
-
????
HRESULT
?hr?=?g_pfnQueryInterface(This,?riid,?ppv); ??
-
????OutputDebugString(_T(
"HookQueryInterface.\n"
)); ??
-
????
return
?hr; ??
-
} ??
-
??
-
IUnknown*?punk?=?NULL; ??
-
??
-
g_pfnQueryInterface?=?(QIPtr)LSetComHook(punk,?0,?HookQueryInterface); ??
-
punk->QueryInterface(...);??
這種掛鉤的方式有一個(gè)局限性,就是掛鉤函數(shù) HookQueryInterface 不能作為一個(gè)非 static 的類(lèi)成員函數(shù)來(lái)實(shí)現(xiàn)。與之類(lèi)似,Win32 的 WNDPROC 也無(wú)法使用非 static 的類(lèi)成員函數(shù)來(lái)封裝,實(shí)乃一大憾事。
當(dāng)然,我們可以通過(guò)非常規(guī)的方法來(lái)解決這個(gè)問(wèn)題,比如 thunk。
在開(kāi)始實(shí)現(xiàn)我的 thunk 之前,先來(lái)看看一個(gè) COM 方法調(diào)用的過(guò)程,考慮如下代碼:
C++代碼
-
class
?A ??
-
{ ??
-
public
: ??
-
????
virtual
?
void
?WINAPI?foo(
int
?i); ??
-
????
int
?m_n; ??
-
}; ??
-
??
-
void
?WINAPI?A::foo(
int
?i) ??
-
{ ??
-
????printf(
"m_n?=?%d,?i?=?%d\n"
,?m_n,?i); ??
-
} ??
-
??
-
A?a; ??
-
A*?pa?=?&a; ??
-
pa->m_n?=?1; ??
-
pa->foo(2);??
這個(gè)調(diào)用過(guò)程所對(duì)應(yīng)的匯編代碼為:
反匯編代碼
-
push????????2 ??
-
mov?????????eax,dword?ptr?[pa] ??
-
-
mov?????????ecx,dword?ptr?[eax] ??
-
-
mov?????????edx,dword?ptr?[pa] ??
-
push????????edx ??
-
mov?????????eax,dword?ptr?[ecx] ??
-
call????????eax??
也就是說(shuō),一個(gè) COM 方法調(diào)用的壓棧順序?yàn)椋?/p>
- 由右至左的各個(gè)參數(shù),也就是 STDCALL 調(diào)用約定的壓棧順序;
- this 指針;
- 當(dāng)然,還有 call 的返回地址,這個(gè)壓棧是在 call 指令內(nèi)部完成的。
從上面可以看出來(lái),為了把一個(gè) COM 調(diào)用重定向到我們自己的類(lèi)成員函數(shù)中,需要做以下工作:
- 保留原 COM 方法的各個(gè)參數(shù);
- 保留原 COM 對(duì)象的 this 指針;
- 加入我們自己類(lèi)對(duì)象的 this 指針;
- 保留 call 原有的返回地址。
簡(jiǎn)單說(shuō)來(lái),這個(gè)重定向的過(guò)程是將堆棧中插入另外一個(gè) this 指針,僅此而已。
明確了這個(gè)操作的步驟,我們可以寫(xiě)出如下的 thunk 代碼,這段代碼將被放到目標(biāo) COM 對(duì)象的虛表中。
匯編代碼
-
-
pop?eax ??
-
-
push?this ??
-
-
push?eax ??
-
-
jmp?addr??
相應(yīng)地,我們?yōu)檫@個(gè) thunk 定義一個(gè)結(jié)構(gòu):
C++代碼
-
#pragma?pack(push,?1)
??
-
typedef
?
struct
?_tagHookThunk?{ ??
-
????
BYTE
?PopEax;??
??
-
????
BYTE
?Push;????
??
-
????
PVOID
?This; ??
-
????
BYTE
?PushEax;?
??
-
????
BYTE
?Jmp;?????
??
-
????
PBYTE
?Addr; ??
-
}?HOOKTHUNK,?*PHOOKTHUNK; ??
-
#pragma?pack(pop)
??
以及一個(gè)用于保存掛鉤信息的結(jié)構(gòu):
C++代碼
-
typedef
?
struct
?_tagComHook?{ ??
-
????HOOKTHUNK?Thunk; ??
-
????
PVOID
*?vptr; ??
-
????
int
?index; ??
-
????
PVOID
?pfnOriginal; ??
-
}?COMHOOK;??
最后,就可以實(shí)現(xiàn)這個(gè)升級(jí)版的掛鉤函數(shù)了,如下:
C++代碼
-
HCOMHOOK?WINAPI?LSetComHook( ??
-
????IUnknown*?unk, ??
-
????
int
?index, ??
-
????
PVOID
?This, ??
-
????
PVOID
?pfnHook, ??
-
????
PVOID
*?pfnOriginal) ??
-
{ ??
-
????PHOOKHELPER?p?=?(PHOOKHELPER)unk; ??
-
??
-
????HCOMHOOK?h?=?
new
?COMHOOK; ??
-
????
??
-
????h->Thunk.PopEax?=?0x58; ??
-
????
??
-
????h->Thunk.Push?=?0x68; ??
-
????h->Thunk.This?=?This; ??
-
????
??
-
????h->Thunk.PushEax?=?0x50; ??
-
????
??
-
????h->Thunk.Jmp?=?0xe9; ??
-
????h->Thunk.Addr?=?(
PBYTE
)((
int
)pfnHook?-?(
int
)h?-?
sizeof
(HOOKTHUNK)); ??
-
????::FlushInstructionCache(::GetCurrentProcess(),?&h->Thunk, ??
-
????????
sizeof
(HOOKTHUNK)); ??
-
??
-
????h->vptr?=?p->vptr; ??
-
????h->index?=?index; ??
-
????h->pfnOriginal?=?LSetComHook(unk,?index,?&h->Thunk); ??
-
??
-
????*pfnOriginal?=?h->pfnOriginal; ??
-
????
return
?h; ??
-
}??
測(cè)試代碼如下,使用 B 類(lèi)中的 hook_foo 掛鉤了上文中的 A::foo。
C++代碼
-
typedef
?
void
?(WINAPI?*?ptr)(A*?This,?
int
?i); ??
-
??
-
class
?B ??
-
{ ??
-
public
: ??
-
????
void
?WINAPI?hook_foo(A*?This,?
int
?i); ??
-
????ptr?pfn; ??
-
}; ??
-
??
-
void
?WINAPI?B::hook_foo(A*?This,?
int
?i) ??
-
{ ??
-
????puts(
"hooked?by?B"
); ??
-
????pfn(This,?i); ??
-
} ??
-
??
-
B?b; ??
-
HCOMHOOK?h?=?LSetComHook((IUnknown*)pa,?0,?&b, ??
-
????member_cast<
PVOID
>(&B::hook_foo),?(
PVOID
*)&b.pfn); ??
-
pa->foo(2);??
其中 member_cast 用于非 static 成員的類(lèi)型轉(zhuǎn)換,可以參考《獲取成員函數(shù)的指針》一文,再次感謝 likunkun 所提供的優(yōu)雅解決方案。
全部示例代碼見(jiàn)附件。