内容来源于:易道云信息技术研究院VIP课
上一个内容:游戏菜单文字资源读取的逆向分析-CSDN博客
之前的内容:接管游戏的自动药水设定功能-CSDN博客
码云地址(master分支):https://gitee.com/dye_your_fingers/sro_-ex.git
码云版本号:55358fb135a0c821d8e80a931f33640494b54b9d
代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-文字资源读取类的C++还原.zip
链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg
提取码:q9n5
--来自百度网盘超级会员V4的分享
HOOK引擎,文件名为:黑兔sdk.zip
链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw
提取码:78h8
--来自百度网盘超级会员V4的分享
以 游戏菜单文字资源读取的逆向分析-CSDN博客 它的代码为基础进行修改
效果图:
GameEx.cpp文件的修改,修改了 ExitGame函数,上方效果图里的代码只能通过重新登录才能修改好按钮的名字,通过ExitGame函数,可以让游戏在按钮初始化之前把字符串给修改了
#include "pch.h"
#include "GameEx.h"
#include "htdHook2.h"
#include "GameProtect.h"
#include "extern_all.h"
extern int client;
extern GameProtect* _protect;
extern unsigned _stdcall GetFunctionAddress(int index);
htd::hook::htdHook2 hooker;
#include <windows.h>
#include<stdio.h>
#include<TlHelp32.h>
/**
声明要拦截的函数地址
*/
auto h = GetModuleHandle(NULL);
DWORD address = (DWORD)h;
DWORD addRExit = address + 0x88C77E;
size_t 被拦截修改的函数的地址 = (size_t)addRExit;
LONG NTAPI 异常回调(struct _EXCEPTION_POINTERS* Excep)
{
printf("异常回调1\n");
/**
判断出异常的地方是否为 我们修改的地方
*/
if ((size_t)Excep->ExceptionRecord->ExceptionAddress == 被拦截修改的函数的地址) {
//const char* szStr = "nei Rong Bei Xiu Gai";
//*(DWORD*)(Excep->ContextRecord->Esp + 0x8) = (DWORD)szStr;
//szStr = "biao Ti Bei Xiu Gai";
//*(DWORD*)(Excep->ContextRecord->Esp + 0xC) = (DWORD)szStr;
AfxMessageBox(L"游戏退出!");
DWORD* _esp = (DWORD*)Excep->ContextRecord->Esp;
DWORD _val = _esp[1];
if (_val == 0x1035D0C) {
AfxMessageBox(L"游戏退出2!");
auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
if (hMuls) ReleaseSemaphore(hMuls, 1, 0);
ExitProcess(0);
}
Excep->ContextRecord->Eip = *(DWORD *) Excep->ContextRecord->Esp;
Excep->ContextRecord->Esp += 8;
return EXCEPTION_CONTINUE_EXECUTION;
}
else {
/**
防止被其它地方修改了函数地址
*/
Excep->ContextRecord->Dr0 = 被拦截修改的函数的地址;
Excep->ContextRecord->Dr7 = 0x405;
return EXCEPTION_CONTINUE_SEARCH;
}
}
VOID 设置线程的dr寄存器(HANDLE 线程句柄) {
printf("设置线程的dr寄存器1\n");
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(线程句柄, &ctx);
ctx.Dr0 = 被拦截修改的函数的地址;
ctx.Dr7 = 0x1;
SetThreadContext(线程句柄, &ctx);
printf("设置线程的dr寄存器2\n");
}
VOID 使用dr寄存器拦截修改函数() {
printf("使用dr寄存器拦截修改函数1\n");
HANDLE 线程快照句柄 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
if (线程快照句柄 == INVALID_HANDLE_VALUE) {
printf("线程快照创建失败");
return;
}
THREADENTRY32* 线程结构体 = new THREADENTRY32;
线程结构体->dwSize = sizeof(THREADENTRY32);
/**
Thread32First获取快照中第一个线程
返回值bool类型
*/
// Thread32First(线程快照句柄, &线程结构体);
HANDLE 线程句柄 = NULL;
printf("使用dr寄存器拦截修改函数2\n");
/**
Thread32Next获取线程快照中下一个线程
*/
while (Thread32Next(线程快照句柄, 线程结构体))
{
if (线程结构体->th32OwnerProcessID == GetCurrentProcessId()) {
printf("使用dr寄存器拦截修改函数3\n");
线程句柄 = OpenThread(THREAD_ALL_ACCESS, FALSE, 线程结构体->th32ThreadID);
printf("使用dr寄存器拦截修改函数4\n");
设置线程的dr寄存器(线程句柄);
printf("使用dr寄存器拦截修改函数5\n");
CloseHandle(线程句柄);
}
}
}
bool AutoHelper(HOOKREFS2) {
bool vip = false;
if (vip)return false;
else return true;
}
bool ExitGame(HOOKREFS2) {
auto read = _pgamebase->SRO_Res->ReadTitle((wchar_t*)0xEBC968);
CString txt;
txt.Format(L"------ %s", read->wcstr());
read->Ptitle = L"自动助手 [VIP] (%s)";
AfxMessageBox(txt);
// if (_protect->CheckDebugByNT())AfxMessageBox(L"检测到了DEBUG程序的存在");
// AfxMessageBox(L"游戏退出2222!");
DWORD* _esp = (DWORD*)_ESP;
DWORD _val = _esp[1];
if (_val == 0x1035D0C) {
// AfxMessageBox(L"游戏退出!");
auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
if (hMuls) ReleaseSemaphore(hMuls, 1, 0);
client--;
ExitProcess(0);
}
return true;
}
GameEx::GameEx()
{
// AfxMessageBox(L"注册hook!");
// auto h = GetModuleHandle(NULL);
// DWORD address = (DWORD)h;
// DWORD* addRExit = (DWORD*)(address + 0x88C77E);
/**addRExit = 0;*/
// CString txt;
// txt.Format(L"addRExit[0]D:%d,addRExit[0]X:%X,addRExit:%X", addRExit[0], addRExit[0], addRExit);
// AfxMessageBox(txt);
// hooker.SetHook((LPVOID)addRExit, 3, ExitGame);
//AddVectoredExceptionHandler(1, 异常回调);
//设置线程的dr寄存器(GetCurrentThread());
}
void GameEx::InitInterface()
{
unsigned addr_cps = GetFunctionAddress(0);
hooker.SetHook((LPVOID)(addr_cps + 0x30 - 2), 0x3, ExitGame);
hooker.SetHook((LPVOID)(addr_cps + 0x51 - 2), 0x3, ExitGame);
unsigned addr_autohelper = GetFunctionAddress(1);
hooker.SetHook((LPVOID)(addr_autohelper), 0x03, AutoHelper, (LPVOID)(addr_autohelper + 0x90));
}
新加extern_all.h文件
#pragma once
#include "GameBase.h"
extern GameBase* _pgamebase;
htdMfcDll.h文件的修改,新加 引入GameBase头文件、game_base变量
// htdMfcDll.h: htdMfcDll DLL 的主标头文件
//
#pragma once
#ifndef __AFXWIN_H__
#error "在包含此文件之前包含 'pch.h' 以生成 PCH"
#endif
#include "resource.h" // 主符号
#include "CUI.h"
#include "GameProtect.h"
#include "GameEx.h"
#include "GameBase.h"
// ChtdMfcDllApp
// 有关此类实现的信息,请参阅 htdMfcDll.cpp
//
class ChtdMfcDllApp : public CWinApp
{
public:
ChtdMfcDllApp();
// 重写
public:
virtual BOOL InitInstance();
CUI* wndMain{};
DECLARE_MESSAGE_MAP()
protected:
GameProtect protect;
GameEx game_ex;
GameBase game_base;
};
新加Res.cpp文件
#include "pch.h"
#include "Res.h"
// 这里为了给 _ReadTitle 默认值,防止编译不通过
Res::PROC_WCHAR1 Res::_ReadTitle{};
psro_string Res::ReadTitle(wchar_t* index)
{
/**
获取按钮文字通过逆向分析,看出是一个thiscall
在调用获取文字函数时给ecx赋值了,这是典型的thiscall方式
下方(this->*_ReadTitle)(index)写法翻译成汇编就会是
mov ecx,this
mov eax, [ecx+xxx];
push index
call eax
大概就会变成上方四行代码,也就是一个thiscall
如果穿插汇编代码,会很lou,这样用c++写就很优雅
*/
return (this->*_ReadTitle)(index);
}
新加Res.h文件
#pragma once
// sro_string通过逆向分析,文字获取方式得到的一个结构体
typedef class sro_string {
public:
int un1;
union {
wchar_t title[0x4];
wchar_t* Ptitle;
};
int un2[0x2];
unsigned lenth;
unsigned size;
public:
wchar_t* wcstr() {
if (size < 0x8) {
return title;
}
else {
return Ptitle;
}
}
}*psro_string;
typedef class Res
{
// 定义一个函数指针
typedef psro_string(Res::* PROC_WCHAR1)(wchar_t*);
public:
// 给函数指针声明一个变量
static PROC_WCHAR1 _ReadTitle;
// 封装 _ReadTitle 使用时比较好用
psro_string ReadTitle(wchar_t* index);
}*PRes;
新加GameBase.h文件
#pragma once
#include "Res.h"
class GameBase
{
void Init();
void InitClassProc(LPVOID proc_addr, unsigned value);
public:
GameBase();
PRes SRO_Res;
};
新加GameBase.cpp文件
#include "pch.h"
#include "GameBase.h"
GameBase* _pgamebase;
void GameBase::Init()
{
SRO_Res = (PRes)0x1036518;
InitClassProc(&Res::_ReadTitle, 0x9A46C0);
}
void GameBase::InitClassProc(LPVOID proc_addr, unsigned value)
{
unsigned* uWrite = (unsigned*)proc_addr;
uWrite[0] = value;
}
GameBase::GameBase()
{
_pgamebase = this;
Init();// 初始化机制,完成游戏与我们dll的对接
}