|
 
- 精华
- 0
- 经验
- 22 点
- 金钱
- 83 元
- 威望
- 0 点
- 贡献
- 0 点
|
凡本论坛原创内容,其作者享有著作权,未经许可谢绝转载。
本帖最后由 李马 于 2009-7-3 11:59 编辑
原文链接:http://www.titilima.cn/show-557-1.html
COM 的挂钩其实已经是一个很古老的话题了,其核心技术就是替换 COM 对象虚表中相应位置的函数指针,从而达到挂钩的效果。顺便说一句,这个方法和内核的 SSDT 挂钩是十分类似的。其相应的实现代码也十分简单,如下所示:- 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 改变虚表的页面属性,就像挂钩 SSDT 时要改变 cr0 的保护属性一样。
整个的挂钩过程及使用类似于这个样子:- 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;
- // CoCreateInstance....
- g_pfnQueryInterface = (QIPtr)LSetComHook(punk, 0, HookQueryInterface);
- punk->QueryInterface(...);
复制代码 这种挂钩的方式有一个局限性,就是挂钩函数 HookQueryInterface 不能作为一个非 static 的类成员函数来实现。与之类似,Win32 的 WNDPROC 也无法使用非 static 的类成员函数来封装,实乃一大憾事。
当然,我们可以通过非常规的方法来解决这个问题,比如 thunk。
在开始实现我的 thunk 之前,先来看看一个 COM 方法调用的过程,考虑如下代码:- 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);
复制代码 这个调用过程所对应的汇编代码为: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 方法调用的压栈顺序为:
- 由右至左的各个参数,也就是 STDCALL 调用约定的压栈顺序;
- this 指针;
- 当然,还有 call 的返回地址,这个压栈是在 call 指令内部完成的。
从上面可以看出来,为了把一个 COM 调用重定向到我们自己的类成员函数中,需要做以下工作:
- 保留原 COM 方法的各个参数;
- 保留原 COM 对象的 this 指针;
- 加入我们自己类对象的 this 指针;
- 保留 call 原有的返回地址。
简单说来,这个重定向的过程是将堆栈中插入另外一个 this 指针,仅此而已。
明确了这个操作的步骤,我们可以写出如下的 thunk 代码,这段代码将被放到目标 COM 对象的虚表中。; 弹出 call 的返回地址
pop eax
; 加入自己的 this 指针
push this
; 重新压入 call 的返回地址
push eax
; 跳至挂钩函数之中
jmp addr 相应地,我们为这个 thunk 定义一个结构:- #pragma pack(push, 1)
- typedef struct _tagHookThunk {
- BYTE PopEax; // 0x58
- BYTE Push; // 0x68
- PVOID This;
- BYTE PushEax; // 0x50
- BYTE Jmp; // 0xe9
- PBYTE Addr;
- } HOOKTHUNK, *PHOOKTHUNK;
- #pragma pack(pop)
复制代码 以及一个用于保存挂钩信息的结构:- typedef struct _tagComHook {
- HOOKTHUNK Thunk;
- PVOID* vptr;
- int index;
- PVOID pfnOriginal;
- } COMHOOK;
复制代码 最后,就可以实现这个升级版的挂钩函数了,如下:- HCOMHOOK WINAPI LSetComHook(
- IUnknown* unk,
- int index,
- PVOID This,
- PVOID pfnHook,
- PVOID* pfnOriginal)
- {
- PHOOKHELPER p = (PHOOKHELPER)unk;
- HCOMHOOK h = new COMHOOK;
- // pop eax
- h->Thunk.PopEax = 0x58;
- // push this
- h->Thunk.Push = 0x68;
- h->Thunk.This = This;
- // push eax
- h->Thunk.PushEax = 0x50;
- // jmp addr
- 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;
- }
复制代码 测试代码如下,使用 B 类中的 hook_foo 挂钩了上文中的 A::foo。- 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 成员的类型转换,可以参考《获取成员函数的指针》一文,再次感谢 likunkun 所提供的优雅解决方案。
全部示例代码见附件。 |
附件: 您需要登录才可以下载或查看附件。没有帐号?注册
|