56.第二阶段x64游戏实战-硬断HOOK实现下载预加载lua

发布于:2025-07-12 ⋅ 阅读:(18) ⋅ 点赞:(0)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

本次游戏没法给

内容参考于:微尘网络安全

上一个内容:55.第二阶段x64游戏实战-分析游戏载入lua过程

效果图:

hook下图红框位置(只要能正确的得到rsp+0x40 和 rsp+0x50 的值,hook什么位置都行)

本次hook使用硬件断点(硬断hook),然后通过硬件断点接管异常,然后在异常里面进行处理(下载lua),硬件断点采用dr寄存器,这里先学会使用,不要管为什么,后面会解释

然后开始,首先创建两个按钮,并分别双击它们创建点击时执行的函数

MyDialog.cpp文件的修改

HardBreakHook g_hbHook;
void MyDialog::OnBnClickedButton10()
{
	g_hbHook.m_dr0 = g_GameBase + 0x40C874;// 断点的位置
	g_hbHook.m_dr7 = 0x55;// 0x55表示dr0,dr1,dr2,dr3,这4个寄存器都可以用来设置断点位置
	// 获取调用当前函数的线程id,后面要给它排除掉,能调用到这肯定是MFC创建的线程,不是游戏原有线程
	g_hbHook.m_removeThread[0] = GetCurrentThreadId();
	g_hbHook.setExceptionHandlingFunction(异常处理);// 接管异常
	g_hbHook.hook();//开始hook
}

然后开始针对上方的代码解释,首先是setExceptionHandlingFunction函数,它就是调用Windows提供的AddVectoredExceptionHandler函数给进程添加一个异常处理(用来配合硬件断点)

void HardBreakHook::setExceptionHandlingFunction(PVECTORED_EXCEPTION_HANDLER Func)
{
	// AddVectoredExceptionHandler(1, Func) 表示把Func放到异常处理函数中的第一个
	m_exceptionHandle = AddVectoredExceptionHandler(1, Func); 
}

然后是 异常处理 函数,大体逻辑是先判断当前异常是不是硬件断点异常,然后他通过入参(入参是Windows传递的)获取异常的代码地址,然后判断这个地址是不是我们想hook的位置,如果是就执行下载lua逻辑,然后执行游戏中原有代码,然后通过设置rip寄存器(处理器执行哪一行代码取决于rip寄存器的值)跳转回游戏中的代码执行(这里要注意比如hook的位置是0x001,0x001位置的代码是4字节,跳回的位置应该是rip+4的位置,也就是hook位置的下一行代码的地址,因为被hook了,再跳转回去(跳转回hook地址)还是会触发异常的,这样成死循环了)

#include "stdint.h"
#include "string.h"

LONG NTAPI 异常处理(struct _EXCEPTION_POINTERS* exceptionInfo)
{
	if (exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)//判断是不是硬断异常
	{
		// 判断是不是要hook的位置
		if ((uintptr_t)exceptionInfo->ExceptionRecord->ExceptionAddress == g_GameBase + 0x40C874)
		{
			LogA("----------rsp+0x40:%s----------", ReadDword64(exceptionInfo->ContextRecord->Rsp + 0x40));

			std::string  luaFile = (char*)ReadDword64(exceptionInfo->ContextRecord->Rsp + 0x40);//获取lua的目录地址



			int weizhi = luaFile.rfind("/");

			if (weizhi == -1)
				weizhi = 0;

			string lua文件名 = luaFile.substr(weizhi, luaFile.size() - weizhi);// 获取lua文件名




			std::string PATH;
			PATH = "d:\\lua\\";

			PATH = PATH + lua文件名;// 把lua保存到 d:\lua 目录下
			char* lua文件内容 = (char*)ReadDword64(exceptionInfo->ContextRecord->Rsp + 0x50);//获取lua的文件内容

			HANDLE hResFile = CreateFileA(PATH.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//创建文件  

			if (INVALID_HANDLE_VALUE == hResFile)
			{
				LogA("创建失败");
			}

			DWORD dwWritten = 0;//写入文件的大小 

			SetFilePointer(hResFile, 0, 0, FILE_BEGIN);
			LogA("jinru1");
			WriteFile(hResFile, lua文件内容, strlen(lua文件内容), &dwWritten, NULL);//写入文件  
			FlushFileBuffers(hResFile);
			CloseHandle(hResFile);//关闭文件句柄  
			//可以任意处理了


			/**
				还原处理,hook的位置出现异常了会导致原本的代码不会执行了
				在这结尾处要执行一下原本的代码(mov r8, qword ptr ds:[rcx]),然后跳回游戏中的代码
			*/ 
		
			exceptionInfo->ContextRecord->R8 = ReadDword64(exceptionInfo->ContextRecord->Rcx);
			exceptionInfo->ContextRecord->Rip += 3;

			return EXCEPTION_CONTINUE_EXECUTION;
		}
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

然后是g_hbHook.hook(),大体逻辑是先通过 CreateToolhelp32Snapshot 获取当前进程所有的线程,从里面找到当前进程创建的线程,找到之后,打开线程(获取线程句柄),然后暂停线程,获取线程上下文(这里面就包含了dr寄存器),然后修改dr寄存器的值,然后重新设置线程上下文,然后恢复线程,然后关闭线程句柄

#include <TlHelp32.h>
void HardBreakHook::hook()
{
	// 获取当前进程中所有线程,在调用 CreateToolhelp32Snapshot 函数后出现的线程没办法获取到,需要重新调用
	HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	if (h == INVALID_HANDLE_VALUE)
	{
		//如果函数成功,它将返回指定快照的打开句柄。
		//如果函数失败,它将返回 INVALID_HANDLE_VALUE。 要获得更多的错误信息,请调用 GetLastError。 可能的错误代码包括 ERROR_BAD_LENGTH。
		return;
	}

	THREADENTRY32 thread_entry32;
	thread_entry32.dwSize = sizeof(thread_entry32);

	/**
		最开始获取了当前进程所有线程,这里通过调用 Thread32First 函数获取第一个线程
	*/ 
	if (!Thread32First(h, &thread_entry32))
	{
		//如果线程列表的第一个条目已复制到缓冲区,则返回 TRUE ,否则返回 FALSE 。 如果不存在线程或快照不包含线程信息,则 GetLastError 函数返回ERROR_NO_MORE_FILES错误值。
		CloseHandle(h);
		return;
	}

	do
	{
		// 判断当前线程是不是我们(游戏)进程创建的,如果是我们自己创建的就不进入if里执行
		if (!(thread_entry32.th32OwnerProcessID == GetCurrentProcessId()))
		{
			//判断线程的进程ID 是否等于我们自进程ID
			//当前线程的拥有进程ID 非 自身 跳过
			continue;
		}

		// 判断当前线程是不是我们要hook的线程
		if (!isHookThread(thread_entry32.th32ThreadID))
		{
			//不是我们想要HOOK的线程ID
			continue;
		}
		// 到这就找到了我们想要的线程,这里打开线程
		HANDLE threadHandle = OpenThread(THREAD_ALL_ACCESS, FALSE, thread_entry32.th32ThreadID);//打开线程 获取线程句柄
		if (threadHandle)
		{
			SuspendThread(threadHandle);//暂停线程

			//开始设置dr寄存器进行HOOK
			CONTEXT context;//线程上下文
			context.ContextFlags = CONTEXT_ALL;//设置获取全部权限
			//获取线程的上下文,线程里有很多数据,所以要先获取一下线程中原有数据,然后再给dr寄存器赋值
			GetThreadContext(threadHandle, &context);
			context.Dr0 = m_dr0;
			context.Dr1 = m_dr1;
			context.Dr2 = m_dr2;
			context.Dr3 = m_dr3;
			context.Dr7 = m_dr7;
			SetThreadContext(threadHandle, &context);//设置线程的上下文

			ResumeThread(threadHandle);//恢复线程
			CloseHandle(threadHandle);//关闭句柄
		}
		// 到这会继续循环找,循环完当游戏的代码执行到dr0或dr1或dr2或dr3,设置的地址时就会触发断点,然后就来到 异常处理 这个函数
	} while (Thread32Next(h, &thread_entry32));

	CloseHandle(h);
}

bool HardBreakHook::isHookThread(DWORD threadId)
{
	for (int i = 0; i < 0x20; i++)
	{
		if (m_removeThread[i] == threadId)
		{
			return false;
		}
	}
	return true;
}

卸载hook

void MyDialog::OnBnClickedButton11()
{
	g_hbHook.unHook();// 关闭硬件断点
}

g_hbHook.unHook()函数

void HardBreakHook::unHook()
{
	m_dr0 = 0; // 清空地址
	m_dr1 = 0; // 清空地址
	m_dr2 = 0; // 清空地址
	m_dr3 = 0; // 清空地址
	m_dr7 = 0; // 关闭dr寄存器
	hook();// 然后重新设置dr寄存器,进行关闭硬件断点
	// 删除异常函数
	RemoveVectoredExceptionHandler(m_exceptionHandle);
}


img


网站公告

今日签到

点亮在社区的每一天
去签到