浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第22章 DLL注入和API拦截(3)

Posted on 2016-02-11 21:01  浅墨浓香  阅读(2171)  评论(0编辑  收藏  举报

22.6 API拦截的一个例子

22.6.1 通过覆盖代码来拦截API

(1)实现过程

  ①在内存中对要拦截的函数(假设是Kernel32.dll中的ExitProcess)进行定位,从而得到它的内存地址。

  ②把这个函数的起始的几个字节保存在我们自己的内存中。

  ③用CPU的一条JUMP指令来覆盖这个函数起始的几个字节,这条JUMP指令用来跳转到我们替代函数的内存地址。当然,我们的替代函数的函数签名必须与要拦截的函数的函数签名完全相同,即所有的参数必须相同,返回值必须相同,调用约定也必须相同。

  ④现在,当线程调用被拦截函数时,跳转指令实际上跳转到我们的替代函数。这时,就可以执行我们想要执行的任何代码。

  ⑤为了撤销对函数的拦截,我们必须把第2步中保存下来的字节放回被拦截函数起始的几个字节中。

  ⑥我们调用被拦截函数(现在己经不再对它进行拦截了),让该函数执行它正常处理。

  ⑦当原来的函数返回时,我们再次执行第2步和第3步,这样的替代函数将来还会被调用到。

(2)主要缺点

  ①因CPU的指令在x86、x64、IA-64等上各不相同,所以该方法严重依赖CPU。

  ②而且在抢占式、多线程环境下根本不能工作,因为一个线程覆盖另一个函数起始位置的代码需要一定时间,在这个过程中,另一个线程可能试图调用同一个函数,其结构可能是灾难性的!)

22.6.2 通过修改模块的导入段来拦截API

(1)要解决上述两个不足,可以用另一种拦截API的方法,即通过修改模块的导入段来拦截API,这种方法不仅容易,而且也相当健壮。

(2)为了拦截特定的函数,我们需要修改模块导入段的IAT表,让线程调用该导入函数时,重新跳转到我们指定的函数地址去。(利用PEView工具查看)

 

(3)内存中的IAT表:——修改其内容的自定义函数:ReplaceIATEntryInOneMod函数

  ①举例:拦截MyAppExe.exe模块中对ExitProcess函数的调用

PROC pfnOrig = GetProcAddress(GetModuleHandle("Kernel32"),"ExitProcess");

HModule HmodCaller = GetModuleHandle("MyAppExe.exe");

ReplaceIATEntryInOneMod("Kernel32.dll",pfnOrig,MyExitProcess,hmodCaller);

  ②ReplaceIATEntryInOneMod只能修改一个模块的指定API。如果同一个地址空间中有另一个DLL也调用了相同的API(如多个DLL都调用了ExitProcess函数),则需要用ReplaceIATEntryInAllMods函数。

  ③当调用Replace*AllMods后如果调用LoadLibrary载入一个新的DLL时,新载入的DLL仍有可能会调用ExitProcess,所以我们必须同时拦截LoadLibrary*函数,并为新载入的DLL调用Replace*InOnMod,但考虑到这个DLL又依赖其他静态链接的DLL时,而他们也可能调用ExitProcess,这时当调用LoadLibrary*的同时,将没有机会更新其他这些Dll的IAT表,所以简单的一种方案就是载入一个新DLL时,调用Replace*InAllMods来替代Replace*InOneMod。

  ④如果是调用GetProcAddress函数来获取ExitProcess的地址时仍然会成功,所以还必须拦截GetProcAddress函数。

(4)内存中的导出表(EAT)—修改其内容的自定义函数:ReplaceEATEntryInOneMod函数

【LastMessageBox info】示例程序——用于拦截别的进程对MessageBox的调用

(1)利用Windows钩子技术来注入DLL

(2)必须同时拦截MessageBoxA和MessageBoxW两个函数(位于User32.dll)

(3)CAPIHook类的构造函数里记录哪些API被拦截下来,析构里会重置为原来API的地址。

(4)当CAPIHook对象被构造时,要拦截的函数所在的模块必须己经被载入。而延时载入模块会直到函数被调用该模块才会被载入,所以CAPIHook类无法处理延迟载入的模块。

 //动态链接库端的文件

//APIHook.h

/************************************************************************
Module:  APIHook.h
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/

#pragma once
#include <windows.h>
//////////////////////////////////////////////////////////////////////////
class CAPIHook{
public:
    //拦截将被注入的进程所有模块中指定的API
    CAPIHook(PSTR pszCalleeModName, //被拦截函数所在的模块(DLL)
             PSTR pszFuncName,      //要被拦截函数的名称
             PROC pfnHook);         //拦截函数(也叫替代函数)地址

    //从所有模块中卸载一个函数(即拦截函数)
    ~CAPIHook();

    //返回最初的、被拦截函数的地址
    operator PROC(){ return (m_pfnOrig); }

    //是否连当前调用CAPIHook的模块(即该DLL本身)也要拦截?
    //这个变量在ReplaceIATEntryInAllMods中会使用到,这里被声明为static
    static BOOL ExcludeAPIHookMod;

public:
    //调用真正的GetProcAddress
    static FARPROC WINAPI GetProcAddressRaw(HMODULE hmod, PCSTR pszProcName);

private:
    static CAPIHook* sm_pHead;     //链表中,第一个被拦截函数
    CAPIHook* m_pNext;             //下一个被拦截函数

    PCSTR m_pszCalleeModName; //含被拦截函数的模块名称(ANSI)
    PCSTR m_pszFuncName;      //被拦截的函数名称(ANSI)
    PROC m_pfnOrig; //在被调用者中原始的函数,也就是被拦截的函数
    PROC m_pfnHook;  //拦截函数——用来替代m_pfnOrig

private:
    //在一个模块的导入表中替换指定符号(函数)的地址
    //即hmodCaller模块导入了pszCalleeModeName模块里的pfnOrg函数。现在要将hmodCaller导入表中的
    //pfnOrg函数替换为pfnHook函数。
    static void WINAPI ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, //被调用模块名称
                                               PROC pfnOrig,             //被拦截函数地址,pszCalleeModeName模块中
                                               PROC pfnHook,            //拦截函数(替换函数)地址
                                               HMODULE hmodCaller);     //调用模块,即要修改导入段的模块

    //替换当前进程中,所有模块的导入表中指定符号地址为拦截函数的地址
    static void WINAPI ReplaceIATEntryInAllMods(PCSTR pszCalleeModName, PROC pfnOrig, PROC pfnHook);

    //在一个模块的导出表中替换指定符号(函数)的地址
    static void WINAPI ReplaceEATEntryInOneMods(HMODULE hmod, PCSTR pszFunctionName, PROC pfnNew);

private:
    //当DLL是在被拦截以后加载进来时,使用下面这个函数
    static void WINAPI FixupNewlyLoadedModuled(HMODULE hmod, DWORD dwFlags);

    //当Dlls后在被加载进行时使用
    static HMODULE WINAPI LoadLibraryA(PCSTR pszModulePath);
    static HMODULE WINAPI LoadLibraryW(PCWSTR pszModulePath);
    static HMODULE WINAPI LoadLibraryExA(PCSTR pszModulePath,HANDLE hFile,DWORD dwFlags);
    static HMODULE WINAPI LoadLibraryExW(PCWSTR pszModulePath, HANDLE hFile, DWORD dwFlags);

    //如果拦截函数要需面,返回被替换函数的真实地址(被保存在链表里)
    static FARPROC WINAPI GetProcAddress(HMODULE hmod, PCSTR pszProcName);

private:
    //实例化下面这些函数
    static CAPIHook  sm_LoadLibraryA;
    static CAPIHook  sm_LoadLibrarayW;
    static CAPIHook  sm_LoadLibraryExA;
    static CAPIHook  sm_LoadLibrarayExW;
    static CAPIHook  sm_GetProcAddress;
};

//APIHook.cpp

/************************************************************************
Module:  APIHook.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/

#include "../../CommonFiles/CmnHdr.h"
#include <ImageHlp.h>
#pragma comment(lib, "ImageHlp")

#include "APIHook.h"
#include "../../CommonFiles/ToolHelp.h"
#include <strsafe.h>

//////////////////////////////////////////////////////////////////////////

//CAPIHook中保存函数对象的链表头部
CAPIHook* CAPIHook::sm_pHead = NULL;

//默认调用CAPHook()的模块不被拦截
BOOL CAPIHook::ExcludeAPIHookMod = TRUE;

//////////////////////////////////////////////////////////////////////////
CAPIHook::CAPIHook(PSTR pszCalleeModName, PSTR pszFuncName, PROC pfnHook){
    //注意:只有要拦截的模块被加载时,函数才能被拦截。一个解决方案是,把函数名称
    //      保存起来。然后在LoadLibrary*函数的拦截处理器中分析CAPIHook各实例
    //      检查pszCalleeModName模块是否是要被拦截的模块

    
    m_pNext = sm_pHead;  //下一个节点
    sm_pHead = this; //因为每拦截一个函数都要创建一个CAPIHook的实例

    //将被拦截函数信息保存起来
    m_pszCalleeModName = pszCalleeModName;
    m_pszFuncName = pszFuncName;
    m_pfnHook = pfnHook;
    HMODULE hmod = GetModuleHandleA(pszCalleeModName);
    m_pfnOrig = GetProcAddressRaw(hmod, m_pszFuncName);

    //如果函数不存在,则退出,这种情况发生在模块未被加载进来
    if (m_pfnOrig == NULL){
        wchar_t szPathname[MAX_PATH];
        GetModuleFileNameW(NULL, szPathname, _countof(szPathname));
        wchar_t sz[1024];
        StringCchPrintfW(sz, _countof(sz), 
                         TEXT("[%4u - %s] 找不到 %S\r\n"),
                         GetCurrentProcessId(),szPathname,pszFuncName);
        OutputDebugString(sz);
        return;
    }

    //拦截当前被载入的所有模块中指定的函数
    ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnOrig, m_pfnHook);
}

//////////////////////////////////////////////////////////////////////////
CAPIHook::~CAPIHook(){
    //取消拦截
    ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnHook, m_pfnOrig);

    //删除链表
    CAPIHook* p = sm_pHead;
    if (p == this){  //删除头结点
        sm_pHead = p->m_pNext;
    } else{
        BOOL bFound = FALSE;

        //遍历链表,并删除当前结点
        for (; !bFound && (p->m_pNext != NULL);p = p->m_pNext){
            if (p->m_pNext == this){
                p->m_pNext = p->m_pNext->m_pNext;
                bFound = TRUE;
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////////
//返回包含指定内存地址的模块句柄
static HMODULE ModuleFromAddress(PVOID pv){
    MEMORY_BASIC_INFORMATION mbi;
    return ((VirtualQuery(pv, &mbi, sizeof(mbi)) != 0) 
              ? (HMODULE)mbi.AllocationBase : NULL);
}

//////////////////////////////////////////////////////////////////////////
//如果模块没被加载,处理一些非预期的异常
LONG WINAPI  InvalidReadExceptionFilter(PEXCEPTION_POINTERS pep){
    //处理所有非预期的异常,因为在这种情况下,我们根本不修补任何模块
    LONG lDisposition = EXCEPTION_EXECUTE_HANDLER; //表示可以进入__except模块处理了

    //注意:pep->ExceptionRecord->ExceptionCode的值可能为0xC0000005
    return (lDisposition);
}
//////////////////////////////////////////////////////////////////////////
void CAPIHook::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, 
                   PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller){
    //获得hmodCaller模块的导入表
    ULONG ulSize;

    //当传入ImageDirectorEntryToData的是一个一个无效的模块句柄时,从而会
    //产生一个0xC0000005的异常。如Explorer进程(Windows资源管理器)的另一
    //个线程快速动态载入和卸装DLL,这时可能导致该函数异常。我们需要捕获异
    //常来保护代码。
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
    __try{
        pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
            hmodCaller,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulSize);
        
    }
    __except (InvalidReadExceptionFilter(GetExceptionInformation())){
        //这里什么也不错,但当hmodCaller为无效句柄时,此时可以保证线程仍能
        //正常运行,但pImportDesc为NULL。
    }

    if (pImportDesc == NULL)
        return; //模块没有导入段或还未被加载。

    //查找导入段中被调用函数的地址
    for (; pImportDesc->Name; pImportDesc++){
        //模块导入段的所有字符串都是以ANSI格式保存的
        PSTR pszModName = (PSTR)((PBYTE)hmodCaller + pImportDesc->Name);
        if (lstrcmpiA(pszModName,pszCalleeModName) == 0){

            //获得调用者导入表中要被拦截函数的地址
            PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
                       ((PBYTE)hmodCaller + pImportDesc->FirstThunk);
            //将要被拦截函数的地址替换为新的拦截函数的地址

            //替当前函数替换为新函数的地址
            for (; pThunk->u1.Function;pThunk++){

                //获得函数地址
                PROC* ppfn = (PROC*)&pThunk->u1.Function;

                //是要查找的函数?
                BOOL bFound = (*ppfn == pfnCurrent);
                if (bFound){                    
                    if (!WriteProcessMemory(GetCurrentProcess(),ppfn,&pfnNew,
                        sizeof(pfnNew),NULL)&&(ERROR_NOACCESS == GetLastError())) {
                        //该内存写保护
                        DWORD dwOldProtect;
                        if (VirtualProtect(ppfn,sizeof(pfnNew),PAGE_EXECUTE_WRITECOPY,
                            &dwOldProtect)){
                            WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,
                                               sizeof(pfnNew), NULL);
                            VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect,
                                           &dwOldProtect);
                        }
                    }
                    return; //做完,退出
                }
            }

        } //有些编译器会在同一模块中生成多个导入段。要循环各导入段,直至找到并修改为地址
    }
}

//////////////////////////////////////////////////////////////////////////
void CAPIHook::ReplaceEATEntryInOneMods(HMODULE hmod, PCSTR pszFunctionName, PROC pfnNew){
    //获得hmod的导出表
    ULONG ulSize;

    PIMAGE_EXPORT_DIRECTORY pExportDir = NULL;
    __try{
        pExportDir = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryEntryToData(
            hmod, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ulSize);

    }
    __except (InvalidReadExceptionFilter(GetExceptionInformation())){
        //这里什么也不错,但当hmod为无效句柄时,此时可以保证线程仍能
        //正常运行,但pExportDir为NULL。
    }

    if (pExportDir == NULL)
        return; //模块没有导入段或还未被加载。

    PDWORD pdwNamesRvas = (PDWORD)((PBYTE)hmod + pExportDir->AddressOfNames);
    PWORD pwNameOrdinals = (PWORD)((PBYTE)hmod + pExportDir->AddressOfNameOrdinals);
    PDWORD pdwFunctionAddresses = (PDWORD)((PBYTE)hmod + pExportDir->AddressOfFunctions);

    //查找导出段中被调用函数的地址
    for (DWORD n = 0; n < pExportDir->NumberOfNames; n++){
        //模块导入段的所有字符串都是以ANSI格式保存的
        PSTR pszFuncName = (PSTR)((PBYTE)hmod + pdwNamesRvas[n]);
        if (lstrcmpiA(pszFuncName, pszFuncName) != 0)
            continue;

        //找到指定的函数
        WORD ordinal = pwNameOrdinals[n];

        //获取函数的地址
        PROC* ppfn = (PROC*)&pdwFunctionAddresses[ordinal];

        //将新的地址写入RVA
        pfnNew = (PROC)((PBYTE)pfnNew - (PBYTE)hmod);

        //将被拦截函数替换为替换函数
        if (!WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,
            sizeof(pfnNew), NULL) && (ERROR_NOACCESS == GetLastError())) {
            //该内存写保护
            DWORD dwOldProtect;
            if (VirtualProtect(ppfn, sizeof(pfnNew), PAGE_EXECUTE_WRITECOPY,
                &dwOldProtect)){
                WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,
                                   sizeof(pfnNew), NULL);
                VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect,
                               &dwOldProtect);
            }
        }
        break;
    }
}

//////////////////////////////////////////////////////////////////////////
void CAPIHook::ReplaceIATEntryInAllMods(PCSTR pszCalleeModName, PROC pfnCurrent, PROC pfnNew){
    HMODULE hmodThisMod = ExcludeAPIHookMod ?
        ModuleFromAddress(ReplaceIATEntryInAllMods) : NULL; //是否连该DLL本身的模块也替换,
                                                            //默认是被排除的,即本DLL不替换。

    //获取进程模块列表
    CToolhelp th(TH32CS_SNAPMODULE, GetCurrentProcessId());

    MODULEENTRY32 me = { sizeof(me) };
    for (BOOL bOk = th.ModuleFirst(&me); bOk;bOk = th.ModuleNext(&me)){

        //注意:我们不替换自己的模块
        if (me.hModule != hmodThisMod){
            //替换函数
            ReplaceIATEntryInOneMod(
                pszCalleeModName, pfnCurrent, pfnNew, me.hModule);
        }
    }
}

//////////////////////////////////////////////////////////////////////////
//当DLL是在被拦截以后加载进来时,使用下面这个函数
void WINAPI CAPIHook::FixupNewlyLoadedModuled(HMODULE hmod, DWORD dwFlags){
    //如果一个模块最近被加载,拦截相应的API
    if ((hmod != NULL) &&  
        (hmod !=ModuleFromAddress(FixupNewlyLoadedModuled)) &&//不要拦截自己的模块
        ((dwFlags & LOAD_LIBRARY_AS_DATAFILE) ==0) &&
        ((dwFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE) == 0) &&
        ((dwFlags & LOAD_LIBRARY_AS_IMAGE_RESOURCE) == 0)
        ){

        // 如果某个对象的原函数地址为NULL,可能是延迟加载等原因导致原来DLL不在内存中而引起的
        // 因此在这里根据模块名获得函数现在的地址    
        for (CAPIHook* p = sm_pHead; p != NULL;p=p->m_pNext){
            if (p->m_pfnOrig !=NULL){ 
                //本来只需对这个被加载的DLL修复那些被拦截的函数地址即可,
                //但新载入的DLL,如果又静态链接(注意,不是动态)了其他DLL,这另外的这个DLL也调
                //用了要被拦截的函数时,就产生了链接依赖。所以可以简单地调用替换所有模块的这个函数
                ReplaceIATEntryInAllMods(p->m_pszCalleeModName, p->m_pfnOrig, p->m_pfnHook);
            } else{            
#ifdef _DEBUG
                //这里不应该就结束了
                wchar_t szPathname[MAX_PATH];
                GetModuleFileNameW(NULL, szPathname, _countof(szPathname));
                wchar_t sz[1024];
                StringCchPrintfW(sz, _countof(sz),
                                 TEXT("[%4u - %s]找不到 %S\r\n"), 
                                 GetCurrentProcessId(),szPathname,p->m_pszCalleeModName);
                OutputDebugString(sz);
#endif
            }
        }
    }
}


//当Dlls后在被加载进行时使用
HMODULE WINAPI CAPIHook::LoadLibraryA(PCSTR pszModulePath){
    HMODULE hmod = ::LoadLibraryA(pszModulePath);
    FixupNewlyLoadedModuled(hmod, 0);
    return (hmod);
}

HMODULE WINAPI CAPIHook::LoadLibraryW(PCWSTR pszModulePath){
    HMODULE hmod = ::LoadLibraryW(pszModulePath);
    FixupNewlyLoadedModuled(hmod, 0);
    return (hmod);
}
HMODULE WINAPI CAPIHook::LoadLibraryExA(PCSTR pszModulePath, HANDLE hFile, DWORD dwFlags)
{
    HMODULE hmod = ::LoadLibraryExA(pszModulePath,hFile,dwFlags);
    FixupNewlyLoadedModuled(hmod, dwFlags);
    return (hmod);
}
HMODULE WINAPI CAPIHook::LoadLibraryExW(PCWSTR pszModulePath, HANDLE hFile, DWORD dwFlags)
{
    HMODULE hmod = ::LoadLibraryExW(pszModulePath, hFile, dwFlags);
    FixupNewlyLoadedModuled(hmod, dwFlags);
    return (hmod);
}
//////////////////////////////////////////////////////////////////////////
//获得GetProcAddess函数的真实地址
FARPROC  CAPIHook::GetProcAddressRaw(HMODULE hmod, PCSTR pszProcName){
    return (::GetProcAddress(hmod, pszProcName)); //调用全局的API
}
//////////////////////////////////////////////////////////////////////////
//拦截通过对GetProcAddress调用而直接获得要被拦截函数地址的
//获取被Hook函数被保存起来的真实地址
FARPROC WINAPI CAPIHook::GetProcAddress(HMODULE hmod, PCSTR pszProcName){
    //获取函数的真实地址
    FARPROC pfn = GetProcAddressRaw(hmod, pszProcName);

    //函数是否是己被hook过的呢?
    CAPIHook* p = sm_pHead;

    for (; (pfn != NULL) && (p != NULL);p=p->m_pNext){
        if (pfn == p->m_pfnOrig){//函数地址匹配
            pfn = p->m_pfnHook;
            break;
        }
    }
    return (pfn);
}

//////////////////////////////////////////////////////////////////////////
//拦截LoadLibrary和GetProcAddress函数
CAPIHook  CAPIHook::sm_LoadLibraryA("Kernel32.dll", "LoadLibraryA",
                                    (PROC)CAPIHook::LoadLibraryA);

CAPIHook  CAPIHook::sm_LoadLibrarayW("Kernel32.dll", "LoadLibraryW",
                                    (PROC)CAPIHook::LoadLibraryW);

CAPIHook  CAPIHook::sm_LoadLibraryExA("Kernel32.dll", "LoadLibraryExA",
                                    (PROC)CAPIHook::LoadLibraryExA);

CAPIHook  CAPIHook::sm_LoadLibrarayExW("Kernel32.dll", "LoadLibraryExW",
                                     (PROC)CAPIHook::LoadLibraryExW);

//LastMsgBoxInfoLib.h

/************************************************************************
Module:  LastMsgBoxInfoLib.h
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/
#pragma  once

#include <windows.h>

#ifndef LASTMSGBOXINFOLIBAPI
#define LASTMSGBOXINFOLIBAPI extern "C" __declspec(dllimport)
#endif

//////////////////////////////////////////////////////////////////////////
LASTMSGBOXINFOLIBAPI  BOOL WINAPI LastMsgBoxInfo_HookAllApps(BOOL bInstall, DWORD dwThreadId);

//////////////////////////////////////////////////////////////////////////

//LastMsgBoxInfoLib.cpp

/************************************************************************
Module:  LastMsgBoxInfoLib.cpp
NOtices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/
#include "../../CommonFiles/CmnHdr.h"
#include <tchar.h>
#include "APIHook.h"

#define LASTMSGBOXINFOLIBAPI extern "C" __declspec(dllexport)
#include "LastMsgBoxInfoLib.h"
#include <strsafe.h>

//////////////////////////////////////////////////////////////////////////
//被拦截函数的原型
//MessageBoxA
typedef int(WINAPI* PFNMESSAGEBOXA)(HWND hWnd, PCSTR pszText, 
                PCSTR pszCaption,UINT uType);

//MessageBoxW
typedef int(WINAPI* PFNMESSAGEBOXW)(HWND hWnd, PCWSTR pszText,
                                    PCWSTR pszCaption, UINT uType);

//外部变量
extern CAPIHook  g_MessageBoxA;
extern CAPIHook  g_MessageBoxW;

//////////////////////////////////////////////////////////////////////////
HHOOK g_hHook = NULL;


//////////////////////////////////////////////////////////////////////////
//这个函数将MessageBox信息发送给我们的主对话框
void SendLastMsgBoxInfo(BOOL bUnicode, PVOID pvCaption, PVOID pvText, int nResult){
    //获取弹出“MessageBox”进程的路径
    wchar_t szProcessPathname[MAX_PATH];
    GetModuleFileNameW(NULL, szProcessPathname, MAX_PATH); //NULL—获取调用该函数的当前模块

    //将返回值转为“可识别”的字符串
    PCWSTR pszResult = L"(未知)";
    switch (nResult)
    {
        case IDOK:        pszResult = L"确定";        break;
        case IDCANCEL:    pszResult = L"取消";        break;
        case IDABORT:    pszResult = L"中止";        break;
        case IDRETRY:    pszResult = L"重试";        break;
        case IDIGNORE:    pszResult = L"忽略";        break;
        case IDYES:        pszResult = L"";            break;
        case IDNO:        pszResult = L"";            break;
        case IDCLOSE:    pszResult = L"关闭";        break;
        case IDHELP:    pszResult = L"帮助";        break;
        case IDTRYAGAIN:pszResult = L"重试";        break;
        case IDCONTINUE:pszResult = L"继续";        break;
    }

    //创建要发送到主对话框的字符串
    wchar_t sz[2048];
    StringCchPrintfW(sz, _countof(sz), bUnicode ?
            L"进程:(%d) %s\r\n 标题:%s\r\n 内容:%s\r\n 选择结果:%s\r\n":
            L"进程:(%d) %s\r\n 标题:%S\r\n 内容:%S\r\n 选择结果:%s\r\n",
            GetCurrentProcessId(),szProcessPathname,pvCaption,pvText,pszResult);

    //将字符串发送给主对话框
    COPYDATASTRUCT cds = { 0, ((DWORD)wcslen(sz) + 1)*sizeof(wchar_t), sz };
    FORWARD_WM_COPYDATA(FindWindow(NULL, TEXT("Last MessageBox Info")), NULL,
                        &cds,SendMessage);
}

//////////////////////////////////////////////////////////////////////////
//MessageBoxW的替换函数
int WINAPI Hook_MessageBoxW(HWND hWnd, PCWSTR pszText, LPCWSTR pszCaption, UINT uType){
    //调用原MMessageBoxW
    int nRet = ((PFNMESSAGEBOXW)(PROC)g_MessageBoxW)
              (hWnd,pszText,pszCaption,uType);

    //将“MessageBox”的信息发给我们的主对话框程序
    SendLastMsgBoxInfo(TRUE,(PVOID)pszCaption,(PVOID)pszText,nRet);

    //返回选择结果给调用者
    return (nRet);
}

//////////////////////////////////////////////////////////////////////////
//MessageBoxA的替换函数
int WINAPI Hook_MessageBoxA(HWND hWnd, PCSTR pszText, LPCSTR pszCaption, UINT uType){
    //调用原MessageBoxA
    int nRet = ((PFNMESSAGEBOXA)(PROC)g_MessageBoxA)
        (hWnd, pszText, pszCaption, uType);

    //将“MessageBox”的信息发给我们的主对话框程序
    SendLastMsgBoxInfo(FALSE, (PVOID)pszCaption, (PVOID)pszText, nRet);

    //返回选择结果给调用者
    return (nRet);
}

//////////////////////////////////////////////////////////////////////////
//挂钩MessageBoxA和MessageBoxW函数
CAPIHook g_MessageBoxA("user32.dll", "MessageBoxA", (PROC)Hook_MessageBoxA);
CAPIHook g_MessageBoxW("user32.dll", "MessageBoxW", (PROC)Hook_MessageBoxW);

//////////////////////////////////////////////////////////////////////////
//钩子程序
static LRESULT WINAPI GetMsgProc(int code, WPARAM wParam, LPARAM lParam){
    return (CallNextHookEx(g_hHook, code, wParam, lParam));
}

//////////////////////////////////////////////////////////////////////////
//返回包含包含指定内存地址的模块句柄
static HMODULE ModuleFromAddress(PVOID pv){

    MEMORY_BASIC_INFORMATION mbi;
    return ((VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)
            ? (HMODULE)mbi.AllocationBase:NULL);
}

//////////////////////////////////////////////////////////////////////////
BOOL  WINAPI LastMsgBoxInfo_HookAllApps(BOOL bInstall, DWORD dwThreadId){
    BOOL bOk;

    if (bInstall){
        chASSERT(g_hHook == NULL);//钩子未安装,防止被安装两次

        //安装Windows钩子
        g_hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc,
                    ModuleFromAddress(LastMsgBoxInfo_HookAllApps), dwThreadId);
        bOk = (g_hHook != NULL);
    } else{
        chASSERT(g_hHook != NULL);//钩子己被安装
        bOk = UnhookWindowsHookEx(g_hHook);
        g_hHook = NULL;
    }

    return (bOk);
}
////////////////////////////////////文件结束///////////////////////////////

//测试程序

//LastMsgBoxInfo.cpp

/************************************************************************
Module:  LastMsgBoxInfo.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/

#include "../../CommonFiles/CmnHdr.h"
#include <tchar.h>
#include "resource.h"
#include "../22_LastMsgBoxInfoLib/LastMsgBoxInfoLib.h"

#pragma comment(lib,"../../Debug/22_LastMsgBoxInfoLib.lib")

//////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam){
    chSETDLGICONS(hwnd, IDI_LASTMSGBOXINFO);
    SetDlgItemText(hwnd, IDC_INFO, 
                   TEXT("正在等待“MessageBox”被关闭"));
    return (TRUE);
}

//////////////////////////////////////////////////////////////////////////
void Dlg_OnSize(HWND hWnd, UINT state, int cx, int cy){
    SetWindowPos(GetDlgItem(hWnd, IDC_INFO), NULL, 0, 0, cx, cy, SWP_NOZORDER);
}

//////////////////////////////////////////////////////////////////////////
void  Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity){
    switch (id)
    {
    case IDCANCEL:
        EndDialog(hwnd, id);
        break;
    }
}

//////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnCopyData(HWND hWnd, HWND hWndFrom, PCOPYDATASTRUCT pcds){

    //将被拦截的进程发送给我们的消息对话框信息显示出来
    SetDlgItemTextW(hWnd, IDC_INFO, (PCWSTR)pcds->lpData);
    return (TRUE);
}

//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch (uMsg)
    {
        chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
        chHANDLE_DLGMSG(hWnd, WM_SIZE, Dlg_OnSize);
        chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
        chHANDLE_DLGMSG(hWnd, WM_COPYDATA, Dlg_OnCopyData);
    }

    return (FALSE);
}

//////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int){
    DWORD dwThreadId = 0; //为系统中所有线程安装钩子
    LastMsgBoxInfo_HookAllApps(TRUE, dwThreadId); //TRUE为安装钩子,0表示所有线程
    DialogBox(hInstExe, MAKEINTRESOURCE(IDD_LASTMSGBOXINFO), NULL, Dlg_Proc);
    LastMsgBoxInfo_HookAllApps(FALSE, 0); //FALSE为卸载钩子
    return (0);
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 22_LastMsgBoxInfo.rc 使用
//
#define IDD_LASTMSGBOXINFO              101
#define IDI_LASTMSGBOXINFO              102
#define IDC_INFO                        1001

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//.rc文件

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_LASTMSGBOXINFO DIALOG 0, 0, 379, 55
STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
CAPTION "Last MessageBox Info"
FONT 8, "MS Shell Dlg"
BEGIN
    EDITTEXT        IDC_INFO,0,0,376,52,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
END


/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1               ICON                    "LastMsgBoxInfo.ico"
#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED