首页
社区
课程
招聘
[原创]经典扫雷游戏破解实现
发表于: 2022-1-4 15:30 13852

[原创]经典扫雷游戏破解实现

2022-1-4 15:30
13852

破解扫雷并不是说玩个扫雷都需要去使用作弊手段,只是为了学习思路和方法

 

扫雷游戏大家应该都玩过吧,我这里就不献丑演示游戏玩法了

一、分析思路

1.判断开发语言

可以发现版本为VC2003,根据动态库可以看到msvrt.dll,此为微软的运行时库,可判断此程序为SDK程序,且未发现MFC的动态库,虽然也有可能是使用的静态编译,但是我们观察程序大小可以发现只有100多K,这样我们几乎是可以肯定这就是一个SDK程序(可以寻找WinMain函数,函数内部可能有CreateWindowExW/A)
图片描述

2.研究路线

1.动态调试(从API入手)——对函数下断点
  ①GetWindowTextA/W,SetWindowTextA/W
  ②MoveWindow,SetWindowPos
  ③RegisterClassEx,找到窗口回调函数
  ④SetTimer、KillTimer
  ⑤rand
通过API断点,栈回溯分析附加代码,找到关键数据及代码
2.分析数据(从数据入手)——尽量寻找变化但可控的数据
此处使用CE工具进行数据分析,尽量寻找变化可控的数据,如:
  (1)雷的数量
  (2)时间
  (3)雷区的高度
  (4)雷区的宽度
变化的值更便于我们在内存中搜索和确定,通过内存搜索找到数据的基址,然后在OD中对基址下断点,以此找到对应的代码进行调试分析。
3.实现功能需要的数据及代码
  1.鼠标位置
  2.扫雷数组的宽度高度以及雷数
  3.扫雷数组基地址
  4.鼠标位置转成扫雷数组下标的代码
  5.扫雷数组下标转成鼠标位置的代码

二、数据分析

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

 

②时间
找到时间的地址,之后通过NOP时间可以实现0秒通关的效果。
图片描述

 

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

 

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

 

⑤数据汇总
最后找到的所有数据与地址。
图片描述

三、代码测试

1.遍历雷区
①创建DLL项目
使用Visual Studio2022 创建MFC动态链接库项目,并选定DLL类型为静态链接。
图片描述

 

②使用Spy++获取窗口信息
使用的VS自带的工具Spy++,获取到扫雷窗口的信息。
图片描述
图片描述

 

③测试代码

1
2
3
4
5
6
7
8
9
10
11
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());
}

④注入DLL
图片描述
使用DebugView工具观察注入情况,对比游戏参数验证数值是否正确
图片描述

 

⑤反汇编代码调试
将扫雷程序附加到OD中查看其内存情况,可以看出边界为10,雷为8F,标识为41,42…(无数字标识的地方为40)
图片描述
分析雷区数组的汇编代码部分
图片描述
测试遍历雷区代码,并重新注入进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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());
}

图片描述

 

2.坐标转换
①获取回调函数
找到窗口回调函数的位置,仍然通过Spy++查看。
图片描述

 

②反汇编代码调试
在汇编代码中找到窗口回调函数的位置,并设置假定参数,再设置消息断点
图片描述

 

分析鼠标事件坐标转换的汇编代码部分
图片描述

 

获取鼠标位置,反馈窗口信息

1
2
3
4
5
6
7
8
9
10
11
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"扫雷");
}

通过模拟点击事件把所有非雷区域点击,实现一键通关

1
2
3
4
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后实现一秒钟自动扫雷并通关游戏。
图片描述

 

3.0秒扫雷
①获取地址
在CE中找出是什么访问了这个地址,再显示反汇编程序,可以观察到时间的变化
图片描述

 

②反汇编代码调试
由CE可以看到01002FF5处的指令代码实现时间的自增,如果想要时间不增加,就需要把这里给NOP掉,即将FF 05 9C570001 这六个字节都填充为NOP
图片描述
当我们第一次按下时,01003830会自增1,所以导致之前无法实现0秒完成扫雷,故我们应该把01003830的数据也NOP掉
图片描述

 

将时间自增语句使用NOP填充

1
2
3
4
5
6
7
//获取扫雷进程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);

游戏效果图
图片描述


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 392
活跃值: (1438)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
学习了
2022-3-7 14:59
0
游客
登录 | 注册 方可回帖
返回
//