破解Windows经典扫雷游戏 – 作者:ijzmesec

2.png

关于扫雷,大概老司机们都有影响,都曾今玩过把。

早年时候虫虫就曾玩过,当时用的不知道怎么玩,都一个一个点出来慢慢玩,所以基本上没怎么赢过,当时借用北大化学实验室做课题,偷偷用一台高级设备带的数据处理电脑玩,被大老板发现了,痛批了一顿(影响深刻)。当时都不知道这款游戏的工作原理,直到最近发现了这个方向,其游戏数据老司机都或多或少通过工具做过,比如金山游侠给仙剑偷道具,用变速齿轮减速然后过关,但是具体底层自己写程序可能没做过,所以小编就找来这个主题和大家一起学习下。。

在Windows XP中,你可以从%systemroot%\system32\winmine.exe中找到扫雷游戏的二进制执行文件。如果你没有Windows XP,也没关系,你可以从网上下载下载地址为(http://www.minesweeper.info/downloads/WinmineXP.html)。下载后再win10下也是可以玩的,小编就是下载的这个版本在win10下玩了一把截的图。

静态分析

首先,让我们看看二进制文件是否启用ASLR。DLL特征的值为0x8000,即’IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE’。

确认这个PE未经ASLR保护编译的。如果你熟悉是精于此道的老司机,可以轻松地对地址进行硬编码。

3.png

通过查看IAT,我们可以通过查看从’gdi32.dll’导入的函数来确定程序使用了Microsoft Windows图形设备接口(GDI)。显而易见的,因为这是一个游戏,我们还可以从’advapi32.dll’中看到使用的注册表API,这意味着我们可以通过注册表篡改应用程序。

4.png

篡改分数

扫雷分数存储在注册表中,并从注册表读取值。如果你检查所有导入并找到对’RegQueryValueExW’API的引用并找到一个中断点,则可以找到注册表的位置。另一个简单的方法是使用沟子API。

5.png

点击断点后,你可以看到堆栈中的参数,在注册表中的位置为:

HKEY_CURRENT_USER\software\Microsoft\winmine

6.png

在此可以修改名称,分数,还有颜色,难度,高度,宽度等其他选项。“Name1”的值与十六进制中的“Time1”的分数值相对应。

7.png

破解扫雷区域

在’BeginPaint’ API中点击断点

8.png

进入该函数

01001C4C |. E8 720E0000 CALL Winmine_.01002AC3

同样的步骤进入函数

01002AE6 |. 56 PUSH ESI ; /Arg1

01002AE7 |. E8 BBFBFFFF CALL Winmine_.010026A7 ;\Winmine_.010026A7

你会看到’BitBlt’ api被用来逐个绘制雷区块。

9.png

如果我们检查寄存器,EBX包含地址字段0x010056360,ESI寄存器用于递增字节。

10.png

如果我们导出EBX寄存器,我们可以从0x01005340确定起始点。

11.png

它向EBX添加0x20,我们可以确定每个字段的结尾。

0100271D |. 83C3 20 |ADDEBX,20Coding实现

首先,要打开游戏的进程,我们用FindWindow获取窗口的句柄,然后将它传递给’GetWindowThreadProcessId’,一旦我们获得’dwProcessId’,我们就可以将该值传递给’OpenProcess’ API。也可以使用使用’CreateToolhelp32Snapshot’技术。

      HWNDwindow = FindWindow(NULL, L”Minesweeper”);

       if (window== NULL)

              return wprintf(L”[-] Failed to find Minesweeper process”);

       GetWindowThreadProcessId(window,&dwProcessId);

       HANDLEprocess = OpenProcess(PROCESS_VM_READ, FALSE, dwProcessId);

接下来,我们将分配一个缓冲来存储我们的雷区的数据。

       LPBYTEbuffer = (LPBYTE)malloc(size);

       if(buffer == NULL)

              return wprintf(L”[-] Failed to allocated memory”);

最后,写一个无限循环来,用’ReadProcessMemory’ API从内存的起始地址读取内存,这样每次我们单击一个字段时,字段就会被更新。我们可以使用0x20作为字段中每行的结尾。

while (true) {

              BOOL ret = ReadProcessMemory(process, (LPVOID)start,buffer, size,&dwRead);

              if (ret == NULL) return wprintf(L”[-] Failed toread memory”);

              for (size_t i = 0, j = 0; i < size; i++, j++) {

                     if (j == 0x20) {

                            puts(“”);

                            j = 0;

                     }

                     printf(“%c”, buffer[i]);

              }

              Sleep(1500);

              system(“cls”);

       }

我们只是使用’ReadProcessMemory’ API从内存中导出雷区数据。

12.png

最后完整的源代码:

#include “stdafx.h”

#include <stdio.h>

#include <tchar.h>

#include <windows.h>

#define start 0x1005340

#define end 0x10056A0

int _tmain(int argc, _TCHAR* argv[]) {

   DWORD dwProcessId = 0;

   DWORD dwRead = 0;

   HWND window = FindWindow(NULL, L”Minesweeper”);

   if (window == NULL)

       return wprintf(L”[-] Failed to find Minesweeper process”);

   GetWindowThreadProcessId(window, &dwProcessId);

   HANDLE process = OpenProcess(PROCESS_VM_READ, FALSE, dwProcessId);

   DWORD size = end – start;

   LPBYTE buffer = (LPBYTE)malloc(size);

   if (buffer == NULL)

       return wprintf(L”[-] Failed to allocated memory”);

   while (true) {

       wprintf(L”[+] Minesweeper hack\n”);

       BOOL ret = ReadProcessMemory(process, (LPVOID)start, buffer, size,

           &dwRead);

       if (ret == NULL) return wprintf(L”[-] Failed to read memory”);

       BYTE field = NULL;

       for (size_t i = 0, j = 0; i < size; i++, j++) {

           if (j == 0x20) {

                puts(“”);

                j = 0;

           }

           printf(“%c”, buffer[i]);

       }

       Sleep(1500);

       system(“cls”);

    }

   return 0;

}

 

 

来源:freebuf.com 2018-04-13 09:21:04 by: ijzmesec

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论