COM ATL IDispatchEx InvokeEx
钩子
本文详细介绍InvokeEx的钩子安装过程,至于文章标题别在意(权当是一些关键字吧),其实我也不是很清楚InvokeEx是干什么用的,起因是帮助网友【
jameshooo】对InvokeEx进行拦截。进一步,对其进行拦截究竟能干什么,那也只有见仁见智了,如果【
jameshooo】看见此文章还望就此问题回复,以便对InvokeEx钩子有更好的应用。
一.
IDispatchEx
介绍
我们可以将IDispatchEx理解为接口类,实际上它本来就是一个类,嘿嘿。该类拥有一个虚函数表,其实每个COM类都拥有一个这样的虚函数表,这里注意类和对象的区别。虚函数表的内容为类成员函数地址,并不是类似jmp fun1这样的,而是纯地址列表。当调用IDispatchEx类的成员函数时也包括其派生类函数,程序会通过该函数地址列表找到函数的真正地址并进行调用。那么我们可以通过修改该地址列表,来达到拦截指定函数的目标。原理很简单,不过实现起来并非一帆风顺。
二.
虚函数表
通过
QueryInterface
我们可以获取指定的接口如IDispatchEx,这时我们得到的是一个接口指针,代码如下:
IDispatchEx* spDispEx;
pDisp->QueryInterface(IID_IDispatchEx, (void**)&spDispEx);
spDispEx->InvokeEx(0,0,0,0,0,0,0);
那么函数 spDispEx->InvokeEx 地址在哪里呢?
首先我们来看看 IDispatchEx 的定义,在dispex.h文件中如下:
IDispatchEx : public IDispatch
{
public:
virtual HRESULT STDMETHODCALLTYPE GetDispID(
/* [in] */ BSTR bstrName,
/* [in] */ DWORD grfdex,
/* [out] */ DISPID *pid) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE InvokeEx(
/* [in] */ DISPID id,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [in] */ DISPPARAMS *pdp,
/* [out] */ VARIANT *pvarRes,
/* [out] */ EXCEPINFO *pei,
/* [unique][in] */ IServiceProvider *pspCaller) = 0;
virtual HRESULT STDMETHODCALLTYPE DeleteMemberByName(
/* [in] */ BSTR bstrName,
/* [in] */ DWORD grfdex) = 0;
virtual HRESULT STDMETHODCALLTYPE DeleteMemberByDispID(
/* [in] */ DISPID id) = 0;
virtual HRESULT STDMETHODCALLTYPE GetMemberProperties(
/* [in] */ DISPID id,
/* [in] */ DWORD grfdexFetch,
/* [out] */ DWORD *pgrfdex) = 0;
virtual HRESULT STDMETHODCALLTYPE GetMemberName(
/* [in] */ DISPID id,
/* [out] */ BSTR *pbstrName) = 0;
virtual HRESULT STDMETHODCALLTYPE GetNextDispID(
/* [in] */ DWORD grfdex,
/* [in] */ DISPID id,
/* [out] */ DISPID *pid) = 0;
virtual HRESULT STDMETHODCALLTYPE GetNameSpaceParent(
/* [out] */ IUnknown **ppunk) = 0;
};
我们发现 IDispatchEx 是个抽象类,也就是说我们起初得到的指针spDispEx,实际上是个对象,至于这个对象的派生类(或对象)是什么,我们并不清楚,当然这也不是该文章所关心的。下面再看看虚函数表的定义,如下:
typedef struct IDispatchExVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IDispatchEx * This,
/* [in] */ REFIID riid,
/* [iid_is][out] */ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IDispatchEx * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IDispatchEx * This);
HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )(
IDispatchEx * This,
/* [out] */ UINT *pctinfo);
HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )(
IDispatchEx * This,
/* [in] */ UINT iTInfo,
/* [in] */ LCID lcid,
/* [out] */ ITypeInfo **ppTInfo);
HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )(
IDispatchEx * This,
/* [in] */ REFIID riid,
/* [size_is][in] */ LPOLESTR *rgszNames,
/* [in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ DISPID *rgDispId);
/* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )(
IDispatchEx * This,
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr);
HRESULT ( STDMETHODCALLTYPE *GetDispID )(
IDispatchEx * This,
/* [in] */ BSTR bstrName,
/* [in] */ DWORD grfdex,
/* [out] */ DISPID *pid);
/* [local] */ HRESULT ( STDMETHODCALLTYPE *InvokeEx )(
IDispatchEx * This,
/* [in] */ DISPID id,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [in] */ DISPPARAMS *pdp,
/* [out] */ VARIANT *pvarRes,
/* [out] */ EXCEPINFO *pei,
/* [unique][in] */ IServiceProvider *pspCaller);
HRESULT ( STDMETHODCALLTYPE *DeleteMemberByName )(
IDispatchEx * This,
/* [in] */ BSTR bstrName,
/* [in] */ DWORD grfdex);
HRESULT ( STDMETHODCALLTYPE *DeleteMemberByDispID )(
IDispatchEx * This,
/* [in] */ DISPID id);
HRESULT ( STDMETHODCALLTYPE *GetMemberProperties )(
IDispatchEx * This,
/* [in] */ DISPID id,
/* [in] */ DWORD grfdexFetch,
/* [out] */ DWORD *pgrfdex);
HRESULT ( STDMETHODCALLTYPE *GetMemberName )(
IDispatchEx * This,
/* [in] */ DISPID id,
/* [out] */ BSTR *pbstrName);
HRESULT ( STDMETHODCALLTYPE *GetNextDispID )(
IDispatchEx * This,
/* [in] */ DWORD grfdex,
/* [in] */ DISPID id,
/* [out] */ DISPID *pid);
HRESULT ( STDMETHODCALLTYPE *GetNameSpaceParent )(
IDispatchEx * This,
/* [out] */ IUnknown **ppunk);
END_INTERFACE
} IDispatchExVtbl;
interface IDispatchEx
{
CONST_VTBL struct IDispatchExVtbl *lpVtbl;
};
可以发现我们所要拦截的函数在
IDispatchExVtbl + 8
的位置,那么这个函数表的首地址(IDispatchExVtbl)在哪里呢?假设 spDispEx 所指向的地址的内容就是这个虚函数表首地址,表示为** spDispEx,那么InvokeEx的地址就应该是**
spDispEx
+8,注意这只是个假设,实际上也并非如此,并不是这样的。那么为什么呢?我们看到 IDispatchEx 是个抽象基类,这就避免不了对基于IDispatchEx类的对象进行重定义,这样 spDispEx 地址就不是确定的,而InvokeEx的地址却是固定的。不知道我说明白了没有,我是有点糊涂了,呵呵。简单点说就是 spDispEx 指向一个结构体(对象)首地址,而在这个结构体中包含一个指向虚函数地址列表的指针,IDispatchEx类的派生类的虚函数也放入该表中或直接覆盖以实现多态或继承。具体描述请参看下图。
三.
IDispatchEx
结构
从上图中,我们可以总结出大致的IDispatchEx结构,不知道有没有官方的标准结构,希望知道的高手回复下。结构如下:
struct
DispStr
{
LPDWORD lpUnkl; //
未知的函数表地址,通过**
spDispEx
+8,将获得<
未知函数
>
地址
DWORD dwUnk[2]; //
不知道干什么用的
LPVOID This; //
真正的This指针
LPDWORD lpVtbl; //
虚函数首地址,这是我们要找的
};
DispStr* pStr = (DispStr*)(spDispEx); //
我们可以直接获得该结构
四.
InvokeEx
的调用
假设你调用了
spDispEx->InvokeEx(0,0,0,0,0,0,0);
那么程序将执行如下动作:
1.
通过**
spDispEx
+8,获得<
未知函数
>
地址并进行调用,此步需要注意的是,不仅
InvokeEx
会调用<
未知函数
>
,而且其它接口函数也会调用
<
未知函数
>
即使调用参数个数不同(表现为堆栈混乱)也会如此,那么系统如何进行区分这些函数的呢?奥妙就在
<
未知函数
>
和
DispStr
中。
2.
所有进入
<
未知函数
>
中的调用(无论参数有几个),它的第一个参数都是
DispStr
结构体指针,也就是所谓的This(
这个
This
是假的,它指向的是
DispStr
结构体
)。
3.
在<
未知函数
>
中首先判断Test 0x100,[pCh+1CH],并进行跳转。目前还没有进一步分析该跳转分支。其中pCh = 0X01F79100,见图。
4. 将2中的This修改为真实This[pCh+0CH],所以在Hook_
InvokeEx
中看到的This并非为spDispEx。
5.
从虚函数表中取得InvokeEx函数地址,并将[pCh+20H]的 3修改为8,其中8是常量,最后跳转至 jmp
InvokeEx
函数地址。
五.
关键代码
#pragma
once // Hook.H
#include
<dispex.h>
#define
__CALLTYPE __stdcall
typedef
HRESULT (__CALLTYPE *pfnInvokeEx)(IDispatchEx *, DISPID , LCID , WORD , DISPPARAMS *, VARIANT *, EXCEPINFO *, IServiceProvider *);
HRESULT __CALLTYPE Hook_InvokeEx(IDispatchEx *This, DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pVarRes, EXCEPINFO *pei, IServiceProvider *pspCaller);
struct
DispStr
{
LPDWORD lpUnkl;
DWORD dwUnk[2];
LPVOID This;
LPDWORD lpVtbl;
};
class
CInvokeExHook
{
public
:
CInvokeExHook() : m_pDispEx(NULL), m_pfnOrg(NULL) {}
virtual ~CInvokeExHook() { Unhook(); }
public
:
PROC m_pfnOrg;
IDispatchEx* m_pDispEx;
public
:
PROC* GetOrgAddr(IDispatchEx* pDispEx)
{
DispStr* pStr = (DispStr*)pDispEx;
LPDWORD lpVtabl = pStr->lpVtbl;
PROC* ppfn = (PROC* )(lpVtabl + 8);
return ppfn;
}
void Hook(IDispatchEx* pDispEx)
{
PROC* ppfn = GetOrgAddr(pDispEx);
if(*ppfn != (PROC )Hook_InvokeEx)
{
m_pfnOrg = (PROC )(*ppfn);
PROC pfnNew = (PROC )Hook_InvokeEx;
WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, sizeof(PROC), NULL);
m_pDispEx = pDispEx;
}
}
void Unhook()
{
if(m_pDispEx != 0)
{
PROC* ppfn = GetOrgAddr(m_pDispEx);
WriteProcessMemory(GetCurrentProcess(), ppfn, &m_pfnOrg, sizeof(PROC), NULL);
}
}
};
extern
CInvokeExHook m_hook;
// Hook.Cpp
#include
"StdAfx.h"
#include
"./hook.h"
CInvokeExHook m_hook;
HRESULT __CALLTYPE Hook_InvokeEx(IDispatchEx *This, DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pVarRes, EXCEPINFO *pei, IServiceProvider *pspCaller)
{
DispStr* pStr = (DispStr*)(m_hook.m_pDispEx);
if(pStr->This != This)
{
TRACE("Warning:m_pDispEx != this !!!/n"); // __cdecl
HRESULT hr = ((pfnInvokeEx )m_hook.m_pfnOrg)(This, id, lcid, wFlags, pdp, pVarRes, pei, pspCaller);
return hr;
}
if(m_hook.m_pDispEx != 0 && id != 0)
{
CComBSTR name;
m_hook.m_pDispEx->GetMemberName(id, &name);
TRACE(_T("dispid:0x%x(%S), flag:%s/n"), id, name,
wFlags==DISPATCH_METHOD?_T("METHOD"):(wFlags==DISPATCH_PROPERTYGET?_T("PROPERTYGET"):(wFlags==DISPATCH_PROPERTYPUT?_T("PROPERTYPUT"):(wFlags==DISPATCH_PROPERTYPUTREF?_T("PROPERTYPUTREF"):_T("CONSTRUCT"))))
);
}
HRESULT hr = ((pfnInvokeEx )m_hook.m_pfnOrg)(This, id, lcid, wFlags, pdp, pVarRes, pei, pspCaller);
return hr;
}
代码下载地址:
http://download.csdn.net/source/336174
六.
调试环境
Windows Server 2003 + VS2005 调试通过。
Windows Xp + VS2003 测试通过。
注意:VS2003 部分版本 会存在调用约定冲突问题。即
__stdcall Hook_
InvokeEx
函数中调用
__cdecl
函数,编译器不会为你管理堆栈。
七.
钩子扩展
使用InvokeEx配合
QueryInterface
创建组合钩子,那样会强大很多,即钩住QueryInterface,当查询指定的(我们所关心的函数)如InvokeEx接口时,顺便将其地址修改
。