0
点赞
收藏
分享

微信扫一扫

COM ATL IDispatchEx InvokeEx 钩子

脱下愤怒的小裤衩 2022-01-13 阅读 29
p2plinqgnu

 

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接口时,顺便将其地址修改 。

 

举报

相关推荐

0 条评论