目录

  1. 问题导入
  2. 雷区分析
  3. 逆向分析

问题导入

  上一节中我们通过修改注册表修改了扫雷英雄榜,自行改动了榜上的昵称和时间。然而这只是表面工作,没有解决扫雷游戏的本质:找出所有雷区。这一节我们分析雷区的存储方式和位置,人工破解雷区。

雷区分析

  扫雷的界面是一个n*m的矩形雷区,每个小方块代表一个位置,和矩阵十分类似。因此我们猜想是通过二维矩阵来存储数据,将矩阵中特定位置标记为雷区,而每次点击非雷区时会出现数字,即计算点击位置周边八个块的地雷数目,然后显示在块上。
  将矩阵转化成扫雷的界面则需要调用几个API函数,比较常用的就是BitBlt函数。BitBlt将某一内存块的数据传送到另一内存块,前一内存块被称为”源”,后一内存块被称为”目标”。使用该函数我们可以将矩阵进行变换,从而显示出来。

逆向分析

  使用OllyDBG打开扫雷程序winmine.exe,查看调用的API函数。

  可以看到从GDI32.dll导入的函数很多与作图有关,如SetLayout、CreatePen等。我们选中BitBlt函数,在每个参考上设置断点,然后运行至断点处。
  第一个断点处,整个扫雷界面都未加载完成,而且我们也没有进行点击,故不是所需代码块,取消该出断点,继续运行。
  图像界面显示出来,程序停止了运行,表示程序还未运行到第二个断点,可能在等待用户点击鼠标。我们选择雷区任意一个位置点击,程序运行到第二个断点处停止。

  BitBlt所需参数均在图中标识出来,现在我们需要分析这些参数,找到二维矩阵的内存位置。   首先我们要知道矩阵位置的计算,对于一维矩阵,a[n],a[i]的内存位置=基址(a[0])+i\*sizeof(单个数据内存大小)。对于二维矩阵a[m][n],a[i][j]的内存位置=基址+i\*n\*sizeof(单个数据内存大小)+j\*sizeof(单个数据内存大小)。

  函数中刚好有两条类似的寻址语句。[edx+eax+01005340h]和[edx*4+105A20]。第一条是mov操作,并且更符合二维数组寻址,因此我们猜测01005340h是数组基址。

  在左下角数据窗口使用ctrl+G,输入01005340h,跳转到该位置。查看数据窗口内容。

  数据窗口一开始全是10,有33个。由于我们使用的是高级模式,扫雷界面是16*32,初步猜测10代表边界。然后是大量0F和少量8F。前七个数据是六个0F和一个8F,那我们点击扫雷第一排前七个格子。前七个均没有雷,第八个是雷。

  查看数据区内容是否发生变化:

  前五个格子为空,数据由0F变为40;第六个格子是数字1,数据由0F变为41。同时数字2、3对应数据也分别变为42、43。得出结论:未点开非雷区数据为0F,点开的非雷区数字x的数据为4x。
  第七个格子是我们点中的雷,数据由8F变为CC;而第八个格子我们未点中的雷由8F变为8A。得出结论:原数据8F即表示该位置是雷。
  除此之外,扫雷还有两个符号,旗子和问号,现在我们再次尝试,分别在雷区和非雷区标上旗子和问号,查看数据。


  标上旗子,雷区数据由8F变成8E,非雷区数据由0F变成0E;标上问号,雷区数据由8F变成8D,非雷区数据由0F变成0D。由此可得雷区最高位为1,不论是8(1000)还是C(1010),最高位都是1;非雷区最高位为0。
  由以上情况作出总结:
雷区(最高位为1):
  1.未点开为8F
  2.点开为CC,其他为8A
  3.标上旗子为8E
  4.标上问号为8D
非雷区(最高位为0):
  1.未点开为0F
  2.点开为4x(x代表格子显示数字)
  3.标上旗子为0E
  4.标上问号为0D
当然我们也可以人为修改数据达到各种效果,如初始化是雷区却不爆雷等。