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

COM ATL IDispatchEx InvokeEx 钩子相关推荐

  1. c6011取消对null指针的引用_COM编程攻略(二十二 IDL中的枚举,指针,数组)

    上一篇: Froser:COM编程攻略(二十一 异步)​zhuanlan.zhihu.com 本篇主要讲idl的一些语法特性. idl的语法和C语言非常类似,但是它扩展了一些特性,这些特性用于兼容其它 ...

  2. 做一个检测钩子程序的工具

    一.引言 Windows系统是建立在事件驱动的机制上的,每一个事件就是一个消息,每个运行中的程序,也就是所谓的进程,都维护者一个或多个消息队列,消息队列的个数取决于进程内包含的线程的个数.由于一个进程 ...

  3. ActiveX控件键盘消息无法响应 ATL COM

    最近使用ATL创建了一个复合控件,想在其上面相应Delete的键盘消息,但发现直接响应WM_KEYDOWN消息竟然没反应,事情是这样的: 1.起初我直接添加消息WM_KEYDOWN的响应函数,键盘按下 ...

  4. ATL的GUI程序设计(3)

    第三章 ATL的窗口类 CWindowImpl.CWindow.CWinTraits,ATL窗口类的奥秘尽在此三者之中.在本章里,李马将为你详细解说它们的使用方法.另外,本章的内容也可以算是本书的核心 ...

  5. 用C#钩子写一个改键外挂

    我的微信群--软件开发测试工程师交流群,欢迎扫码: 改键是一种习惯,比如在玩儿lol或者dota的时候.理论上玩儿什么游戏都可以改键. 做一个窗体(点击Install--应用改键,点击Uninstal ...

  6. VS2010创建ATL类时需要手动填写ProgID

    在新建ATL类的时候VS2010默认是不填写ProgID的: 所以默认创建的类生成的rgs文件中只有NoRemove CLSID这一栏,导致在JS中使用new ActivexObject(" ...

  7. python中forward的参数_如何将关键字参数传递给preforward钩子使用的forward?

    Torchscript不兼容(截至1.2.0) 首先,您的示例torch.nn.Module有一些小错误(可能是意外造成的).在 第二,您可以将任何传递给forward,register_forwar ...

  8. PE文件和COFF文件格式分析——导出表的应用——一种摘掉Inline钩子(Unhook)的方法

    在日常应用中,某些程序往往会被第三方程序下钩子(hook).如果被下钩子的进程是我们的进程,并且第三方钩子严重影响了我们的逻辑和流程,我们就需要把这些钩子摘掉(Unhook).本件讲述一种在32位系统 ...

  9. ATL::CStringA和std::string之间转换的一些误区

    对于刚做windows下VC的开发同学,类型转换应该是一个令其很苦恼的问题.我刚写工作的时候,也为这类问题不停的在网上搜索转换方法.最近工作中遇到一个"神奇"的bug(一般&quo ...

最新文章

  1. Android实战技巧之六:PreferenceActivity使用详解
  2. 让文本垂直居中的几个方法
  3. c语言自学技巧,轻松学C语言,教给你学习技巧
  4. 支付系统信息流和资金流
  5. 【视频内含福利】原来手机套壳视频是这么做出来的
  6. 碧雪情天服务器地址源如何修改,稀有游戏《碧雪情天online》网络版王者归来一键服务端+客户端 支持转生系统和新图...
  7. c语言程序设计实验实训教程公众号,C语言程序设计基础知道答案公众号
  8. 【文末彩蛋】数据仓库服务 GaussDB(DWS)单点性能案例集锦
  9. Python自动对Word文件中Python程序进行着色
  10. Doris之资源管理
  11. 2016版excel_【重磅分享】最完整EXCEL教程,视频+PPT下载
  12. 计算机专业英语2013单词翻译,计算机专业英语词汇翻译
  13. MATLAB信号处理---学习小案例(2)---采样定理
  14. 设计网页字体css,css教程:网页字体及字体大小的设计
  15. vulnhub——XXE练习
  16. 清橙网A1110. 街道
  17. 我的一篇旧作——卢老师
  18. 字节校招面试题分享,别人已经开始面试了,你不会还没有准备吧?
  19. 卡马克:用C++进行函数式编程
  20. 三种通信方式——单工、半双工和双工通信

热门文章

  1. Thread 类的基本用法(附详细代码),通俗易懂。
  2. 脑机接口2——原理和概念
  3. el-popover 最小宽度 min-width
  4. linux全能模拟器,全能游戏模拟器 RetroArch 1.7.0发布
  5. 例如a =2,n=5 则s=2+22+222+2222+22222
  6. GPS模块——基于Arduino
  7. 基于SSM的网上水果生鲜超市商城
  8. Bently Nevada本特利9200速度传感器简介 及选型注意事项
  9. SimpleITK使用——1. 进行Resample/Resize操作
  10. 【Colab】Colab使用教程(跑本地文件)