• Microsoft Detours 2.1简介


    from: http://www.cnitblog.com/cc682/archive/2009/05/28/58883.html

    NetRoc

    http://www.DbgTech.net/

    本文从主站点转贴过来的,附件和pdf请访问http://www.DbgTech.net/下载

    一、简介

    《Windows高级调试》第一章中提到了一个基于Microsoft Detours库的内存泄露检查工具LeakDiag。本文对这个库进行一些介绍。

    一句话来说,Detours是一个用来在二进制级别上对程序中的函数(Function)或者过程(Procedure)进行修改的工具库。一般我们将这种技术称为"Hook"。Detours的实现原理是将目标函数的前几个字节改为jmp指令跳转到自己的函数地址,以此接管对目标函数的调用,并插入自己的处理代码。在现实中,这种技术可以应用在很多场景下。比如Hook某些Windows API,在实际调用到系统函数前进行一些过滤工作;软件中使用到了一些没有源代码的第三方库,但是又想增强其中某些函数的功能,等等。

    图1 Hook前后的程序执行流程对比。

    图2 Hook前后目标函数和跳板代码的改变

    Detours相对其他一些Hook库和自己实现的代码来说,通常有以下这些优点:

    • 考虑全面,代码非常稳定,并且经过了微软自己众多产品的验证。
    • 可以简单的用纯C/C++代码实现对类的成员函数的Hook。
    • 购买版权之后的Detours Professional还可以支持x64和IA64处理器。以此为基础编写的代码拥有更强的可移植性。
    • 使用简单,不需要了解汇编指令以及技术细节。

    二、使用方法

    一般来说,使用Detours的代码都具有固定的模式。Detours 1.5和Detours 2.1的接口函数变了很多,这里按照2.1版本对基本的使用方法进行说明。

    常用的函数有下面几个:

    • DetourTransactionBegin() :开始一次Hook或者Unhook过程。
    • DetourUpdateThread() :列入一个在DetourTransaction过程中要进行update的线程。这个函数的作用稍微有一些复杂,会在后面专门说明。
    • DetourAttach() :添加一个要Hook的函数。
    • DetourDetach () :添加一个要Unhook的函数。
    • DetourTransactionCommit() :执行当前的Transaction过程。在这个函数中才会真正进行Hook或者Unhook操作。前面三个函数都只是做一些记录工作。

    在使用的时候,这几个函数的调用步骤基本上也是按照上面列出来的顺序。举例来说,现在想Hook掉API函数MessageBoxA,将消息框弹出的消息修改掉,可以按下面的方法做。

    进行Hook的步骤:

    1. 首先需要定义目标函数的原型。如果目标函数是Windows API,可以到MSDN中查阅,但是需要注意ANSI版本和Unicode版本的区别。如果没有确切的原型声明,或者目标函数是通过逆向工程找出来的,那么需要定义一个和目标函数原型兼容的声明,即参数个数和调用约定要相同。如MessageBoxA的原型是:

      int MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

      使用typedef定义如下:

      typedef int (WINAPI *pfnMessageBoxA)( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

    2. 声明一个指向目标函数的函数指针:

      pfnMessageBoxA g_pMessageBoxA = ::MessageBoxA;

    3. 编写Hook函数的代码,用于替换目标函数。
    4. 调用DetourTransactionBegin开始一次Detours事务。
    5. 对进程中每个可能调用到目标函数的线程,都需要使用DetourUpdateThread加入到update队列中。这是因为Hook时修改目标函数的前几个字节,如果某个线程刚好执行到这几个字节的位置时,粗暴的修改掉会造成该线程出现异常。Detours事务处理时,会先枚举并暂停update队列中所有线程,获取它们的指令指针,如果发现这种情况,则将指令指针修改到跳板代码的对应字节上。这样就避免出现崩溃的问题。
    6. 对每个需要Hook的函数,调用DetourAttach加入到事务列表中。
    7. 调用DetourTransactionCommit进行实际的Hook操作。

    Unhook的过程和上面的流程基本一样,只是第6步改为调用DetourDetach函数。

    Hook MessageBoxA的完整示例代码如下:

    //Hook函数的向前声明

    int WINAPI Hook_MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

    //目标函数原型声明

    typedef int (WINAPI *pfnMessageBoxA)( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

    //指向目标函数的指针

    pfnMessageBoxA g_pMessageBoxA = ::MessageBoxA;

    BOOL StartHook()

    {//开始Hook

        DetourTransactionBegin();

        //只有一个线程,所以GetCurrentThread

        DetourUpdateThread( GetCurrentThread());

        //添加MessageBoxA的Hook

        if( DetourAttach( &(PVOID&)g_pMessageBoxA, Hook_MessageBoxA) != NO_ERROR)

        {

            printf( "Hook MessageBoxA fail. ");

        }

        //完成事务

        if( DetourTransactionCommit() != NO_ERROR)

        {

            printf( "DetourTransactionCommit fail ");

            return FALSE;

        }

        else

        {

            printf( "DetourTransactionCommit ok ");

            return TRUE;

        }

    }

    BOOL StopHook()

    {//停止Hook

        DetourTransactionBegin();

        DetourUpdateThread( GetCurrentThread());

        if( DetourDetach( &(PVOID&)g_pMessageBoxA, Hook_MessageBoxA) != NO_ERROR)

        {

            printf( "Hook MessageBoxA fail. ");

        }

        if( DetourTransactionCommit() != NO_ERROR)

        {

            printf( "DetourTransactionCommit fail ");

            return FALSE;

        }

        else

        {

            printf( "DetourTransactionCommit ok ");

            return TRUE;

        }

    }

    int WINAPI Hook_MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)

    {

        //需要调用原函数时,可以直接使用前面定义的指针变量

        return g_pMessageBoxA( hWnd, "MessageBox after hook.", "TestDetours", MB_OK);

    }

    在附件的示例代码中还包含了Hook类成员函数的代码。流程和上面基本一致,只是需要用一些强制转换来对付编译器的类型检查。

    另外,Detours还包含一系列其他函数,如果需要使用的话,可以参考Detours安装目录下的示例。

    三、使用Detours的注意事项

    总体来说,Detours库的代码是非常稳定的,但是如果使用方法不对,会造成一些问题。有下面一些地方需要特别注意:

    1. 一定要枚举线程并调用DetourUpdateThread函数。否则可能出现很低几率的崩溃问题,这种问题很难被检查出来。
    2. 如果Hook函数在DLL中,那么绝大多数情况下不能在Unhook之后卸载这个DLL,或者卸载存在造成崩溃的危险。因为某些线程的调用堆栈中可能还包含Hook函数,这时卸载掉DLL,调用堆栈返回到Hook函数时内存位置已经不是合法的代码了。
    3. Detours库设计时并没有考虑到卸载的问题,这是因为钩子的卸载本身是不安全的。当Detours库代码存在于DLL中的时候,即使Unhook了所有函数,清理了所有自己使用到的函数,还是会占用一些内存。卸载这个DLL会造成内存泄露,特别是反复的进行加载DLL->Hook->Unhook->卸载DLL的过程,会让这个问题变得非常严重。后面会用一篇专题文章来讨论Detours内存泄露问题的调试和解决。
    4. 有一些非常短的目标函数是无法Hook的。因为jmp指令需要占用一定空间,有些函数太过短小,甚至不够jmp指令的长度,自然是没有办法Hook掉的。

    Detours不支持9x内核的Windows系统。因为9x内核下的内存模型和NT内核下有非常大的差别。

  • 相关阅读:
    pipelinewise 学习二 创建一个简单的pipeline
    pipelinewise 学习一 docker方式安装
    Supercharging your ETL with Airflow and Singer
    ubuntu中使用 alien安装rpm包
    PipelineWise illustrates the power of Singer
    pipelinewise 基于singer 指南的的数据pipeline 工具
    关于singer elt 的几篇很不错的文章
    npkill 一个方便的npm 包清理工具
    kuma docker-compose 环境试用
    kuma 学习四 策略
  • 原文地址:https://www.cnblogs.com/devc/p/4085911.html
Copyright © 2020-2023  润新知