zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++ HOOK 指定进程的指定 API(MessageBoxA 为例)(最简单)

C++进程API 简单 指定 为例 hook
2023-09-11 14:14:01 时间

这篇文章只是基于我之前的全局 HOOK 的修改,要看全局 HOOK点这里

虽然全局 HOOK 很给力,但是越到后来越发现这个东西除了可以
在这里插入图片描述
以外,实际上用途并没有想象中的广泛。相反,对于制定进程的指定 API 的 HOOK 却非常实用,所以就把以前的代码精简一下,去掉它浮夸的外衣,以最少的代码,实现最基本的 HOOK 功能 。
在这里插入图片描述

dll 代码:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <tlhelp32.h>
#include <process.h>

#pragma region 全局变量

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

#pragma endregion

#pragma region 功能函数

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

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

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

}

// 关闭钩子(修改 API 头 5 个字节)
void HookOff()
{ 

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

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

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

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

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

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

		// 获取函数地址
		HMODULE hmod=::LoadLibrary(L"User32.dll");
		oldMsgBoxA=(MsgBoxA)::GetProcAddress(hmod,"MessageBoxA");	// 原 MessageBoxA 地址
		pfMsgBoxA=(FARPROC)oldMsgBoxA;								// 指向原 MessageBoxA 地址的指针

  
		// 指针为空则结束运行
		if (pfMsgBoxA==NULL){MessageBox(NULL,L"cannot get MessageBoxA()",L"error",0);return;}


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

		// 将原 API 第一个字节改为 jmp
		NewCodeA[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 
		} 
 
		// 开始 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;
}

#pragma endregion

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	// 获取调用 dll 的进程 ID
	DWORD dwPid = ::GetCurrentProcessId();

	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		// 获取调用 dll 的进程句柄
		hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);

		// 开始注入
		Inject();
		
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

好吧,这已经是最短的代码量了。下面演示下效果:
在这里插入图片描述