C++程序反编译笔记(19) 雷区数据结构分析
我们接着上文继续分析点击菜单的处理代码.

点击某个菜单项后窗口会收到WM_COMMAND消息, 上文已经将这个消息的处理代码放到了OnCommand函数中, 其中WPARAM参数的低2个字节是菜单项的ID.

分析菜单代码

开局菜单项的ID是520, 那里调用了sub_100367A这个函数, 里面全是不认识的全局变量, 不适合现在分析.

初级, 中级, 高级 三个菜单项的ID分别是521, 522, 523. 整理了一下它们的处理代码后

这里直接将dword_10056A0命名为g_level_10056A0了, 我猜测是用0, 1, 2表示3个游戏难度, 起一个有意义的名字方便分析. 即使错了也没关系, 后面再改个名字就行了.
从这里可以看出来, 这三个数组都应该至少有7个元素, 那么看一下它们的定义

这三个数组都只有一个元素, 明显存在数组越界的问题. 而且这3个数组的后缀地址只相差4个字节(int在Windows系统中是4个字节), 说明前两个数组确实只有一个元素. 这就矛盾了.
解决这个矛盾的唯一办法是将三个数组和并成一个, 实际上对第2个和第3个数组的访问是在访问第1个数组的第2个和第3个元素, v8可能的值是0, 3, 6, 那么这个数组的长度至少是6 + 2 + 1 = 9(索引从0开始, +1得到数组长度). 这说明IDA 将数组长度识别错了. 修正后的代码如下

如果对数组和指针比较熟悉的话, 这里的转换应该不难理解. 使用的公式是
array[index] = *(array + index)
上图中, 拿uValue = dword_1005014[v8] 来解释一下的话, 就是
原来的 uValue 是 从 1005014 + v8 * 4 的地址处取值,
修改后的 uValue 是从 1005010 + (v8 + 1) * 4 = 1005010 + 4 + v8 * 4 = 1005014 + v8 * 4处取值.
所以, 修改前的代码和修改后的代码等价.

修正数组越界
在IDA 中 按G跳转到1005010 处, 将这个变量命名为g_gameLevelSetting, 并右键菜单点击 "Array... ", 然后将数组长度设置为9. 然后用"Edit"菜单下的"Export Data"菜单项导出数组

在Visual Studio中, 使用导出的数组替换原来的三个数组并修正编译错误即可.

成果
在这篇文章中, 如果细心的话, 会发现有个成果还蛮让人欢喜的. 那就是导出的9个元素的数组, 3个元素一组, 第1个元素是地雷个数, 第2个元素雷区宽度, 第3个元素是雷区长度.
比如, 点击"高级"菜单时, v8 = 2 * 3 = 6, 那么dword_1005010[6] = 99 时地雷个数, dword_1005010[7] = 16是雷区宽度, dword_1005010[8] = 40是雷区长度.
因此, 可以知道dword_1005010就是预定义的雷区数据. 到此已经接触到扫雷的数据结构了!