本帖最后由 Nattevak 于 2021-12-7 08:48 编辑 自己对游戏方面挺感兴趣的,但是毕竟新手入门水平,想搞搞大型游戏还没有那水平,饭还得一口一口吃,先从经典的扫雷小游戏开始尝试 破解扫雷并不是说玩个扫雷都需要去使用作弊手段,只是为了学习思路和方法 扫雷游戏大家应该都玩过吧,我这里就不献丑演示游戏玩法了 ![]() ![]() 一、数据分析 1.雷的数量 添加后分别修改,最后发现地址为01005630的数值进行修改操作时游戏中的雷数会发生变化,故这里就确定的雷数的地址。 ![]() 2.游戏时间 找到时间的地址,之后通过NOP时间可以实现0秒通关的效果。 ![]() 3.雷区宽度 同样的搜索方法寻找到疑似雷区宽度的数据,之后仍进行修改验证测试。 ![]() 4.雷区高度 同样的搜索方法寻找到疑似雷区高度的数据,之后仍进行修改验证测试。 ![]() 5.数据汇总 ![]() 二、游戏分析 1.遍历雷区 ①创建DLL项目 使用Visual Studio创建MFC动态链接库项目,并选定DLL类型为静态链接(我这里使用的是Visual Studio2022) ![]() ②使用Spy++获取窗口信息(Visual Studio2022菜单栏 工具->Spy++ 自带的工具) ![]() ③查看扫雷窗口信息 ![]() 2.此处写了一个遍历雷区的测试代码,供大家参考 [C] 纯文本查看 复制代码 0102030405060708091011if (Msg == WM_KEYDOWN && wParam == VK_F5){ //一键秒杀 OutputDebugString(L"F5"); int nWidth = *g_pWidth; int nHeight = *g_pHeight; int nMineCount = *g_pMineCount; CString strString; strString.Format(L"宽度: %d,高度: %d,雷数:%d ", nWidth, nHeight, nMineCount); OutputDebugString(strString.GetBuffer());} 3.注入DLL ①使用CE 工具->注入DLL ![]() ②使用DebugView工具观察注入情况,对比游戏参数验证数值是否正确 ![]() 4.反汇编代码调试 ①将扫雷程序附加到OD中查看其内存情况,可以看出边界为10,雷为8F,标识为41,42…(无数字标识的地方为40) ![]() ②分析雷区数组的汇编代码部分 ![]() ③测试遍历雷区代码,并重新注入进行测试 [C] 纯文本查看 复制代码 0102030405060708091011121314151617for (size_t y = 1; y < nHeight + 1; y++){ CString strLine; for (size_t x = 1; x < nWidth + 1; x++) { //数组基地址+(y+1)*32+x+1(y=0到高度) BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32); if (byCode == MINE) { nFindCount++; } CString strCode; strCode.Format(L"%02x ", byCode); strLine += strCode; } OutputDebugString(strLine.GetBuffer());} ④再次使用DebugView工具观察注入情况 ![]() 5.坐标转换 我们希望完成一键扫雷的操作,那我们就应该有模拟点击的操作,但是我现在并不知道雷区的坐标是什么样的,所以我们需要先进行坐标的转换 ①获取回调函数 找到窗口回调函数的位置,仍然通过Spy++查看,使用Spy++获取窗口信息再点击确定就会弹出该窗口 ![]() 6.反汇编代码调试 ①在汇编代码中找到窗口回调函数的位置,并设置假定参数,再设置消息断点 ![]() ②分析鼠标事件坐标转换的汇编代码部分 ![]() ③获取鼠标位置,反馈窗口信息 [C] 纯文本查看 复制代码 0102030405060708091011x = LOWORD(lParam);y = HIWORD(lParam);x = (x + 4) >> 4;y = (y - 0x27) >> 4;BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32);if (byCode == MINE){ SetWindowText(hWnd, L"此处有雷");}else{ SetWindowText(hWnd, L"扫雷");} ④通过模拟点击事件把所有非雷区域点击,实现一键通关 [C] 纯文本查看 复制代码 1234xPos = (x << 4) - 4;yPos = (y << 4) + 0x27;SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(xPos, yPos));SendMessage(hWnd, WM_LBUTTONUP, 0, MAKELPARAM(xPos, yPos)); ⑤游戏效果 按下F5后实现一秒钟自动扫雷并通关游戏 ![]() 7.NOP游戏时间 ①获取地址 在CE中找出是什么访问了这个地址,再显示反汇编程序,可以观察到时间的变化 ![]() ②反汇编代码调试 由CE可以看到01002FF5处的指令代码实现时间的自增,如果想要时间不增加,就需要把这里给NOP掉,即将FF 05 9C570001 这六个字节都填充为NOP ![]() 当我们第一次按下时,01003830会自增1,所以导致之前无法实现0秒完成扫雷,故我们应该把01003830的数据也NOP掉 ![]() ③将时间自增语句使用NOP填充 [C] 纯文本查看 复制代码 1234567//获取扫雷进程IDGetWindowThreadProcessId(g_Wnd, &Pid);//获取扫雷进程句柄hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);//将时间自增的语句使用NOP填充result1 = WriteProcessMemory(hProcess, (LPVOID)g_pTime1, &szInc, 6, 0);result2 = WriteProcessMemory(hProcess, (LPVOID)g_pTime2, &szInc, 6, 0); ④游戏效果 ![]() 三、完整代码 [C] 纯文本查看 复制代码 001002003004005006007008009010011012013014015016017018019020021022023024025026027028029030031032033034035036037038039040041042043044045046047048049050051052053054055056057058059060061062063064065066067068069070071072073074075076077078079080081082083084085086087088089090091092093094095096097098099100101102103104105106107108109110111112113114115116117118119// MFCSL.cpp: 定义 DLL 的初始化例程。//#include "pch.h"#include "framework.h"#include "MFCSL.h"#ifdef _DEBUG#define new DEBUG_NEW#endif// 唯一的 CMFCSLApp 对象CMFCSLApp theApp;HWND g_Wnd;//窗口句柄WNDPROC g_OldProc;//老的窗口回调函数PDWORD g_pHeight = (PDWORD)0x01005338;//雷区高度PDWORD g_pWidth = (PDWORD)0x01005334;//雷区宽度PDWORD g_pMineCount = (PDWORD)0x01005330;//雷的数量PBYTE g_pBase = (PBYTE)0x1005340;//雷区基地址#define MINE 0x8F//雷区中的元素标识DWORD Pid = 0;//进程IDHANDLE hProcess = 0;//进程句柄PDWORD g_pTime1 = (PDWORD)0x01002FF5;//时间自增PDWORD g_pTime2 = (PDWORD)0x01003830;//时间首次自增char szInc[6] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };//保存NOP指令DWORD result1, result2;//保存结果// CMFCSLApp 初始化LRESULT CALLBACK WindowProc( _In_ HWND hWnd, _In_ UINT Msg, _In_ WPARAM wParam, _In_ LPARAM lParam){ //获取扫雷进程ID GetWindowThreadProcessId(g_Wnd, &Pid); //获取扫雷进程句柄 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); //将时间自增的语句使用NOP填充 result1 = WriteProcessMemory(hProcess, (LPVOID)g_pTime1, &szInc, 6, 0); result2 = WriteProcessMemory(hProcess, (LPVOID)g_pTime2, &szInc, 6, 0); if (Msg == WM_KEYDOWN && wParam == VK_F5) { //一键秒杀 OutputDebugString(L"F5"); int nWidth = *g_pWidth; int nHeight = *g_pHeight; int nMineCount = *g_pMineCount; CString strString; strString.Format(L"宽度: %d,高度: %d,雷数:%d ", nWidth, nHeight, nMineCount); OutputDebugString(strString.GetBuffer()); int nFindCount = 0; for (size_t y = 1; y < nHeight + 1; y++) { CString strLine; for (size_t x = 1; x < nWidth + 1; x++) { //数组基地址+(y+1)*32+x+1(y=0到高度) BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32); if (byCode == MINE) { nFindCount++; } else { int xPos, yPos; xPos = (x << 4) - 4; yPos = (y << 4) + 0x27; SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(xPos, yPos)); SendMessage(hWnd, WM_LBUTTONUP, 0, MAKELPARAM(xPos, yPos)); } CString strCode; strCode.Format(L"%02x ", byCode); strLine += strCode; } OutputDebugString(strLine.GetBuffer()); } CString strCode; strCode.Format(L"找到的雷数 %d ", nFindCount); OutputDebugString(strCode.GetBuffer()); } else if (Msg == WM_MOUSEMOVE) { //鼠标移动 int x, y; x = LOWORD(lParam); y = HIWORD(lParam); x = (x + 4) >> 4; y = (y - 0x27) >> 4; BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32); if (byCode == MINE) { SetWindowText(hWnd, L"此处有雷"); } else { SetWindowText(hWnd, L"扫雷"); } } return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam);} BOOL CMFCSLApp::InitInstance(){ CWinApp::InitInstance(); //1.通过查找窗口,获取窗口句柄 g_Wnd = ::FindWindow(L"扫雷", L"扫雷");//FindWindowW(_In_opt_ LPCWSTR lpClassName,_In_opt_ LPCWSTR lpWindowName); if (g_Wnd == NULL) { OutputDebugString(L"无法找到 扫雷窗口"); return FALSE; } //2.设置窗口回调函数 g_OldProc = (WNDPROC)SetWindowLong(g_Wnd, GWL_WNDPROC, (LONG)WindowProc); if (g_OldProc == NULL) { OutputDebugString(L"设置窗口回调函数失败"); return FALSE; } return TRUE;} |