跳转至

推断用途

到目前为止,我只展示了如何盲目搜索内存中给定的一块数据,而不考虑它的用法。这种方法可能有效,但并不总是高效。在许多情况下,推断数据的用途、确定哪些代码可能使用该数据、然后定位这些代码以最终找到数据的地址,要快得多。

这听起来可能不容易,但「扫描游戏内存中特定数据类型的特定值,然后根据变化的标准持续过滤结果列表」——也就是你到目前为止学到的——也不容易。所以让我们看看如何根据用途定位生命值的地址。考虑清单 5-1 中的代码。

struct PlayerVital {
    int current, maximum;
};
--snip--
printString("Health: %d of %d\n", health.current, 
health.maximum);

清单 5-1:一个包含玩家生命体征的结构,以及一个显示它们的函数

如果你把 printString() 当作游戏界面绘制文本的花哨函数,那么这段代码与你可能在游戏中找到的非常接近。PlayerVital 结构有两个属性:当前值和最大值。health 值是一个 PlayerVital 结构,所以它也有这些属性。仅凭名称,你可以推断 health 的存在是为了显示玩家生命值的信息,当 printString() 使用这些数据时,你可以看到这个用途得到实现。

即使没有代码,你也可以通过只看游戏界面中显示的生命值文本直观地得出类似结论;毕竟,没有代码,计算机什么也做不了。除了实际的 health 变量,还需要存在一些代码元素才能向玩家显示这段文本。首先,需要有某种显示文本的函数。第二,字符串 Health 和 of 必须在附近。

 为什么我假设文本被拆分成两个单独的字符串而不是一个?游戏界面显示当前生命值位于这两个字符串之间,但可能有很多方式实现,包括格式字符串、strcat(),或与多个显示文本调用对齐的文本。分析数据时,最好保持假设宽泛,以涵盖所有可能性。

要不用内存扫描器找到 health,我们可以利用这两个不同的字符串。不过,我们可能不知道显示文本的函数长什么样、在哪里、被调用多少次。现实地说,字符串是我们唯一知道要找的,而这已经足够了。让我们走一遍。