【干货】《DNF》反外挂逆向教程05:名字类型判断
文摘
社会
2025-01-09 00:00
广西
上期教程我们讲完了周围遍历。我们找到了两种组结构:数组结构和链表结构。数组结构中存放了多种对象,而链表结构只存放了人物和怪物对象。召唤一个新对象只会增加链表中的一个节点。因此,本期教程我们将讨论如何区分人物和怪物,并通过链表结构中的名字来实现这一区分。我们可以通过比较对象的名字来找到特定的人物对象。除了人物外的其他对象都可以传递到死亡调用函数进行秒杀操作。
然而,在数组结构中,通过名字区分对象就不太可行了,因为数组中的成员不仅包括人物和怪物对象,还包括其他模型对象,比如通关后的门等。如果我们把所有对象都秒杀了,可能会导致游戏崩溃。为了解决这个问题,我们可以使用类型来判断即将要秒杀的对象。因此,在本期教程,我们将探讨如何使用对象的类型和名字来进行区分。现在让我们重新扫描一下,首先找到对象的名字。我们召唤了一个名为“沙袋”的怪物,并对其进行了首次扫描。通过观察扫描结果,我们可以找到120级的沙袋,并将其所有实例找出,然后对其进行修改。首先,我们尝试改变一半,看看发生了什么。将一变成了一代后,我们再次进行修改。修改成二还是一代呢?接着,我们将其改成三,变成了三代。再改成四,是否还是二或者三代呢?改成四后,变成了四代。继续改成五,这应该就是我们要的数字了。现在,我们将这些操作记录到查体bug里面,然后右键点击下一个断点来读取字节,结果直接断在了R9处。R9来自于R8,我们继续向上查找可能会看到很多跳转。R8又来自于RCX,而RCX可能又来自于其他地方,如ADDIEXR8或者and f f f f等。继续向上反,直到断点被移除。此时,我们可能不确定我们到底找到了什么地方,因此,我们再次召唤一个沙袋来看一下。第一个沙袋的地方又变回了沙袋,我们打击第二个沙袋,试图将它们区分开来。接着,我们再次召唤一个沙袋,然后在另一个地方召唤一个。重新查找,看看有没有变化。看起来好像没有变化。直接进行修改,但结果两者都变成了沙袋。尝试改成三,但结果还是一样。改成四后,一个变成了四代,另一个却变成了三代。再尝试打击其中一个,发现另一个也跟着变成了相同的代数。这是怎么回事呢?看来它们使用的可能是相同的模型。将其中一个改成了三代,但打击另一个后,两者都变成了同一代数。让我们再来查看一下。读取一个字节,断点被触发。RBX指向了我们要找的地址。RBX来自于ix加八,而ix又来自于一个call。继续追踪,RCX指向了一个看起来像对象的地址。复制这个地址,加上一个偏移量,然后再调用一个call。再次追踪,发现RCX又指向了一个看起来像头部的地址。减号回到上一层,我们攻击一下,发现RCX似乎是一个对象。让我们继续深入研究。这是一个三代对象,也就是我们刚刚改的那个沙袋。那我们刚刚看到的对象,是不是一个怪物对象呢?怪物对象的偏移加上0x7C8就得到了名字。我们要记下名字的偏移是什么,这样以后就可以通过名字来识别类型了。我们找到了角色对象,看起来好像叫“好吃的软糖”。我们可以在代码段里找到这种游戏类型。或者,我们可以一一对比。比如说,我们从零开始复制出一个人物对象的一部分,然后再找一个怪物的对象进行对比。首先,复制一个人物对象的头部地址,然后找到一个怪物对象的头部地址。攻击一下怪物对象,断点会被触发,RCX传进来的就是一个怪物的对象。我们可以添加一些新的定义结构,确认一下。然后,我们可以进入一个人物对象的数组里找到一个怪物对象,然后再召唤一个怪物,获取他的对象地址。现在我们有三组地址了,我们可以通过这些地址来比较一下。我们可以从头部开始看,第一行是一个人物对象,两个怪物对象的地址是一样的。第二行两个怪物对象的地址不一样,第三行都是零,这是不可能的。第四行两个怪物对象的地址又一样。这两个不一样,但和第一行一样。这一行似乎是一个虚表函数。但是,这两个怪物的对象都不一样。这一行也像一个虚表函数。但我们每次更新都会发生变化。这一行不一样,两个怪物直接就不一样了。一行一行对比,看起来这两个怪物不一样。但实际上,这两个怪物都是同一种怪物。由于它们不一样,所以不能作为类型进行比较。这两个怪物不一样,然后这几个零就不用看了。这一行不一样,这个是一个可以说是一个虚表函数。然后,我们要继续查看其他数据。我记得B8的地方和C0的地方是一组数组。我们进入一个组,找到一组没用的数据。这两个不一样,因为一个是沙袋,一个是我们的对象。所以,我认为这里有三种类型。继续对比,看起来这两个不一样。但是,这个和我的一样也不行。零零不需要看了。这两个也不一样。然后,我们看中间两个,这两个不一样。继续往下看,这两个也不一样。FF是一样的,就不看了。绿颜色的也不看了,看红颜色的。这两个不一样,然后再看红色的,这两个也不一样。我看到了ERC的地方,这两个沙袋都是529,而我的是273。这个不知名的对象是一个二。这个就可以留下了吗?这个是我们所需要的吗?其实我找的就是这个,包括在代码里看到的。我们记录一下。我找的就是这个。然后,我们可以在代码里看到这种数据吗?我们可以通过掉血的地方来举例。比如,我们的早死亡代码。我带大家看一下。清除再召唤一个怪物。生命值改成100,然后召唤一个怪物。然后,攻击这个怪物,观察减少的数值。刚刚,我们直接减少了13,再次扫描未变动的,应该是这两个。我想改一下,给他留1000点血。断点写入,留1000点血不行啊,那就给他留1000点吧。断点,写入四字节,然后我攻击他一下,断下了。这就是我们的死亡调用的地方。是不是这里写入了我们的血量?这里既写入敌人血量,也写入我们自己的血量。那这里会不会就有一个判断类型的呢?否则,他写这么多跳转,干什么?如果我就统一地每个人到这里都加减血,是不是就没必要写这么多跳转?那我们就从头部开始,这个调用的头部开始看一下跳转。我们可以看到有三个分歧点。第一个,就是这里。这里是什么?R8和R8进行了一个对比。下个段看一下R8是什么啊。如果是我们人物对象的话,R8是FFF,所以说跳走了。来看一下怪物对象的情况下。怪物对象还真不好看。这里的IX好像是我们人物对象,不是啊。我们要找到一个人物对象,否则断不下来呀。我们拿到的头部,重新下段,然后编辑不等于RCX运行。然后,直接就F4到这跳到这里来啊。R8是什么?R8是零的时候,是不是就不跳转了?那主要的分歧还是阿巴阿巴干了什么?R8和RCX进行了一个减法。很明显,它是一个血量。R8减了RCX,如果是我们人物的话,它变成了FFF。是因为我们血量是满的吗?我猜想他的血量是满的,所以说变成FF。然后,如果我们血量不满,它减掉血量,是不是我们打怪的时候,我们怪物的血量会减少?所以说,R8才不等于FFF下端啊。我们给它记录一下,B2C,获得了类型。这样,我们就可以通过类型或者名字来判断我们人物对象和怪物对象,具体什么类型的对象可以放进这个死亡调用进行秒杀。本期教程就到这里了。