新剑侠情缘学习笔记

一、结论

·

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注入:按键代替按钮
  2. 寻找药品call:血量低自动吃
  3. 寻找敌人/NPC坐标:自动打怪/聊天
  4. 寻找道具/武功栏地址:代码换物品

二、寻找药品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

猜你喜欢

转载自blog.csdn.net/cj1064789374/article/details/114325026