Nattevak 发表于 2021-12-6 18:41

经典扫雷游戏破解实现

本帖最后由 Nattevak 于 2021-12-7 08:48 编辑

自己对游戏方面挺感兴趣的,但是毕竟新手入门水平,想搞搞大型游戏还没有那水平,饭还得一口一口吃,先从经典的扫雷小游戏开始尝试
破解扫雷并不是说玩个扫雷都需要去使用作弊手段,只是为了学习思路和方法
扫雷游戏大家应该都玩过吧,我这里就不献丑演示游戏玩法了{:1_932:}

   其中包含 扫雷EXE源文件 以及 VS工程 (DLL文件在MFC -> Debug ->MFCSL.dll)

一、数据分析
1.雷的数量
添加后分别修改,最后发现地址为01005630的数值进行修改操作时游戏中的雷数会发生变化,故这里就确定的雷数的地址。


2.游戏时间
找到时间的地址,之后通过NOP时间可以实现0秒通关的效果。


3.雷区宽度
同样的搜索方法寻找到疑似雷区宽度的数据,之后仍进行修改验证测试。


4.雷区高度
同样的搜索方法寻找到疑似雷区高度的数据,之后仍进行修改验证测试。


5.数据汇总


二、游戏分析
1.遍历雷区
①创建DLL项目
使用Visual Studio创建MFC动态链接库项目,并选定DLL类型为静态链接(我这里使用的是Visual Studio2022)


②使用Spy++获取窗口信息(Visual Studio2022菜单栏 工具->Spy++自带的工具)


③查看扫雷窗口信息


2.此处写了一个遍历雷区的测试代码,供大家参考
    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());
    }

3.注入DLL
①使用CE 工具->注入DLL


②使用DebugView工具观察注入情况,对比游戏参数验证数值是否正确


4.反汇编代码调试
①将扫雷程序附加到OD中查看其内存情况,可以看出边界为10,雷为8F,标识为41,42…(无数字标识的地方为40)


②分析雷区数组的汇编代码部分


③测试遍历雷区代码,并重新注入进行测试
      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++;
                }
                CString strCode;
                strCode.Format(L"%02x ", byCode);
                strLine += strCode;
            }
            OutputDebugString(strLine.GetBuffer());
      }

④再次使用DebugView工具观察注入情况


5.坐标转换
我们希望完成一键扫雷的操作,那我们就应该有模拟点击的操作,但是我现在并不知道雷区的坐标是什么样的,所以我们需要先进行坐标的转换
①获取回调函数
找到窗口回调函数的位置,仍然通过Spy++查看,使用Spy++获取窗口信息再点击确定就会弹出该窗口


6.反汇编代码调试
①在汇编代码中找到窗口回调函数的位置,并设置假定参数,再设置消息断点


②分析鼠标事件坐标转换的汇编代码部分


③获取鼠标位置,反馈窗口信息
      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"扫雷");
      }

④通过模拟点击事件把所有非雷区域点击,实现一键通关
xPos = (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填充
    //获取扫雷进程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);

④游戏效果


三、完整代码
// 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;//进程ID
HANDLE hProcess = 0;//进程句柄
PDWORD g_pTime1 = (PDWORD)0x01002FF5;//时间自增
PDWORD g_pTime2 = (PDWORD)0x01003830;//时间首次自增
char szInc = { 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;
}

ACEcode 发表于 2021-12-7 21:59

WINDOWS自带的扫雷游戏作弊方法!绝对实用~
打开windows附带的扫雷游戏后
输入
注意,shift要按2秒以上
然后当你的鼠标移动到有地雷的那个小方块时
桌面左上角的一个dot会变成黑色(转)

sd952202 发表于 2021-12-6 19:14

可以自己制作地狱级扫雷了

yinlin 发表于 2021-12-7 08:53

厉害,人脑还是比电脑强:lol

迈克老狼 发表于 2021-12-6 19:10

这个都能破解厉害了

Demo 发表于 2021-12-6 19:11

win7以上的更有挑战性

arssswcr 发表于 2021-12-6 19:17

啊这,本来就是活跃思维的

tiancaiashuai 发表于 2021-12-6 19:22

厉害厉害,实在是佩服啊{:1_921:}

githubi 发表于 2021-12-6 19:22

优秀,有时间学习一下,谢谢

yzice 发表于 2021-12-6 19:28

以后都不能吹嘘会玩扫雷游戏了

孤独尽头是自由 发表于 2021-12-6 19:38

不错不错

翩跹丶 发表于 2021-12-6 19:47

好家伙,这个厉害了
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 经典扫雷游戏破解实现