新剑侠情缘学习笔记

一、结论

·

1.1技能道具

  • A:0x008649B8
  • S:0x00864B38
  • D:0x00864CB8
  • F:0x00864E38
  • G:0x00864FB8
  • Z:0x0085EBEC
  • X:0x0085EDC0
  • C:0x0085EF94
  • map.x:0x004ED730
  • map.y:0x004ED734
    ·

1.2人物数据

  • 当前生命:[[0x00845618]*0x42B0+0x004ED780]
  • 最大生命:[[0x00845618]*0x42B0+0x004ED784]
  • 当前内力:[[0x00845618]*0x42B0+0x004ED788]
  • 最大内力:[[0x00845618]*0x42B0+0x004ED78C]
  • 当前体力:[[0x00845618]*0x42B0+0x004ED790]
  • 最大体力:[[0x00845618]*0x42B0+0x004ED794]
  • 攻击:[[0x00845618]*0x42B0+0x004ED798]
  • 防御:[[0x00845618]*0x42B0+0x004ED79C]
  • 身法:[[0x00845618]*0x42B0+0x004ED7A0]
  • 等级:[[0x00845618]*0x42B0+0x004ED7A4]
  • 经验:[[0x00845618]*0x42B0+0x004ED7B0]
  • 升级:[[0x00845618]*0x42B0+0x004ED7B8]
  • 人物X:[[0x00845618]*0x42B0+0x004ED7D0]
  • 人物Y:[[0x00845618]*0x42B0+0x004ED7D4]
    ·

1.3技能call

  • eax=[0x845618]
  • ecx=[[0x845638]*0x180+0x845618+0x1D438+0x168]
  • edi=[0x845638]*0x180+0x845618+0x1D438
  • [0x845638]的取值是0x14到0x18,分别表示ASDFG技能
    ·

二、分析

·

2.1技能道具

  • 改变ASDFGZXC这部分的值
  • CE一下就能搜到绿址,且重启游戏仍有效
  • (虽然人物数据也是绿址,但是重启后会变,所以再开一小节分析)·

·

2.2人物数据

  • 3886=[edx],edx=0x005387E4
  • edx=0x005387E4=edx+004ED784,edx=0x0004B060
  • edx=0x0004B060=edx<<4,edx=0x00004B06
  • edx=0x4B06=edx+esi*2,edx=0x12,esi=0x257A
  • esi=0x257A=edx+esi*4,edx=0x12,esi=0x95A
  • esi=0x95A=edx+esi*4,edx=0x12,esi=0x252
  • esi=0x252=edx+esi,edx=0x12,esi=0x240
  • esi=0x240=esi<<5,esi=0x12
  • esi=0x12=edx,edx=0x12
  • edx=[ecx],ecx=0x00845618,查不到这个地址,现在关键是找到谁赋给ecx
  • 上一行指令0042DB97,je这里,加装备不跳进去执行下面指令,减装备跳进去
  • 用OD找到0042D81C就是函数入口地址!
  • ecx=0x00845618=esi,esi=0x00845618
  • esi=0x00845618=ecx,ecx=0x00845618
  • 找到上一个调用的函数地址:0042D695
  • ecx=0x00845618=esi,esi=0x00845618
  • 本函数第一条指令是0042D667,跳到结束并返回函数入口是00404087,此时ESI的值是1(不是00845618)
  • 00404087的函数跳进去的第一条指令是0042D541,我们把目标定在0042D541与0042D667之间,发现一开始就传进去的就是ecx,ecx再赋给- esi的,我们在00404087的上一条00404082看到,00845618竟然是绿色地址!这下就稳了!

·

  • 最终最大血条地址:[[0x00845618]*17072+0x004ED784],其中17072是十进制,转换成十六进制是42B0
  • 大血条地址出来了,查看数据结构,观察附近的值就可以得到其他值了(类似之前学习CS时知道了Z就知道XY一样),代码中只需要修改后面的0x004ED784偏移即可

·

2.3技能call

在这里插入图片描述

  • 利用技能等级改变定位到等级地址后使用技能,找到是谁访问这个地址,利用访函数返回处的指令即可找到调到他的call(就是ret的上一条指令)

·

  • 技能call(0042D4CB-0042D4D5)以鼠标或人物朝向释放技能,当然有些技能是原地释放如回血
  • push 00
  • push eax
  • push ecx
  • push edi
  • mov ecx,新剑侠情缘.exe+387B8
  • call 新剑侠情缘.exe+15F70

·

  • 然后测试了D与F技能两个call如下:
  • D技能call
  • push 0x00
  • push 0x02
  • push 0x0A
  • push 0x864B50
  • mov ecx,0x4E87B8
  • call 0x415F70

·

  • F技能call
  • push 0x00
  • push 0x02
  • push 0x0A
  • push 0x864CD0
  • mov ecx,004E87B8
  • call 00415F70

·

  • 发现上面的第二三四个Push的参数都要找他们的基地址,即eax,ecx,edi
  • 注意到0042D4CB处push 00的前两句指令

·

  • mov eax,[esi]
  • mov ecx,[edi+00000168]
  • 那也就是说,eax与ecx由esi与edi决定

·

  • 追溯到本函数头0042D480,从0042D481-0042D48D,这里用传入的ecx决定了esi,再通过eax作为临时变量,用esi决定了edi,之后都没再对edi与esi作出更改,这是值得高兴的第一个地方

·

  • 其中mov esi,ecx,此时ecx=0x00845618,这是值得高兴的第二个地方,不用再往回找了,因为他就是一个基地址!

·

  • mov esi,ecx,ecx=0x00845618
  • mov eax,[esi+20]
  • lea eax,[eax+eax*2]
  • shl eax,07
  • lea edi,[eax+esi+0001D438]

·

  • 整合即可得到结论,以D技能测试为例:
  • eax=[0x845618]
  • ecx=[[0x845638]*384+0x845618+0x1D438+0x168],十进制384是十六进制180
  • edi=[0x845638]*384+0x845618+0x1D438,十进制384是十六进制180
  • 我们可以通过上式计算出0x02,0x0A,0x864B50这三个参数值

·

  • 最后的问题就是如何区分当前使用的技能?
  • 因为传进来的就只有ecx这个基地址呀!
  • 我们可以发现不同的技能释放时唯一的参数不同就是edi,而edi表达式中只有[0x845618+0x20]个这是变的,我们尝试使用不同技能记下他的值

·

  • 经测试可以发现A-G就是0x14到0x18,凭直觉感觉这个值可以直接用了。

  • 当然保险起见,还是查看一下是什么访问了这个内存地址吧

  • 我们发现原来设置他的位置就在上面0x0042D469处!
    在这里插入图片描述
    ·

  • mov eax,[esp+18],esp=0x0019FDFC

  • mov [esi+20],eax,esi=0x00845618

·

  • 现在问题就转变为求esp+18
  • 我们知道esp指向的永远是栈顶,这里的+18要往回看第6个push的值(因为每个push占4B)
  • 一直数可以得到0042D3E0处有个push eax,这里的值就是后面的[esp+18],现在转变为求eax,然后上一行lea eax,[esp+0C],理论上应该是往回看第3个push的值,但是此函数开始处(0042D3D0处)有个sub esp,10,这时就要看调用这个函数的call的最后一个传参了!

·

  • 在右下角的堆栈窗口中点第一个跳到堆栈区一眼可以看到push的数14-18,我们前面的判断是没有错的,然后ecx的初始赋值也是绿色地址!

·

  • 最后我们整理一下会发现:
  • push 00
  • push eax
  • push ecx
  • push edi
  • 这四个push中,第一和第三个push是可以直接设值的,第二和第四个push也可能通过一次寻址得到

·

  • 至此搜索结束,打开VS写游戏辅助吧(特别注意一定要是Release版本)!

·

三、代码:

  • 按钮一:不传参且编译器不优化的测试函数
  • 按钮二:传参且优化的测试函数
  • 按钮三:开定时器五个技能轮流放
  • 按钮四:所有人物属性/技能级别/道具数增加1
//按钮一:不传参且编译器不优化的测试函数
DWORD FindGageProcessIdByWndTitle(CString strTitle) {
	HWND hWnd = ::FindWindow(NULL, strTitle.GetBuffer());
	DWORD dwPid = 0;
	GetWindowThreadProcessId(hWnd, &dwPid);
	return dwPid;
}
__declspec(naked) void D_call() {
	__asm {
		pushad
		push 0x00000000
		mov eax, ds:[0x00845618]
		push eax
		mov ecx, ds:[0x00864B50+0x168]
		push ecx
		push 0x00864B50
		mov ecx, 0x004E87B8
		mov edx, 0x00415F70
		call edx
		popad
		ret
	}
	//改变0x00864B50可以改变释放的技能
}
void CxjxqygameDlg::OnBnClickedButton1(){
	DWORD dwPid = FindGageProcessIdByWndTitle(_T("Sword Window"));
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (!hProcess) {
		MessageBox(_T("打开程序失败"), NULL, 0);
		return;
	}
	LPVOID ThreadFunAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (!ThreadFunAdd) {
		MessageBox(_T("分配内存失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	DWORD tmpWrite = 0;
	DWORD tmpSize = 4096;
	BOOL wpm = WriteProcessMemory(hProcess, ThreadFunAdd, D_call, tmpSize, &tmpWrite);
	if (!wpm) {
		MessageBox(_T("写入代码失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadFunAdd, NULL, NULL, NULL);
	if (!hThread) {
		MessageBox(_T("远程调用失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	DWORD dwWait = WaitForSingleObject(hThread, INFINITE);
	VirtualFreeEx(hProcess, hThread, 0, MEM_RELEASE);
	CloseHandle(hProcess);
}
//按钮二:传参且优化的测试函数
typedef struct _ASDFG_call_parame {
	DWORD ASDFG;
	DWORD ASDFG_add168;
}ASDFG_call_parame, *PASDFG_call_parame;
DWORD __stdcall _ASDFG_call(LPVOID lpThreadParame) {
	PASDFG_call_parame pParame = (PASDFG_call_parame)lpThreadParame;
	DWORD ASDFG = pParame->ASDFG;
	DWORD ASDFG_add168 = pParame->ASDFG_add168;
	__asm {
		pushad
		push 0x00000000
		mov eax, ds: [0x00845618]
		push eax
		mov ecx, ds : [ASDFG_add168]
		push ecx
		push ASDFG
		mov ecx, 0x004E87B8
		mov edx, 0x00415F70
		call edx
		popad
	}
	return 0;
}
void CxjxqygameDlg::OnBnClickedButton2(){
	ASDFG_call_parame parame;
	parame.ASDFG=0x00864B50;
	parame.ASDFG_add168 = 0x00864B50 + 0x168;
	DWORD dwPid = FindGageProcessIdByWndTitle(_T("Sword Window"));
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (!hProcess) {
		MessageBox(_T("打开程序失败"), NULL, 0);
		return;
	}
	LPVOID ThreadFunAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);	
	if (!ThreadFunAdd) {
		MessageBox(_T("分配内存失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	DWORD tmpWrite = 0;
	BOOL wpm1 = WriteProcessMemory(hProcess, ThreadFunAdd, _ASDFG_call, 4096, &tmpWrite);
	if (!wpm1) {
		MessageBox(_T("写入代码失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	LPVOID ParamAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);	
	if (!ParamAdd) {
		MessageBox(_T("分配内存失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	BOOL wpm2 = WriteProcessMemory(hProcess, ParamAdd, &parame, sizeof(parame), &tmpWrite);	
	if (!wpm2) {
		MessageBox(_T("写入数据失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadFunAdd, ParamAdd, NULL, NULL);
	if (!hThread) {
		MessageBox(_T("远程调用失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	DWORD dwWait = WaitForSingleObject(hThread, INFINITE);												
	VirtualFreeEx(hProcess, hThread, 0, MEM_RELEASE);
	CloseHandle(hProcess);
}
//按钮三:开定时器五个技能轮流放
int OnTimerCnt = 0;
void CxjxqygameDlg::OnTimer(UINT_PTR nIDEvent) {
	switch (nIDEvent) {
		case 1: {
			ASDFG_call_parame parame;
			OnTimerCnt = (OnTimerCnt + 1) % 5;
			if (OnTimerCnt == 0) {
				parame.ASDFG = 0x00864850;
				parame.ASDFG_add168 = 0x00864850 + 0x168;
			}else if (OnTimerCnt == 1) {
				parame.ASDFG = 0x008649D0;
				parame.ASDFG_add168 = 0x008649D0 + 0x168;
			}else if (OnTimerCnt == 2) {
				parame.ASDFG = 0x00864B50;
				parame.ASDFG_add168 = 0x00864B50 + 0x168;
			}else if (OnTimerCnt == 3) {
				parame.ASDFG = 0x00864CD0;
				parame.ASDFG_add168 = 0x00864CD0 + 0x168;
			}else if (OnTimerCnt == 4) {
				parame.ASDFG = 0x00864E50;
				parame.ASDFG_add168 = 0x00864E50 + 0x168;
			}
			DWORD dwPid = FindGageProcessIdByWndTitle(_T("Sword Window"));
			HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
			if (!hProcess) {
				MessageBox(_T("打开程序失败"), NULL, 0);
				return;
			}
			LPVOID ThreadFunAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
			if (!ThreadFunAdd) {
				MessageBox(_T("分配内存失败"), NULL, 0);
				CloseHandle(hProcess);
				return;
			}
			DWORD tmpWrite = 0;
			BOOL wpm1 = WriteProcessMemory(hProcess, ThreadFunAdd, _ASDFG_call, 4096, &tmpWrite);
			if (!wpm1) {
				MessageBox(_T("写入代码失败"), NULL, 0);
				CloseHandle(hProcess);
				return;
			}
			LPVOID ParamAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
			if (!ParamAdd) {
				MessageBox(_T("分配内存失败"), NULL, 0);
				CloseHandle(hProcess);
				return;
			}
			BOOL wpm2 = WriteProcessMemory(hProcess, ParamAdd, &parame, sizeof(parame), &tmpWrite);
			if (!wpm2) {
				MessageBox(_T("写入数据失败"), NULL, 0);
				CloseHandle(hProcess);
				return;
			}
			HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadFunAdd, ParamAdd, NULL, NULL);
			if (!hThread) {
				MessageBox(_T("远程调用失败"), NULL, 0);
				CloseHandle(hProcess);
				return;
			}
			DWORD dwWait = WaitForSingleObject(hThread, INFINITE);//等到线程执行完才返回,固定值则最多等待该值(ms),0是立即返回
			VirtualFreeEx(hProcess, hThread, 0, MEM_RELEASE);
			CloseHandle(hProcess);
			break;
		}
		default: {
			break;
		}
	}
	CDialogEx::OnTimer(nIDEvent);//nIDEvent号计时器重新计时
}
int TimerOnOrOff = 0;
void CxjxqygameDlg::OnBnClickedButton3(){
	if (!TimerOnOrOff)SetTimer(1, 1000, NULL);//1号计时器每1秒调用一次OnTimer
	else KillTimer(1);
	TimerOnOrOff = (TimerOnOrOff + 1) % 2;
}
//按钮四:所有人物属性/技能级别/道具数增加1
void allAddOne(HANDLE hProcess,DWORD address) {
	DWORD tmpRead = 0;
	DWORD tmpWrite = 0;
	DWORD getData = 0;
	BOOL rpm = ReadProcessMemory(hProcess, (LPCVOID)address, &getData, 4, &tmpRead);
	getData += 1;
	BOOL wpm = WriteProcessMemory(hProcess, (LPVOID)address, &getData, 4, &tmpWrite);
}
void CxjxqygameDlg::OnBnClickedButton4(){
	DWORD dwPid = FindGageProcessIdByWndTitle(_T("Sword Window"));
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (!hProcess) {
		MessageBox(_T("打开程序失败"), NULL, 0);
		return;
	}
	allAddOne(hProcess, 0x008649B8);//A
	allAddOne(hProcess, 0x00864B38);//S
	allAddOne(hProcess, 0x00864CB8);//D
	allAddOne(hProcess, 0x00864E38);//F
	allAddOne(hProcess, 0x00864FB8);//G
	allAddOne(hProcess, 0x0085EBEC);//Z
	allAddOne(hProcess, 0x0085EDC0);//X
	allAddOne(hProcess, 0x0085EF94);//C
	DWORD tmpRead = 0;
	DWORD getData = 0;
	BOOL rpm = ReadProcessMemory(hProcess, (LPCVOID)0x00845618, &getData, 4, &tmpRead);
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED780);//当前生命
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED784);//最大生命
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED788);//当前内力
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED78C);//最大内力
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED790);//当前体力
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED794);//最大体力
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED798);//攻击
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED79C);//防御
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED7A0);//身法
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED7A4);//等级
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED7B0);//经验
	allAddOne(hProcess, getData * 0x42B0 + 0x004ED7B8);//升级
	CloseHandle(hProcess);
}


写在最后尝试的功能:

  1. DLL注入金钱+1
  2. 寻找药品call
  3. 寻找道具/武功栏地址

一、DLL注入金钱+1

事实证明:注入DLL后加金钱就两行
int* money = (int*)0x00845628;
*money = *money + 1;

创建MFC DLL文件,新建资源为默认dialog,绑定类AssistMainDlg
在dll文件入口创建线程,线程再创建AssistMainDlg类对象,模态显示
之后就可以加按钮,操作与之前的差不多,但读写内存变得十分简便(因为不是第三方了)
!记得关闭辅助窗口后再卸载dll模块!

// wmgj_dll.h: wmgj_dll DLL 的主标头文件
#pragma once
#ifndef __AFXWIN_H__
	#error "在包含此文件之前包含 'pch.h' 以生成 PCH"
#endif
#include "resource.h"		// 主符号
// CwmgjdllApp
class CwmgjdllApp : public CWinApp{
public:
	CwmgjdllApp();
// 重写
public:
	virtual BOOL InitInstance();
	virtual int ExitInstance();
	DECLARE_MESSAGE_MAP()
};

// wmgj_dll.cpp: 定义 DLL 的初始化例程。
#include "pch.h"
#include "framework.h"
#include "wmgj_dll.h"
#include "AssistMainDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
BEGIN_MESSAGE_MAP(CwmgjdllApp, CWinApp)
END_MESSAGE_MAP()
CwmgjdllApp::CwmgjdllApp(){
}
CwmgjdllApp theApp;
DWORD WINAPI ShowMainDlg(LPVOID pParam) {
	::MessageBox(NULL,_T("辅助模块加载成功"),L"",0);
	AssistMainDlg dlg;
	dlg.DoModal();
	return 0;
}
BOOL CwmgjdllApp::InitInstance(){
	CWinApp::InitInstance();
	::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ShowMainDlg, NULL, NULL, NULL);
	return TRUE;
}
int CwmgjdllApp::ExitInstance(){
	::MessageBox(NULL, _T("辅助模块卸载成功"), L"", 0);
	return CWinApp::ExitInstance();
}

#pragma once
// AssistMainDlg 对话框
class AssistMainDlg : public CDialog{
	DECLARE_DYNAMIC(AssistMainDlg)
public:
	AssistMainDlg(CWnd* pParent = nullptr);   // 标准构造函数
	virtual ~AssistMainDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_MAIN_DIALOG };
#endif
protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
	DECLARE_MESSAGE_MAP()
// 用户实现方法:AssistMainDlg 消息处理程序
public:
	virtual BOOL OnInitDialog();
	afx_msg void OnBnClickedButton1GetMoney();
};

// AssistMainDlg.cpp: 实现文件
#include "pch.h"
#include "wmgj_dll.h"
#include "AssistMainDlg.h"
#include "afxdialogex.h"
// AssistMainDlg 对话框
IMPLEMENT_DYNAMIC(AssistMainDlg, CDialog)
AssistMainDlg::AssistMainDlg(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_MAIN_DIALOG, pParent){
}
AssistMainDlg::~AssistMainDlg(){
}
void AssistMainDlg::DoDataExchange(CDataExchange* pDX){
	CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(AssistMainDlg, CDialog)
	ON_BN_CLICKED(IDC_BUTTON1_GET_MONEY, &AssistMainDlg::OnBnClickedButton1GetMoney)
END_MESSAGE_MAP()

// 用户实现方法:AssistMainDlg 消息处理程序
BOOL AssistMainDlg::OnInitDialog(){
	CDialog::OnInitDialog();
	MessageBox(L"窗口加载完成", L"窗口加载完成", 0);
	return TRUE; 
}
void AssistMainDlg::OnBnClickedButton1GetMoney(){
	int* money = (int*)0x00845628;//这个就是金钱地址
	*money = *money + 1;
}

二、寻找药品call

在这里插入图片描述

//吃药call(0x4175B开始)
//说明:这里的吃药call是有消耗的

//用z药
push 0x0
push 0xDD
mov ecx,0x845618
call 0x42D540

//用x药
push 0x0
push 0xDE
mov ecx,0x845618
call 0x42D540

//用c药
push 0x0
push 0xDF
mov ecx,0x845618
call 0x42D540

//要吃药无消耗方法:
//把0x0041CEB6处的指令mov [edi+00000198],ecx改成nop即可
//技能call
//说明:这里释放技能是最开始的入口,会先播放动画及判断内力够不够,最后再跳到执行call(就是上一篇笔记中技能调用的call)
//当然下面的主call也是可以用的,但是有消耗有延时,所以用回上一篇笔记的技能释放call就可以了,而上面吃药本身就没有延时,所以直接用吃药的主call吧

//用A技能
push 14
mov ecx,0x845618
call 0x82D3D0

//用S技能
push 15
mov ecx,0x845618
call 0x82D3D0

//用D技能
push 16
mov ecx,0x845618
call 0x82D3D0

//用F技能
push 17
mov ecx,0x845618
call 0x82D3D0

//用G技能
push 18
mov ecx,0x845618
call 0x82D3D0

三、寻找道具/武功栏地址

找到两个数量不同的物品,在物品栏第一格互换位置即可得到第一格物品数量地址,同理得第二格地址,得每格物品所占空间
找到两个级别不同的技能,在技能栏第一格互换位置即可得到第一格技能级别地址,同理得第二格技能,得每格技能所占空间
对第一格物品数量地址浏览附近内存区域,再把第一格物品换掉,观察变化的内存,易得物品栏首址
对第一格技能级别地址浏览附近内存区域,再把第一格技能换掉,观察变化的内存,易得技能栏首址
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值