C/C++HOOK全局API

全局 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

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

在 .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);
}

效果图: