主要针对X86游戏的HOOK
ide环境准备:VS2022、MFC.dll文件创建X86、多字节版本
1.Hook初始化1.1 Hook类的封装
[C++] 纯文本查看 复制代码 #pragma once
class Hook5
{
public:
//-这个是构造函数, 用于初始化一个Hook5对象。它接受4个参数 :
//-hook地址 : HOOK的位置, 也就是要替换的地址。
//- hookTargetAdress : 跳转目标位置, 也就是HOOK跳转过去执行的地址。
//- oldAdress : 要替换的原始5字节代码, 用于后续恢复HOOK。
//- offset : m_hook返回地址相对于hook地址的偏移, 用于计算HOOK返回后执行的地址。
Hook5(DWORD hook地址,DWORD hookTargetAdress,BYTE oldAdress[5], DWORD offset=5);
//析构函数
~Hook5();
public:
BOOL Hook();
BOOL UnHook();
public:
//由于使用了 #pragma pack(1) 指令,结构体中的成员变量 jmp 和 offset 将会紧密地排列在一起,
//不会产生字节对齐的空隙。这样构造的跳转指令在执行时可以直接跳转到指定的地址。
#pragma pack(1)
typedef struct JmpCode5
{
BYTE jmp;
DWORD offset;
JmpCode5() {
jmp = 0xE9;
offset = 0;
}
}JmpCode5;
#pragma pack()
public:
static DWORD m_hook地址;
static DWORD m_hook返回地址;
JmpCode5 m_jmpCode5;//跳转指令
BYTE m_oldCode[5];//原始代码
BOOL m_init;//是否初始化
BOOL m_hooked; //是否HOOK
};
此类的作用是用于实现函数钩子(function hooking)的功能。函数钩子是一种常用的技术,用于拦截并修改程序中指定函数的行为,常用于软件调试、病毒分析、游戏修改等领域。
Hook5类实现了一个对5字节函数进行HOOK的功能,通过构造函数传入要替换的地址、跳转目标地址、原始5字节代码和返回地址相对于hook地址的偏移等参数,通过调用Hook函数实现对指定函数的拦截,并将原始函数的执行流程跳转到指定的目标函数地址,从而实现对函数行为的修改。同时,Hook5类也提供了UnHook函数用于恢复原始函数。
Hook5 类的方法包括:
构造函数 Hook5:用于初始化 Hook5 类对象,传入 hook 地址、跳转目标地址、原始 5 字节代码和偏移量;
析构函数 ~Hook5:用于释放 Hook5 对象;
Hook:实现函数 hook 的方法,将跳转指令写入 hook 地址处,并保存原有代码内存属性.同时修改内存属性为 PAGE_EXECUTE_READWRITE,最后恢复内存属性;
UnHook:取消函数 hook 的方法,将原有代码复制回 hook 地址处,同时恢复内存属性。
1.2 Hook类的实现
[C++] 纯文本查看 复制代码 #include "pch.h"
#include "Hook5.h"
DWORD Hook5::m_hook地址 = 0;
DWORD Hook5::m_hook返回地址 = 0;
//构造函数
Hook5::Hook5(DWORD hook地址, DWORD hookTargetAdress, BYTE oldAdress[5], DWORD offset)
{
Call_输出调试信息("hook地址:%x hookTargetAdress:%x", hook地址, hookTargetAdress);
m_hook地址 = hook地址;
m_hook返回地址 = hook地址 + offset;
m_jmpCode5.offset = hookTargetAdress - hook地址 - 0x5;
memcpy_s(m_oldCode, 5, oldAdress, 5);
m_init = TRUE;
m_hooked = FALSE;
}
//
Hook5::~Hook5()
{
if (m_hooked)
{
UnHook();
}
}
BOOL Hook5::Hook()
{
if (!m_init)
{
return FALSE;
}
if (m_hooked)return TRUE;
__try
{
Call_输出调试信息("开始hook test");
//1.修改内存页面属性
//参数1:要修改的内存地址
//参数2:要修改的内存大小
//参数3:新的内存属性
//参数4:保存原来的内存属性
DWORD dwOldProtect;
VirtualProtect((LPVOID)Hook5::m_hook地址, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//2.将跳转指令写入到m_hook地址处
memcpy_s((void*)Hook5::m_hook地址, 5, &m_jmpCode5, 5);
//3.恢复内存页面属性
VirtualProtect((LPVOID)Hook5::m_hook地址, 5, dwOldProtect, &dwOldProtect);
m_hooked = TRUE;
return TRUE;
}
__except(1)
{
Call_输出调试信息("拦截异常");
}
return 0;
}
BOOL Hook5::UnHook()
{
if (!m_init)
{
return FALSE;
}
__try
{
Call_输出调试信息("开始UnHook test");
//1.修改内存页面属性
//参数1:要修改的内存地址
//参数2:要修改的内存大小
//参数3:新的内存属性
//参数4:保存原来的内存属性
DWORD dwOldProtect = 0;
VirtualProtect((LPVOID)Hook5::m_hook返回地址, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//2.将跳转指令写入到m_hook地址处
memcpy_s((void*)Hook5::m_hook地址, 5, &m_oldCode, 5);
//3.恢复内存页面属性
VirtualProtect((LPVOID)Hook5::m_hook地址, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
m_hooked = FALSE;
return TRUE;
}
__except (1)
{
Call_输出调试信息("拦截异常");
}
return 0;
}
以上就是基本的Hook类封装,所有X86 5字节的Hook都可以调用这个封装,接下来我们以Hook某龙的lua为例,简单调用这层Hook类封装。
2.Hook某龙
2.1 MFC资源dialog创建
2.2窗口类创建(略过,详情请查阅 A星算法04剑网3)
2.3.窗口模态显示(略过,详情请查阅 A星算法04剑网3)
2.4 HOOK和Unhook点击事件函数创建
2.5 Hook地址的初始化
[C++] 纯文本查看 复制代码 #define HookAdressOffset 0x29C243(此为hook目标地址的偏移)
BYTE oldAdress[5] = { 0x51, 0xFF, 0x30, 0xFF, 0xD6 };//原始代码
Hook5 hook5((DWORD)GetModuleHandle(NULL) + HookAdressOffset, (DWORD)MyHookFunction, oldAdress, 0x5);
2.6 hook钩子函数业务的编写
[C++] 纯文本查看 复制代码 __declspec(naked) void MyHookFunction()
{
__asm
{
//保存寄存器
pushad
mov lua_click,ebx
mov lua_env, ecx
mov lua_dostring, esi
mov lua_state, eax
//恢复标志寄存器
popad
}
//调用输出调试信息函数
Call_输出调试信息("<%s--------%s>\n",(char*)lua_click, (char*)lua_env);
__asm
{
//调用原函数,Call_输出调试信息可能会改变ecx,eax,esi的值,所以要重新赋值
//0111C243 | 51 | push ecx |
//0111C244 | FF30 | push dword ptr ds : [eax] |
//0111C246 | FFD6 | call esi | call lua_dostring
mov ecx, lua_env
push ecx
mov eax, lua_state
push dword ptr ds : [eax]
mov esi, lua_dostring
call esi
//跳转到原来的函数
jmp Hook5::m_hook返回地址
}
}
2.7 Hook点击事件调用Hook5类的Hook业务函数(记得引入Hook5.h)
[C++] 纯文本查看 复制代码 void HmainDialogASM02::OnBnClickedButton1()
{
if (!hook5.m_hooked) { // 只有未 Hook 时才执行
if (hook5.Hook())
{
Call_输出调试信息("successful hook");
}
}
}
//unhook按钮
void HmainDialogASM02::OnBnClickedButton2()
{
if (hook5.m_hooked){ // 只有已 Hook 时才执行
if (hook5.UnHook())
{
Call_输出调试信息("successful Unhook");
}
}
}
3.生成DLL注入游戏
我们发现成功拦截了游戏的LUA接口,获取到所有的LUA详细信息并打印输出。
实战环节略有缺失,本文主要是作为笔记的补充以及思路的提供,读者如果有不懂之处,可私信。
言归正传,本案例的核心功能是实现对Hook功能的封装,即Hook5类。开发者可根据业务需要,调用此HOOK封装,本案例主要是通过MFC DLL的形式注入,开发人员可以自行选择注入方法。当然也可以不用我这个方法,采用外部读取目标进程的形式也不是不可以。
|