C/C++ HOOK 全局 API

全局 API Hook 用于在操作系统级别劫持和修改全局API(Application Programming Interface,应用程序编程接口)的调用。通过全局API Hook,可以截获和篡改应用程序对特定API的调用,从而实现对应用程序行为的监控、修改或增强。

全局API Hook的实现通常涉及以下步骤:

  1. 选择目标API:确定要劫持和修改的目标API,例如文件操作、网络通信、注册表访问等常见系统API。

  2. 注册钩子函数:在操作系统中注册一个钩子函数,用于截获目标API的调用。

  3. 钩子过程:当应用程序调用目标API时,操作系统将调用注册的钩子函数。钩子函数可以在API调用之前或之后执行自定义逻辑,例如记录日志、修改参数、阻止调用等。

  4. 钩子链处理:对于多个全局API Hook的情况,需要处理钩子函数之间的执行顺序和传递参数等细节。

全局API Hook的应用场景包括但不限于以下情况:

  1. 行为监控:通过劫持关键API的调用,可以实时监控应用程序的行为,例如检测恶意软件、网络通信分析、系统监控等。

  2. 行为修改:可以修改API的参数或返回值,从而改变应用程序的行为,例如篡改文件操作、网络请求重定向等。

  3. 调试和逆向工程:可以用于调试和逆向工程,通过截获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 的窗口。

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

效果图:

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值