C/C++ HOOK 全局 API

简介: 全局 Hook 不一定需要用到 Dll ,比如全局的鼠标钩子、键盘钩子都是不需要 Dll 的,但是要钩住 API,就需要 Dll 的协助了,下面直接放上 Dll 的代码,注意这里使用的是 MFC DLL。

全局 Hook 不一定需要用到 Dll ,比如全局的鼠标钩子、键盘钩子都是不需要 Dll 的,但是要钩住 API,就需要 Dll 的协助了,下面直接放上 Dll 的代码,注意这里使用的是 MFC DLL。

// Test_Dll(mfc).cpp : 定义 DLL 的初始化例程。
//

#include "stdafx.h"
#include "Test_Dll(mfc).h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#pragma region 我的代码

#define  UM_WNDTITLE WM_USER+100    // 自定义消息(私有窗口类的消息标识符)

// 全局共享变量(多进程之间共享数据)
#pragma data_seg(".Share")
    HWND g_hWnd = NULL;                // 主窗口句柄
    HHOOK hhk = NULL;                // 鼠标钩子句柄
    HINSTANCE hInst = NULL;            // 本dll实例句柄
#pragma data_seg()
#pragma comment(linker, "/section:.Share,rws")


// 全局变量
HANDLE hProcess=NULL;                // 进程句柄
BOOL bIsInjected=FALSE;                // 是否注入完成
typedef int (WINAPI *MsgBoxA)(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType);            // 声明一个别名 MsgBoxA
typedef int (WINAPI *MsgBoxW)(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);        // 声明一个别名 MsgBoxW
MsgBoxA oldMsgBoxA=NULL;            // 保存原函数地址
MsgBoxW oldMsgBoxW=NULL;            // 保存原函数地址
FARPROC pfMsgBoxA=NULL;                // 指向原函数地址的远指针
FARPROC pfMsgBoxW=NULL;                // 指向原函数地址的远指针
BYTE OldCodeA[5];                    // 老的系统API入口代码
BYTE NewCodeA[5];                    // 要跳转的API代码 (jmp xxxx)
BYTE OldCodeW[5];                    // 老的系统API入口代码
BYTE NewCodeW[5];                    // 要跳转的API代码 (jmp xxxx)
int WINAPI MyMessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType);                // 我们自己的 MessageBoxA 函数
int WINAPI MyMessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);            // 我们自己的 MessageBoxW 函数


// 开启钩子(修改 API 头 5 个字节)
void HookOn() 
{ 
    // 检验进程句柄是否为空
    ASSERT(hProcess!=NULL);

    DWORD dwTemp = 0,        // 修改后的内存保护属性
        dwOldProtect,        // 之前的内存保护属性
        dwRet = 0,            // 内存写入成功标志,0不成功、1成功
        dwWrite;            // 写入进程内存的字节数
 
    // 更改虚拟内存保护
    VirtualProtectEx(        
        hProcess,            // 进程句柄
        pfMsgBoxA,            // 指向保护区域地址的指针
        5,                    // 要更改的区域的字节大小
        PAGE_READWRITE,        // 内存保护类型,PAGE_READWRITE:可读可写
        &dwOldProtect        // 接收原来的内存保护属性
        ); 

    // 判断是否成功写入内存
    dwRet = WriteProcessMemory(
        hProcess,            // 进程句柄
        pfMsgBoxA,            // 指向写入地址的指针
        NewCodeA,            // 指向存放写入内容的缓冲区指针
        5,                    // 写入字节数
        &dwWrite            // 接收传输到进程中的字节数
        );
    if (0==dwRet||0==dwWrite){
        TRACE("NewCodeA 写入失败");    // 记录日志信息
    }    

    // 恢复内存保护状态
    VirtualProtectEx(hProcess,pfMsgBoxA,5,dwOldProtect,&dwTemp);

    // 同上,操作剩下的 MessageBoxW
    VirtualProtectEx(hProcess,pfMsgBoxW,5,PAGE_READWRITE,&dwOldProtect); 
    dwRet=WriteProcessMemory(hProcess,pfMsgBoxW,NewCodeW,5,&dwWrite);
    if (0==dwRet||0==dwWrite){TRACE("NewCodeW 写入失败");}
    VirtualProtectEx(hProcess,pfMsgBoxW,5,dwOldProtect,&dwTemp);
}

// 关闭钩子(修改 API 头 5 个字节)
void HookOff()
{ 
    // 检验进程句柄是否为空
    ASSERT(hProcess!=NULL);

    DWORD dwTemp = 0,            // 修改后的内存保护属性
        dwOldProtect = 0,        // 之前的内存保护属性
        dwRet = 0,                // 内存写入成功标志,0不成功、1成功
        dwWrite = 0;            // 写入进程内存的字节数

    // 更改虚拟内存保护
    VirtualProtectEx(
        hProcess,                // 进程句柄
        pfMsgBoxA,                // 指向保护区域地址的指针
        5,                        // 要更改的区域的字节大小
        PAGE_READWRITE,            // 内存保护类型,PAGE_READWRITE:可读可写
        &dwOldProtect            // 接收原来的内存保护属性
        ); 

    dwRet = WriteProcessMemory(
        hProcess,                // 进程句柄
        pfMsgBoxA,                // 指向写入地址的指针
        OldCodeA,                // 指向存放写入内容的缓冲区指针
        5,                        // 写入字节数
        &dwWrite                // 接收传输到进程中的字节数
        ); 
    if (0==dwRet||0==dwWrite){
        TRACE("OldCodeA 写入失败");    // 记录日志信息
    }

    // 恢复内存保护状态
    VirtualProtectEx(hProcess,pfMsgBoxA,5,dwOldProtect,&dwTemp); 

    // 同上,操作剩下的 MessageBoxW
    VirtualProtectEx(hProcess,pfMsgBoxW,5,PAGE_READWRITE,&dwOldProtect); 
    WriteProcessMemory(hProcess,pfMsgBoxW,OldCodeW,5,&dwWrite); 
    if (0==dwRet||0==dwWrite){TRACE("OldCodeW 写入失败");}
    VirtualProtectEx(hProcess,pfMsgBoxW,5,dwOldProtect,&dwTemp);  
}

// 代码注入
void Inject()
{
    // 如果还没有注入
    if (!bIsInjected){ 

        //保证只调用1次
        bIsInjected=TRUE;

        // 获取函数地址
        HMODULE hmod=::LoadLibrary(_T("User32.dll"));
        oldMsgBoxA=(MsgBoxA)::GetProcAddress(hmod,"MessageBoxA");    // 原 MessageBoxA 地址
        pfMsgBoxA=(FARPROC)oldMsgBoxA;                                // 指向原 MessageBoxA 地址的指针
        oldMsgBoxW=(MsgBoxW)::GetProcAddress(hmod,"MessageBoxW");    // 原 MessageBoxW 地址
        pfMsgBoxW=(FARPROC)oldMsgBoxW;                                // 指向原 MessageBoxW 地址的指针
  
        // 指针为空则结束运行
        if (pfMsgBoxA==NULL){MessageBox(NULL,_T("cannot get MessageBoxA()"),_T("error"),0);return;}
        if (pfMsgBoxW==NULL){MessageBox(NULL,_T("cannot get MessageBoxW()"),_T("error"),0);return;}

        // 将原API中的入口代码保存入 OldCodeA[],OldCodeW[]
        _asm 
        { 
            lea edi,OldCodeA        ; 把 OldCodeA 的地址给 edi
            mov esi,pfMsgBoxA        ; 把 MessageBoxA 的地址给 esi
            cld                        ; 方向标志位复位
            movsd                    ; 复制双子
            movsb                    ; 复制字节
        }
        _asm 
        { 
            lea edi,OldCodeW        ; 以相同的方式操作 MessageBoxW
            mov esi,pfMsgBoxW
            cld 
            movsd 
            movsb 
        }

        // 将原 API 第一个字节改为 jmp
        NewCodeA[0]=0xe9;            
        NewCodeW[0]=0xe9;            

        // 计算 jmp 后面要跟的地址
        _asm 
        { 
            lea eax,MyMessageBoxA                ; 将 MyMessageBoxA 的地址给 eax
            mov ebx,pfMsgBoxA                    ; 将 MessageBoxA 的地址给 ebx
            sub eax,ebx                            ; 计算 jmp 后面要跟的地址
            sub eax,5 
            mov dword ptr [NewCodeA+1],eax 
        } 
        _asm 
        { 
            lea eax,MyMessageBoxW                ; 以相同的方式操作 MessageBoxW
            mov ebx,pfMsgBoxW
            sub eax,ebx 
            sub eax,5 
            mov dword ptr [NewCodeW+1],eax 
        } 
 
        // 开始 Hook
        HookOn(); 
    }
}

// 假 MessageBoxA
int WINAPI MyMessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType)
{
    int nRet = 0;        

    // 先恢复 Hook,不然会造成死循环
    HookOff();

    // 检验 MessageBoxA 是否失败(失败返回 0)
    nRet = ::MessageBoxA(hWnd,"Hook MessageBoxA",lpCaption,uType);
    //nRet=::MessageBoxA(hWnd,lpText,lpCaption,uType);    // 调用原函数(如果你想暗箱操作的话)

    // 再次 HookOn,否则只生效一次
    HookOn();

    return nRet;
}

// 假 MessageBoxW
int WINAPI MyMessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType)
{
    int nRet = 0;

    // 先恢复 Hook,不然会造成死循环
    HookOff();

    // 检验 MessageBoxW 是否失败(失败返回 0)
    nRet = ::MessageBoxW(hWnd,_T("Hook MessageBoxW"),lpCaption,uType);
    //nRet=::MessageBoxW(hWnd,lpText,lpCaption,uType);    // 调用原函数(如果你想暗箱操作的话)

    // 再次 HookOn,否则只生效一次
    HookOn();

    return nRet;
}
#pragma endregion

#pragma region 忽略掉
//
//TODO: 如果此 DLL 相对于 MFC DLL 是动态链接的,
//        则从此 DLL 导出的任何调入
//        MFC 的函数必须将 AFX_MANAGE_STATE 宏添加到
//        该函数的最前面。
//
//        例如:
//
//        extern "C" BOOL PASCAL EXPORT ExportedFunction()
//        {
//            AFX_MANAGE_STATE(AfxGetStaticModuleState());
//            // 此处为普通函数体
//        }
//
//        此宏先于任何 MFC 调用
//        出现在每个函数中十分重要。这意味着
//        它必须作为函数中的第一个语句
//        出现,甚至先于所有对象变量声明,
//        这是因为它们的构造函数可能生成 MFC
//        DLL 调用。
//
//        有关其他详细信息,
//        请参阅 MFC 技术说明 33 和 58。
//

// CTest_DllmfcApp

BEGIN_MESSAGE_MAP(CTest_DllmfcApp, CWinApp)
END_MESSAGE_MAP()


// CTest_DllmfcApp 构造

CTest_DllmfcApp::CTest_DllmfcApp()
{
    // TODO: 在此处添加构造代码,
    // 将所有重要的初始化放置在 InitInstance 中
}


// 唯一的一个 CTest_DllmfcApp 对象

CTest_DllmfcApp theApp;
#pragma endregion

// 程序入口
BOOL CTest_DllmfcApp::InitInstance()
{
    CWinApp::InitInstance();

    #pragma region 我的代码
    
    // 获取 dll 自身实例句柄
    hInst = AfxGetInstanceHandle();

    // 获取调用 dll 的进程 ID
    DWORD dwPid = ::GetCurrentProcessId();
    
    // 获取调用 dll 的进程句柄
    hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);

    // 开始注入
    Inject();

    #pragma endregion

    return TRUE;
}

// 程序出口
int CTest_DllmfcApp::ExitInstance()
{
    // TODO: 在此添加专用代码和/或调用基类

    #pragma region 我的代码
    
    // 恢复其他进程的的 API 
    HookOff();

    #pragma endregion

    return CWinApp::ExitInstance();
}

#pragma region 我的代码

// 鼠标钩子回调
LRESULT CALLBACK MouseProc(
                           int nCode,      // 钩子代号
                           WPARAM wParam,  // 消息标识符
                           LPARAM lParam   // 光标的坐标
                           ){
    if (nCode==HC_ACTION){
        
        // 将钩子所在窗口句柄发给主程序
        ::SendMessage(
            g_hWnd,                                            // 接收消息的窗口句柄
            UM_WNDTITLE,                                    // 发送的消息
            wParam,                                            // 附加消息信息
            (LPARAM)(((PMOUSEHOOKSTRUCT)lParam)->hwnd)        // 附加消息信息,此处为鼠标所在窗口的窗口句柄
            );
        /* 
        typedef struct tagMOUSEHOOKSTRUCT { // 传递给 WH_MOUSE 的鼠标事件信息结构体
            POINT     pt;                    // 光标的 xy 坐标
            HWND      hwnd;                    // 光标对应的窗口句柄
            UINT      wHitTestCode;            // 是否击中
            ULONG_PTR dwExtraInfo;            // 消息关联
        } MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT, *LPMOUSEHOOKSTRUCT;
        */
    }

    // 讲消息传递给下一个钩子
    return CallNextHookEx(
        hhk,        // 钩子句柄,此处为鼠标钩子
        nCode,        
        wParam,
        lParam
        );
}

// 安装钩子
BOOL WINAPI StartHook(HWND hWnd)
{
    // 获取鼠标所在的主窗口句柄
    g_hWnd = hWnd;
    
    // 获取鼠标钩子句柄
    hhk = ::SetWindowsHookEx(
        WH_MOUSE,        // 钩子类型
        MouseProc,        // 指向回调函数的指针
        hInst,            // dll句柄,这里为本 dll 的实例句柄
        NULL            // 表示与所在桌面的所有线程相关联
        );
    
    // 判断 SetWindowsHookEx 是否执行成功
    if (hhk==NULL){return FALSE;} 
    else{return TRUE;}
}

// 卸载钩子
VOID WINAPI StopHook()
{
    // 这里只恢复了自身 API
    HookOff();

    
    if (hhk!=NULL)
    {
        // UnHook 鼠标钩子
        UnhookWindowsHookEx(hhk);

        // 卸载 dll
        FreeLibrary(hInst);
    }
}

#pragma endregion

因为这里没法使用代码折叠,所以不太直观,我放一张折叠后的图:

image.png

在 .def 文件中添加导出函数:(一般就在 .cpp 文件的下面)

; Test_Dll(mfc).def : 声明 DLL 的模块参数。

LIBRARY

EXPORTS
StartHook
StopHook
    ; 此处可以是显式导出

然后开始写调用 Dll 的代码:(这里要用 MFC 项目,因为全局鼠标钩子需要用到 CWnd 中的 m_hWnd)
由于我认为大部分的全局 HOOK 需要在隐藏自己然后默默执行,这与 MFC 的窗口交互模式风格相冲突,所以我在这里隐藏了 MFC 的窗口,

具体方法可以参考:https://blog.csdn.net/Simon798/article/details/99063945

HINSTANCE g_hInst;        // 全局变量,同 HMODULE

void CTest_MFCDlg::HOOK()
{
    // TODO: 在此添加控件通知处理程序代码

    // 加载 dll(需要根据自己 dll 的实际路径而定,建议使用相对路径)
    g_hInst = ::LoadLibrary(_T("E:\\MyFiles\\Programing\\vs2012\\MyPrograms\\Test_Dll(mfc)\\Debug\\Test_Dll(mfc).dll"));

    // 判断是否加载成功
    if (g_hInst==NULL){AfxMessageBox(_T("加载 dll 失败"));}

    // 声明别名
    typedef BOOL (WINAPI* StartHook)(HWND hWnd);

    // 调用 dll 中的导出函数 StartHook
    StartHook Hook = (StartHook)::GetProcAddress(g_hInst,"StartHook");

    // 判断导出函数是否调用成功
    if (Hook==NULL){AfxMessageBox(_T("StartHook 调用失败"));}

    // 开始 Hook
    Hook(m_hWnd);
}
void CTest_MFCDlg::UNHOOK()
{
    // TODO: 在此添加控件通知处理程序代码
    
    // 检查是否需要 UnHook
    if (g_hInst==NULL){return;}

    // 声明别名
    typedef VOID (WINAPI* StopHook)();

    // 调用 dll 中的导出函数 StopHook
    StopHook UnHook = (StopHook)::GetProcAddress(g_hInst,"StopHook");
    
    // 判断导出函数是否调用成功
    if (UnHook==NULL){AfxMessageBox(_T("StopHook 调用失败"));return;}

    // 开始 UnHook
    UnHook();

    // 卸载 dll
    FreeLibrary(g_hInst);

    // 重置 g_hInst , 方便下一次 UnHook 时判断
    g_hInst=NULL;
}

// 窗体创建事件
int CTest_MFCDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CDialogEx::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO:  在此添加您专用的创建代码
    
    // 程序被打开时,执行 HOOK() 函数。这里就不演示 UNHOOK 了。
    HOOK();
}

void CTest_MFCDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
    CDialogEx::OnWindowPosChanging(lpwndpos);

    // TODO: 在此处添加消息处理程序代码

    // 隐藏窗体
    lpwndpos->flags &= ~SWP_SHOWWINDOW;
    CDialog::OnWindowPosChanging(lpwndpos);
}

效果图:

image.png

目录
相关文章
|
5月前
|
API 数据库 C语言
【C/C++ 数据库 sqlite3】SQLite C语言API返回值深入解析
【C/C++ 数据库 sqlite3】SQLite C语言API返回值深入解析
242 0
|
5天前
|
API
本地hook API MessageBoxA的masm32源代码[07-10更新]
本地hook API MessageBoxA的masm32源代码[07-10更新]
|
2月前
|
Dart API 开发工具
Dart ffi 使用问题之Dart API要在C++中使用,该如何初始化
Dart ffi 使用问题之Dart API要在C++中使用,该如何初始化
|
4月前
|
Java Linux API
微信API:探究Android平台下Hook技术的比较与应用场景分析
微信API:探究Android平台下Hook技术的比较与应用场景分析
|
5月前
|
Linux API C++
【Linux C/C++ 线程同步 】Linux API 读写锁的编程使用
【Linux C/C++ 线程同步 】Linux API 读写锁的编程使用
47 1
|
5月前
|
编译器 API C++
【C++ 动态库设计】动态库中的模板函数:解决如果将模板函数封装成API库
【C++ 动态库设计】动态库中的模板函数:解决如果将模板函数封装成API库
233 0
|
5月前
|
SQL API 开发工具
【C/C++ API设计】C/C++ API与动态库设计:从入门到精通
【C/C++ API设计】C/C++ API与动态库设计:从入门到精通
551 0
|
5月前
|
存储 JavaScript API
C++ 正则表达式库 std::basic_regex 中文手册(API说明来自cppreference.com)
C++ 正则表达式库 std::basic_regex 中文手册(API说明来自cppreference.com)
133 0
|
5月前
|
存储 前端开发 JavaScript
这个 hook api,曾吓退许多前端开发者
这个 hook api,曾吓退许多前端开发者
|
5月前
|
监控 JavaScript 前端开发
这个 hook api,是 useState 的双生兄弟
这个 hook api,是 useState 的双生兄弟