植物大战僵尸笔记

无限阳光

  1. 打开CE连接游戏程序,通过改变阳光找到阳光值的内存地址

  2. 把该地址添加到候选地址,右击找出是什么改写了这个地址,再回到游戏改变阳光

  3. 得到指令mov [edi+00005560],esi,指针基址可能是 edi=135F7788

  4. CE新的扫描,勾上Hex,数值填135F7788,新的扫描

  5. 扫描类型改未变动的数值,然后按再次扫描若干次

  6. 取左上角筛选出来的地址中的第一个为候选地址,本例为0487A6C8,右击找出是什么访问了这个地址

  7. 会发现很多+00000768的指令,点开第一条指令

  8. mov esi,[edi+00000768],指针基址可能是 edi=04879F60

  9. CE新的扫描,勾上Hex,查找04879F60,首次扫描

  10. 左上角筛选出来的地址中有四个是绿色的地址,下面列出来了:

  • PlantsVsZombies.exe+2A9EC0
  • PlantsVsZombies.exe+2A9F38
  • PlantsVsZombies.exe+2A9F78
  • PlantsVsZombies.exe+2AA00C
  1. 我这里取了第一个作为基地址,双击加入到下面的候选地址栏,得到他的地址006A9EC0

  2. 当然,你把00400000代入PlantsVsZombies.exe去计也是一样的

  3. 至此,基地址006A9EC0,一级偏移地址+768,二级偏移地址+5560,全部找出

  4. 创建MFC,拉一个按钮并双击,写实现代码

void CplantsprojectDlg::OnBnClickedButton1() {
    
    
	/*
		阳光基地址:0x6A9EC0
		阳光第1级偏移地址:[0x6A9EC0]0x768
		阳光第2级偏移地址:[[0x6A9EC0]+0x768]+0x5560
	*/
	UpdateData(TRUE);
	DWORD dwPid = FindGageProcessIdByWndTitle(_T("植物大战僵尸中文版"));
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (!hProcess) {
    
    
		MessageBox(_T("打开失败"), NULL, 0);
		return;
	}
	DWORD dwTemp = 0;	//基地址
	if (!ReadProcessMemory(hProcess, (LPVOID)0x6A9EC0, (LPVOID)&dwTemp, sizeof(DWORD), &dwPid)) {
    
    
		MessageBox(_T("读取失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	dwTemp += 0x768;	//加偏移地址
	if (!ReadProcessMemory(hProcess, (LPVOID)dwTemp, (LPVOID)&dwTemp, sizeof(DWORD), &dwPid)) {
    
    
		MessageBox(_T("读取失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	dwTemp += 0x5560;	//加偏移地址
	DWORD dwShine = 9999;	//是传入的值,表示修改后的阳光数
	DWORD dwLength = 0;		//是传出的值,表示修改了的字节数
	if (!::WriteProcessMemory(hProcess, (LPVOID)dwTemp, &dwShine, sizeof(DWORD),&dwLength)){
    
    
		MessageBox(_T("写入失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	CloseHandle(hProcess);
}

在这里插入图片描述

无限金币

思路同无限阳光,CE连接游戏程序,通过金币改变找地金币的值的内存地址,加入到下面候选地址

右击,找出是什么改写了这个地址,然后回到游戏改变金币,得到更改金币指令,从而得到候选地址=某寄存器值+偏移地址28

新的扫描,16进制,刚刚得到的寄存器值,然后转未改变的数值再扫描若干次

取出左上角筛选地址的第一个到候选地址,右击,找出是什么访问了这个地址,会发现很多某寄存器值+偏移地址82C

新的扫描,16进制,刚刚得到的寄存器值,然后转未改变的数值再扫描若干次

取出左上角筛选地址的绿色地址(有四个任取其一即可),或者说是“本程序名”+0级偏移地址(程序基地址偏移)

至此,得到了金币基地址,一级偏移,二级偏移,在MFC程序中增加按钮,双击,写实现代码:

void CplantsprojectDlg::OnBnClickedButton2(){
    
    
	/*
		金币基地址:006A9EC0(或者说是:程序基地址00400000+0级偏移地址002A9EC0)
		金币1级偏移地址:[006A9EC0]+0x82C
		金币2级偏移地址:[[006A9EC0]+82C]+0x28
	*/
	UpdateData(TRUE);
	DWORD dwPid = FindGageProcessIdByWndTitle(_T("植物大战僵尸中文版"));
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (!hProcess) {
    
    
		MessageBox(_T("打开失败"), NULL, 0);
		return;
	}
	DWORD dwTemp = 0;	//基地址
	if (!ReadProcessMemory(hProcess, (LPVOID)0x6A9EC0, (LPVOID)&dwTemp, sizeof(DWORD), &dwPid)) {
    
    
		MessageBox(_T("读取失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	dwTemp += 0x82C;	//加偏移地址
	if (!ReadProcessMemory(hProcess, (LPVOID)dwTemp, (LPVOID)&dwTemp, sizeof(DWORD), &dwPid)) {
    
    
		MessageBox(_T("读取失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	dwTemp += 0x28;	//加偏移地址
	DWORD dwShine = 9999;	//是传入的值,表示修改后的金币数
	DWORD dwLength = 0;		//是传出的值,表示修改了的字节数
	if (!::WriteProcessMemory(hProcess, (LPVOID)dwTemp, &dwShine, sizeof(DWORD), &dwLength)) {
    
    
		MessageBox(_T("写入失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	CloseHandle(hProcess);
}

无CD

  1. 通过改变某一植物的CD状态,寻找CD值所在的内存地址,右击,找到是什么改变了这个地址,回到游戏改变CD,再回到CE看到mov eax,dword ptr ds:[edi+0x24] ,其内存位置是00487290 ,然后用OD打开游戏程序,查看该位置的反汇编代码

00487290 |. 8B47 24 mov eax,dword ptr ds:[edi+0x24] ; PlantsVs.00690062
00487293 |. 3B47 28 cmp eax,dword ptr ds:[edi+0x28] ; PlantsVs.00730065
00487296 ^ 7E 90 jle short PlantsVs.00487228

  1. 我们在OD测试,把 jle short PlantsVs.00487228 这行代码改成nop nop,发现游戏所有植物都没有了CD,爆破完成

  2. 下面开始在MFC中写实现代码,有几点要注意

  • 读写数据与读写指令是一样的,都是机器码,计算机识别是数据还是指令全靠寄存器,所以下面MFC程序中的读写0x00487296的值同之前无限阳光无限金币的写法一样
  • 00487296不用考虑偏移地址,他是指令,没有局部变量的说法
  • nop nop是90 90 ,原指令jle short 00487228的机器码是7E 14
void CplantsprojectDlg::OnBnClickedCheck1(){
    
    
	UpdateData(TRUE);
	DWORD dwPid = FindGageProcessIdByWndTitle(_T("植物大战僵尸中文版"));
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (!hProcess) {
    
    
		MessageBox(_T("打开失败"), NULL, 0);
		return;
	}
	UCHAR buf[2] = {
    
     0 };
	if (m_edit_nocd) {
    
    
		buf[0] = 0x90;
		buf[1] = 0x90;
	}else {
    
    
		buf[0] = 0x7E;
		buf[1] = 0x14;
	}
	if (!::WriteProcessMemory(hProcess, (LPVOID)0x487296, buf, sizeof(buf), NULL)) {
    
    
		MessageBox(_T("写入失败"), NULL, 0);
		CloseHandle(hProcess);
		return;
	}
	/*
		这里说明一下UpdateData(FALSE)与UpdateData(TRUE)的用法:
		在建立了控件和变量之间的联系后,
		如果后端修改了变量的值,希望前端对话框更新显示,就应该在修改变量后调用UpdateData(FALSE)
		如果前端对话框输入了值,希望后端获得对话框内容,就应该在访问变量前调用UpdateData(TRUE)
	*/
}

大嘴花无限吞食

  1. 在场上放一个大嘴花,利用他吞吃状态与等待状态,用CE寻找到其计时的值的内存地址
  2. 找到是什么改写了这个地址,等大嘴花状态改变,得到改变的指令地址
  3. 到OD定位到该地址(可能有多个,逐个试),对附近的条件跳转类指令逐个改成nop测试,会发现0x461565处是关键跳转
  4. 然后写实现代码,与上面的一样,只是(LPVOID)0x487296的地址变成了(LPVOID)0x461565

自动种植

  1. 找call函数思路:从植物的类型入手,第一下点击植物时是写入内存记录类型,第二下点击空位时是读出内存记录记的类型
  2. OD打开游戏程序,CE连接,首次搜索未知的值
  3. 点击植物,CE找变动的值,未放下,CE找没变动的值若干次
  4. 放置植物,CE找变动的值,未点击新植物,CE找没变动的值若干次
  5. 如此循环得到记录当前鼠标选中的植物的内存地址
  6. 在OD中给这个内存地址打内存读断点,继续运行游戏并操作
  7. 对断点断到的指令,依次进行分析其后面的call,我们预测call之前会push进去xy坐标还有植物种类id
  8. 分析方法一:我们重复用同一种植物在同一位置种植,则call前的push中应完全相同
  9. 分析方法二:我们重复用不同种植物在同一位置种植,则call前的push中应变化一个值
  10. 分析方法三:我们重复用不同种植物在不同位置种植,则call前的push中应变化全部值

!!!重点思想:控制传入的参数找CALL!!!

最后定位到00410A91处的五行代码就是我们要操作的:

00410A94  |.  52            push edx                                 ;  PlantsVs.<ModuleEntryPoint>
00410A95  |.  50            push eax
00410A96  |.  8B4424 20     mov eax,dword ptr ss:[esp+0x20]
00410A9A  |.  57            push edi                                 ;  PlantsVs.<ModuleEntryPoint>
00410A9B  |.  55            push ebp
00410A9C  |.  E8 7FC6FFFF   call PlantsVs.0040D120

00410A94 固定是-1
00410A95 植物种类ID
00410A96 X坐标
00410A9A y坐标
00410A9B 未知的参数,每次运行都有变化

对于未知参数,我们要用前面找阳光与找金币的方法找到他的偏移地址:[[6A9EC0]+768]

!!!重点思想:对不确定意义的传入参数,我们要用偏移地址来表示他!!!

下面是实现代码:


__declspec(naked) void putPlant() {
    
    //__declspec(naked)告诉编译器不要改我写的汇编代码,注意最后要手动ret
	//源码位置00410A91,下面的2是植物类型,3是X坐标,5是Y坐标
	__asm{
    
    
		pushad
		push	-1
		push	2
		mov		eax, 3
		push	5
		mov		ebx,ds:[0x006A9EC0]
		mov		ebx,ds:[ebx + 0x768]
		push	ebx
		mov		edx, 0x40D120
		call	edx
		popad
		ret
	}
}
void CplantsprojectDlg::OnBnClickedButton5() {
    
    
	//一、窗口名获PID
	DWORD dwPid = FindGageProcessIdByWndTitle(_T("植物大战僵尸中文版"));
	//二、PID获进程话柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	if (!hProcess) {
    
    
		MessageBox(_T("打开程序失败"), NULL, 0);
		return;
	}
	//三、分配内存(进程话柄/上一步获取,内存地址/随意,大小/4K,提交/立马使用,权限/读写执行)
	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, putPlant, 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);
}

void CplantsprojectDlg::AddPlantsToComBox() {
    
    
	int tmp = 0;
	tmp = typeOfPlants.AddString(_T("葵花"));//类型是1
	typeOfPlants.SetItemData(tmp, 1);
	tmp = typeOfPlants.AddString(_T("樱桃"));//类型是2
	typeOfPlants.SetItemData(tmp, 2);
	tmp = typeOfPlants.AddString(_T("坚石"));//类型是3
	typeOfPlants.SetItemData(tmp, 3);
	typeOfPlants.SetCurSel(1);
}
typedef struct _PutPlantsPareame {
    
    
	UINT u_x;
	UINT u_y;
	UINT u_plantid;
}PutPlantsPareame,*PPutPlantsPareame;
DWORD __stdcall putPlant2(LPVOID lpThreadParame) {
    
    
	PPutPlantsPareame pParame = (PPutPlantsPareame)lpThreadParame;
	UINT u_x = pParame->u_x;
	UINT u_y = pParame->u_y;
	UINT u_plantid = pParame->u_plantid;
	__asm {
    
    
		pushad
		push    -1
		push	u_plantid
		mov		eax, u_x
		push	u_y
		mov		ebx, ds: [0x006A9EC0]
		mov		ebx, ds : [ebx + 0x768]
		push	ebx
		mov		edx, 0x40D120
		call	edx
		popad
	}
	return 0;
}
void CplantsprojectDlg::OnBnClickedButton6(){
    
    
	//获取参数
	UpdateData(TRUE);
	CString strPlantName = _T("");
	typeOfPlants.GetWindowTextW(strPlantName);
	int tmp = typeOfPlants.FindString(-1, strPlantName);
	UINT plantid = typeOfPlants.GetItemData(tmp);
	PutPlantsPareame parame;
	parame.u_plantid = plantid;
	parame.u_x = m_x;
	parame.u_y = m_y;
	//远程注入
	DWORD dwPid = FindGageProcessIdByWndTitle(_T("植物大战僵尸中文版"));
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);														//需要判空
	LPVOID ThreadFunAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);							//需要判空
	DWORD tmpWrite = 0;
	BOOL wpm1 = WriteProcessMemory(hProcess, ThreadFunAdd, putPlant2, 4096, &tmpWrite);										//需要判假
	LPVOID ParamAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);								//需要判空
	BOOL wpm2 = WriteProcessMemory(hProcess, ParamAdd, &parame, sizeof(parame), &tmpWrite);									//需要判假
	HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadFunAdd, ParamAdd, NULL, NULL);	//需要判空
	DWORD dwWait = WaitForSingleObject(hThread, INFINITE);													//用INFINITE参数则函数会在等不到返回时卡死
	VirtualFreeEx(hProcess, hThread, 0, MEM_RELEASE);
	CloseHandle(hProcess);
}
void CplantsprojectDlg::OnBnClickedButton7(){
    
    
	UpdateData(TRUE);
	CString strPlantName = _T("");
	typeOfPlants.GetWindowTextW(strPlantName);
	int tmp = typeOfPlants.FindString(-1, strPlantName);
	UINT plantid = typeOfPlants.GetItemData(tmp);
	for (UINT i = 0; i <= 5; i++) {
    
    
		for (UINT j = 0; j <= 8; j++) {
    
    
			PutPlantsPareame parame;
			parame.u_plantid = plantid;
			parame.u_x = i;
			parame.u_y = j;
			DWORD dwPid = FindGageProcessIdByWndTitle(_T("植物大战僵尸中文版"));
			HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);														//需要判空
			LPVOID ThreadFunAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);							//需要判空
			DWORD tmpWrite = 0;
			BOOL wpm1 = WriteProcessMemory(hProcess, ThreadFunAdd, putPlant2, 4096, &tmpWrite);										//需要判假
			LPVOID ParamAdd = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);								//需要判空
			BOOL wpm2 = WriteProcessMemory(hProcess, ParamAdd, &parame, sizeof(parame), &tmpWrite);									//需要判假
			HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadFunAdd, ParamAdd, NULL, NULL);	//需要判空
			DWORD dwWait = WaitForSingleObject(hThread, INFINITE);											//用INFINITE参数则函数会在等不到返回时卡死
			VirtualFreeEx(hProcess, hThread, 0, MEM_RELEASE);
			CloseHandle(hProcess);
		}
	}
}
  • 补充找call的重要思路:使用代码注入工具,自己改push的参数来call,验证参数的意义
  • 当然,你直接在od改push指令中要传入的参数值,加断点跑也是可以的

方法总结

  1. CE找多重偏移地址(基本功),实现改值
  2. 根据CE找到的值的内存地址,在OD给该地址加内存断点,在中断指令附近找关键跳转并进行测试,实现改jmp
  3. 根据CE找到的值的内存地址,在OD给该地址加内存断点,在中断指令附近找call并进行测试,测试的方法是跟观察call前的push的值
  • 重复进行游戏观察每一次该call前的push的值的变化,猜测参数意义
  • 在OD修改push的值观察执行效果,验证参数意义
  • 使用代码注入工具,自己改push的参数来call,确保可行后
  • 找不到意义的值要用偏移地址来定

对于改值与改jmp,是比较简单的内存读写
对于追加执行call,是比较复杂的代码注入

猜你喜欢

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