-
-
[原创]经典扫雷游戏破解实现
-
发表于:
2022-1-4 15:30
13772
-
破解扫雷并不是说玩个扫雷都需要去使用作弊手段,只是为了学习思路和方法
扫雷游戏大家应该都玩过吧,我这里就不献丑演示游戏玩法了
一、分析思路
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 );
|
游戏效果图
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)