昨天分析了一下 WinXP 自带的扫雷游戏,现在记录一下,分以下几个部分

  • 找到关键函数
  • 分析函数逻辑与雷区结构
  • 用易语言写一个秒解扫雷的程序

找到关键函数

这一步用 CE 来找会很快,我用 ida + OD 来讲一下思路

首先把 winmine.exe 拖进 ida,因为是有 gui 的程序,肯定会用到很多 win32 API,到 idata 段找找有没有可疑的 引用,在 user32.dll 里发现了 BeginPaint 和 EndPaint

winmine

交叉引用查 BeginPaint,可以发现是 1001C45 的地方在调用

winmine

在 OD 里下个断点,就断在这个地址

运行起来发现扫雷窗口出现,但是东西都没有画出来,在 call BeginPaint 之前压入了扫雷的窗口句柄,那么接下来应该就是画出完整图形了

winmine

上图在 BeginPaint 和 EndPaint 中间夹着的那个红框call F8 步过之后,确实画出了所有区域

winmine

那就步入这个函数

winmine

winmine

winmine

winmine

最后 call 10026A7,雷区的格子就画完了,在 ida 中分析一下这个函数

winmine

这里有个嵌套循环,循环结束的判据是 1005334 和 1005338,推测分别为列数和行数,在 OD 中确认一下

winmine

1005334 为 0x1E(30),1005338 为 0x18(24),确实为扫雷自定义最大的规格(24行30列),同时可以发现下面的区域 8F,0F,10 交错出现,推测为雷区

winmine

接下来我们来找程序处理鼠标点击的函数

玩过的都知道,如果在雷区按住鼠标左键不松开,是不会被视作单击雷块来处理的,也就是说消息为 WM_LBUTTONDOWN 的时候程序应该就简单的获取鼠标坐标来确定按下的是哪一块,并且做图形处理,将按下的效果显示出来。只有当鼠标左键抬起后,才显示是否有雷,所以重点应该是 WM_LBUTTONUP 消息

于是我们跳去 TranslateMessage 函数下一个条件断点,拦截程序对 WM_LBUTTONUP 的处理

下好断点,单击第一个雷块,一路跟踪程序来到 call 10037E1,到 ida 中分析


分析函数逻辑与雷区结构

winmine

在这个函数末尾的有对数组 byte_1005340 的操作,这里的 edx 和 eax 是用户点击的行号和列号

  • test 指令用于判断取出的字节是否已经被点击过且不是雷(就是那些已经被点开,显示周围有多少个雷的格子)
  • and + cmp 用来判断该字节低四位是否为 E,也就是是否为被右键标记过的格子

满足以上两点的格子会被直接忽略,点了也没用,如果是没被点开也没被右键标记过的格子,就调用 1003512

1003512 这个函数首先判断了点击的这个格子是否为雷

winmine

如果不是雷就调用 1003084

winmine

从下图可以看到,该函数调用了 8 次 1003008,对点击格子周围 8 个格子都进行了遍历

winmine

通过调用 1002F3B 查询周围 8 个格子有多少个雷,返回值放在 eax 中,拿 eax 和 0x40 与再赋值到数组对应位置,也就是说点击的格子不是雷,其内存中对应的字节就是 0x40 + 周围 8 个格子雷数,如果周围格子里没有雷,该坐标就会被保存到 10051A0 和 10057C0 两个数组里,这样的坐标都会被自动”点击”,实现一个变相递归

winmine

逻辑部分差不多到这里了,毕竟不是想把整个扫雷逆下来,直接去看雷区结构估计还快点

OD 内存窗口跳到 1005340,根据之前的分析,一行是 32 个字节,0x10 是边界,尝试点击第一个格子

点击前:

winmine

点击后:

winmine

符合之前的分析

一顿尝试之后,发现 0x8F 是雷且没有被点击,0xF 是安全且没有被点击,0x8E 是雷且被右键标记了,0x0E 是安全且被标记了


用易语言写一个秒解扫雷的程序

有了之前的分析,就可以写个程序来读取内存中的雷区数组,一个字节一个字节地判断,遇到 0x8F 就给窗口发送 WM_RBUTTONDOWN 和 WM_RBUTTONUP 的消息,模拟右键标记,其他情况就模拟鼠标左键点击

用到了如下几个 API:

user32: FindWindowA,PostMessageA

kernel32: OpenProcess,CloseHandle,ReadProcessMemory

效果如图:

winmine


资源

链接:网盘
提取码:nm81