破解扫雷并不是说玩个扫雷都需要去使用作弊手段,只是为了学习思路和方法
扫雷游戏大家应该都玩过吧,我这里就不献丑演示游戏玩法了
扫雷.zip
(2.78 MB, 下载次数: 894)
2021-12-7 08:45 上传
点击文件名下载附件
扫雷
下载积分: 吾爱币 -1 CB
其中包含 扫雷EXE源文件 以及 VS工程 (DLL文件在MFC -> Debug ->MFCSL.dll)
一、数据分析
1.雷的数量
添加后分别修改,最后发现地址为01005630的数值进行修改操作时游戏中的雷数会发生变化,故这里就确定的雷数的地址。
image.png (144.22 KB, 下载次数: 0)
下载附件
1.1
2021-12-6 18:20 上传
2.游戏时间
找到时间的地址,之后通过NOP时间可以实现0秒通关的效果。
image.png (118.78 KB, 下载次数: 0)
下载附件
1.2
2021-12-6 18:21 上传
3.雷区宽度
同样的搜索方法寻找到疑似雷区宽度的数据,之后仍进行修改验证测试。
image.png (122.78 KB, 下载次数: 0)
下载附件
1.3
2021-12-6 18:22 上传
4.雷区高度
同样的搜索方法寻找到疑似雷区高度的数据,之后仍进行修改验证测试。
image.png (148.38 KB, 下载次数: 0)
下载附件
1.4
2021-12-6 18:22 上传
5.数据汇总
image.png (64.36 KB, 下载次数: 0)
下载附件
1.5
2021-12-6 18:22 上传
二、游戏分析
1.遍历雷区
①创建DLL项目
使用Visual Studio创建MFC动态链接库项目,并选定DLL类型为静态链接(我这里使用的是Visual Studio2022)
image.png (102.31 KB, 下载次数: 0)
下载附件
2.1.1
2021-12-6 18:24 上传
②使用Spy++获取窗口信息(Visual Studio2022菜单栏 工具->Spy++ 自带的工具)
image.png (71.64 KB, 下载次数: 0)
下载附件
2.1.2
2021-12-6 18:25 上传
③查看扫雷窗口信息
image.png (169.28 KB, 下载次数: 0)
下载附件
2.1.3
2021-12-6 18:26 上传
2.此处写了一个遍历雷区的测试代码,供大家参考
[C] 纯文本查看 复制代码 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
image.png (291.17 KB, 下载次数: 0)
下载附件
3.1.1
2021-12-6 18:28 上传
②使用DebugView工具观察注入情况,对比游戏参数验证数值是否正确
image.png (81.53 KB, 下载次数: 0)
下载附件
3.1.2
2021-12-6 18:28 上传
4.反汇编代码调试
①将扫雷程序附加到OD中查看其内存情况,可以看出边界为10,雷为8F,标识为41,42…(无数字标识的地方为40)
image.png (230.17 KB, 下载次数: 0)
下载附件
4.1.1
2021-12-6 18:29 上传
②分析雷区数组的汇编代码部分
image.png (164.08 KB, 下载次数: 0)
下载附件
4.1.2
2021-12-6 18:30 上传
③测试遍历雷区代码,并重新注入进行测试
[C] 纯文本查看 复制代码 for (size_t y = 1; y
④再次使用DebugView工具观察注入情况
image.png (170.13 KB, 下载次数: 0)
下载附件
4.1.4
2021-12-6 18:31 上传
5.坐标转换
我们希望完成一键扫雷的操作,那我们就应该有模拟点击的操作,但是我现在并不知道雷区的坐标是什么样的,所以我们需要先进行坐标的转换
①获取回调函数
找到窗口回调函数的位置,仍然通过Spy++查看,使用Spy++获取窗口信息再点击确定就会弹出该窗口
image.png (54.58 KB, 下载次数: 0)
下载附件
5.1.1
2021-12-6 18:33 上传
6.反汇编代码调试
①在汇编代码中找到窗口回调函数的位置,并设置假定参数,再设置消息断点
image.png (126.54 KB, 下载次数: 0)
下载附件
6.1.1
2021-12-6 18:35 上传
②分析鼠标事件坐标转换的汇编代码部分
image.png (76.18 KB, 下载次数: 0)
下载附件
6.1.2
2021-12-6 18:35 上传
③获取鼠标位置,反馈窗口信息
[C] 纯文本查看 复制代码 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"扫雷");
}
④通过模拟点击事件把所有非雷区域点击,实现一键通关
[C] 纯文本查看 复制代码xPos = (x
⑤游戏效果
按下F5后实现一秒钟自动扫雷并通关游戏
image.png (229.84 KB, 下载次数: 0)
下载附件
6.1.5
2021-12-6 18:36 上传
7.NOP游戏时间
①获取地址
在CE中找出是什么访问了这个地址,再显示反汇编程序,可以观察到时间的变化
image.png (166.76 KB, 下载次数: 0)
下载附件
7.1
2021-12-7 08:41 上传
②反汇编代码调试
由CE可以看到01002FF5处的指令代码实现时间的自增,如果想要时间不增加,就需要把这里给NOP掉,即将FF 05 9C570001 这六个字节都填充为NOP
image.png (24.73 KB, 下载次数: 0)
下载附件
7.2.1
2021-12-7 08:41 上传
当我们第一次按下时,01003830会自增1,所以导致之前无法实现0秒完成扫雷,故我们应该把01003830的数据也NOP掉
image.png (39.64 KB, 下载次数: 0)
下载附件
7.2.2
2021-12-7 08:42 上传
③将时间自增语句使用NOP填充
[C] 纯文本查看 复制代码 //获取扫雷进程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);
④游戏效果
image.png (217.56 KB, 下载次数: 0)
下载附件
7.4
2021-12-7 08:42 上传
三、完整代码
[C] 纯文本查看 复制代码// 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[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 > 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;
}