COM ATL IDispatchEx InvokeEx 鉤子

 
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;
}
代碼下載地址:
 
六.             調試環境
Windows Server 2003 + VS2005 調試通過。
Windows Xp + VS2003 測試通過。
注意:VS2003 部分版本 會存在調用約定衝突問題。即 __stdcall Hook_InvokeEx函數中調用 __cdecl 函數,編譯器不會爲你管理堆棧。
 
七.             鉤子擴展
使用InvokeEx配合QueryInterface創建組合鉤子,那樣會強大很多,即鉤住QueryInterface,當查詢指定的(我們所關心的函數)如InvokeEx接口時,順便將其地址修改
 
發佈了21 篇原創文章 · 獲贊 3 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章