郁金香2021年游戏辅助技术中级班(一)

news2024/11/18 20:00:06

郁金香2021年游戏辅助技术中级班(一)

    • 用代码读取utf8名字
      • 字节数组搜索UTF-8字符串
    • 用CE和xdbg分析对象名字
      • 从LUA函数的角度进行分析
      • 复习怪物名字偏移
    • 用CE和xdbg分析对象数组
      • 认识虚函数表
      • 分析对象数组
    • 分析对象数组链表部分
      • 链表的定义
      • 链表的数据在内存里面究竟是一个什么样的形式

用代码读取utf8名字

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上图从33行开始到43行的那个代码块是UniCodeToAscii函数。

字节数组搜索UTF-8字符串

现在比较新的CE版本是支持搜索UTF-8字符串的:

在这里插入图片描述

较老一点版本的CE是搜索不到UTF-8字符串的,当时也是有办法的,我们可以修改上图字符串类型为字节数组类型:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

通过搜索上图这个字节数组也是能够找到UTF-8字符串的:

在这里插入图片描述

现在我们用的CE7.2这个比较新的版本,它是支持UTF-8的,所以可以直接搜索该字符串。


用CE和xdbg分析对象名字

在这里插入图片描述

在这里插入图片描述

早期的CE搜不到UTF-8字符串,这里可以用该工具把我们普通的UTF-8字符串转换成字节集的,然后在CE中搜索该字节集。

搜索怪物名字,来找到怪物名字的偏移,但是有可能会搜到很多结果,哪一个才是我们身边选中的怪物呢?
这时候要通过修改名字来过滤筛选了:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

发现身边怪物名字没有变化,那就删除选中的这一大片名字(例如二分法),继续再选大部分名字进行修改,不断重复该步骤。

在这里插入图片描述

还是没有影响到身边该怪物的名字,删除,继续选择剩下的所有名字进行修改:
在这里插入图片描述

注意,再修改名字的时候,不要移动人物、击打怪物,避免刷新地图上的怪物,导致数据过期;另外可以用鼠标来回选中最近的怪物(或者先选中自己,再选那个怪物),切换一下看看选中怪物的血量面板上的名字是否改变(如上图所示,面板上的改变了,模型上的名字没有改变)。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

先选中自己,再选最近怪物,如果怪物面板上的名字已改变,删除剩下的部分,只留下受影响的部分,继续二分法修改(少的时候可以从后往前一半一半的过滤),直至确定当前选中怪物名字的地址,然后就可以去找它的偏移了。

在这里插入图片描述

在这里插入图片描述

我们最好是在xdbg中去分析该地址:

在这里插入图片描述

在这里插入图片描述

在内存窗口该地址处下个4字节的访问断点,直接断下,可以看到eax中就是2D05A2C8,我们来看看eax的来源:

在这里插入图片描述

在这里插入图片描述

我们发现[ebp+8]是等于0的,所以eax=[ecx+5c],这是第一个偏移。

在这里插入图片描述

我们在内存窗口中查看,ecx+5c刚好就是存放2D05A2C8的地址,所以ecx+5c就是怪物名字的第一个偏移;
而第二个偏移(查找ecx的来源)就要到上一层去看看了(通过堆栈窗口双击返回地址):

在这里插入图片描述

在这里插入图片描述

也就是上一层的74F857这个地方调用了53B8E0这个获取怪物名字CALL。

在这里插入图片描述

而我们要找的ecx就来源于67B6A0这个CALL的返回值,即[CALL 67B6A0的返回值+5C]就是怪物名字地址,所以现在的重点就是要进入到67B6A0这个CALL里面分析该CALL的返回值。

由于该CALL比较复杂,我们先在该CALL调用处下个断来跟进去,分析它是从什么地方返回这个eax返回值的;
首先,我们F8单步步过该CALL,看看[eax+5c]处的内容是不是怪物的名字:

在这里插入图片描述

说明我们的找法(分析的位置)是没有问题的,好,我们重新让游戏运行起来,重新断在74F84A地址这个CALL,然后按F7跟进去分析返回值eax,进去后我们一直按F8,去追eax是在什么地方返回的:

在这里插入图片描述

上图就说明了所处位置不对,肯定不是从上图这个地方返回的(否则是能够读出怪物名字的),我们继续向下F8跟:

在这里插入图片描述

从上图这里来看的话,它还有一个0x18的偏移:

在这里插入图片描述

经测试,0x18确实是怪物名字的一个偏移,然后我们再来看一下0F26EF20又是从什么地方来的,我们可以看 - 减号一步一步退回去,看一下0F26EF20这个eax来源于什么地方。

在这里插入图片描述

这个eax来源于CALL 6F6020的返回值。

在这里插入图片描述

这个CALL 6F6020感觉是到怪物数组里面去把那个怪物对象取出来(可以看到ds:[edx+eax*4+4]这种全局数组或静态数组的反汇编形式),有点这种感觉,我们在该CALL开头做个标记。

这个CALL我们感觉有些熟悉,在初级班29课分析角色对象的属性那课:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在CALL 4D4DB0查询对象这个CALL里面有个CALL 4D4BB0:

在这里插入图片描述

这个CALL 4D4DB0和我们刚才分析的CALL 6F6020很相像。

在这里插入图片描述

在这里插入图片描述

对比分析,感觉都是一个模式印出来的,相似度都是非常高的,只是这两个CALL的地址不一样,从代码来分析的话基本上都是一样的。

目前我们还是回到CALL 67B6A0分析上来,就是说这个地方有可能就是遍历怪物数组的地方,遍历完就返回这个怪物对象,基本上就是返回的这个对象+0x18+5C就是怪物名字的地址了;
而且我们在74F84F地址下断,发现返回值eax总是固定的值F0FFCF8(而F0FFCF8-0x18就刚好是上图内层CALL 6F6020的返回值F0FFCE0),即使我们将视角变化一下,可视范围内怪物对象多一些,返回值eax也不变,可能是由于这是选中的那个怪物对象,那么我们换一下怪物看看,甚至选中远处的怪物,结果我们发现返回值eax还是不变,这就比较奇怪了,返回的老是相同的一个数值,这需要下来之后作进一步的分析了。

接着我们看一下传给CALL 6F6020的参数是什么,都是什么内容,猜测都是干什么用的:

在这里插入图片描述

我们在内存窗口中转到当前esp,然后用64位十六进制整数查看,这是为了分析该数值是否是怪物的ID:

在这里插入图片描述

然后用CE搜一下3ACFD370000012B这个8字节的数值:

在这里插入图片描述

发现只有一处地方有这个数值,目前还不知道该数值是不是怪物的ID(只有猜测)。

在这里插入图片描述

有可能CALL 6F6020这个CALL是用怪物的ID来查询怪物的名字,具体我们下去再分析一下。

我们用CE搜一下什么地方存了eax=0F20E4F0这个指针:

在这里插入图片描述

发现搜到的结果比较多,那么我们进到0074F84A地址处的CALL 0067B6A0里面:

在这里插入图片描述

我们发现上图堆栈窗口中每次断下CALL 006F6020的时候传入的参数2会发生变化,ECX没有变动。

从LUA函数的角度进行分析

实际上另外有一种方法可以快速的分析到,通过游戏注册的LUA函数:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在UnitName函数这里,如果传入的是选中的对象,也可以跟到这里。

在这里插入图片描述

我们看一下eax的返回值,因为这是个LUA的函数,最终会返回一个名字,如上图所示这时候返回的是玩家的名字。

我们先把断点删除,让游戏跑起来,选择一个怪物,然后在上图LUA函数开头下断,重新断一下再看看它返回的目标又是什么。

我们也可以在游戏中聊天窗口那里运行宏/run UnitName("target"),如下图所示准备好该LUA指令:

在这里插入图片描述

由于怪物一直在攻击我,该LUA函数这里不好下断,所以我们要在不被攻击的情况下选中目标(可以选稍远一些的):

在这里插入图片描述

内存窗口中转到eax的值,可以看到目标名字就是从CALL 4FD0E0这个CALL返回的,在这个CALL里面必定能找到怪物的名字和偏移。

在这里插入图片描述

我们在该CALL下断,然后在游戏里输入lua指令/run UnitName("target"),回车就会断在该CALL,此时eax=2D1AEF90,我们记下来,然后继续F8单步到CALL 4FD0E0:

在这里插入图片描述

可以看到此时ecx=2D1AEF90,会作为参数传入CALL 4FDDE0,我们跟进该CALL里面,看一下名字是从什么地方来的:

在这里插入图片描述

继续往下跟:

在这里插入图片描述

怪物名字应该是从CALL 72A000这个CALL里面来的,我们跟进去:

在这里插入图片描述

在这里插入图片描述

走到这里的时候,ecx=2D1AEF90仍然是我们的怪物对象:

在这里插入图片描述

在这里插入图片描述

怪物名字的两个偏移一下子就出来了。

UnitName这个LUA的接口就是显示我们选中目标(参数为"target")的名字,当然它实际不会显示出来,如果你要显示出来可以输入/run print(UnitName("target"))

在这里插入图片描述

在这里插入图片描述

如果选中自己就会显示角色名字:

在这里插入图片描述

选中NPC同理,用LUA指令就很简单了。

另一条分析的路径(就是那个+0x18)应该也是能够走通的,具体我们还要以后继续分析该路径,难点就是6F6020这个CALL比较复杂,它里面存在很多数组操作。

复习怪物名字偏移

我们以NPC五毛为例:

在这里插入图片描述

搜到之后对名字进行修改,这时候我们选自己,然后再选五毛,查看角色血量面板,可以看到NPC五毛的名字已经被修改了,NPC人物模型头上的名字变,为了五23(一定不要去看人物头顶上的名字),而面板上的变为五毛11,我们这里要以面板显示的为准。

在这里插入图片描述

我们把五毛11改为五毛12,就确定了CE搜到的怪物名字的地址。

我们再搜一下存放这个名字的地址:

在这里插入图片描述

在这里插入图片描述

这样就找到了它的第一个偏移+5C,然后我们可以继续搜esi的值,也可以显示反汇编程序看一下附近有没有更上层的偏移:

在这里插入图片描述

在这里插入图片描述

可以看到它有另外一个偏移+964。

在这里插入图片描述

接下来我们用xdbg接着分析:

在wow.exe后面加一个点和一个数字零,即wow.exe+32A27F就可以在xdbg中直接转到该地址处:

在这里插入图片描述

我们打开内存2窗口,转到esi的地址处:

在这里插入图片描述

如上图所示复制esi所在地址处开始的内容,这是一个很重要的特征,esi此时的地址值里面保存的内容A34D90是判断它是否是一个对象的关键,虽然它不能用来判断是否是怪物对象,但是如果A34D90是虚函数表vftable的地址,那么这个esi大概率就是一个对象;
因为对象一般它的首地址大概率存在虚函数表vftable,当然不是绝对的,只不过游戏里面怪物对象或者游戏对象的首地址如果是一个虚函数表vftable的话,那么它们就是一个对象,到底是不是,我们在xdbg中内存3这里查看分析一下:

在这里插入图片描述

在这里插入图片描述

可以看到这两个地址都是一个标准的函数头。

在这里插入图片描述

在这里插入图片描述

这样就能够确定A34D90就是一个虚函数表vftable,或者说是虚函数指针。

在这里插入图片描述

这样的话,我们就得到了怪物名字的两个偏移了。

通过这个虚函数指针确定它是一个对象,我们就不用继续找了,找这两个偏移就可以了,一个+964,一个+5C,最终就可以读出怪物名字了。

一定不要去看人物头顶上的名字,否则你是找不到的,要看面板上的信息。


用CE和xdbg分析对象数组

在这里插入图片描述

认识虚函数表

在这里插入图片描述

创建基于对话框的MFC应用。

我们在项目文件目录中新建一个怪物类的目录(筛选器),创建一个怪物的类,用来模拟怪物对象:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

当然这个模拟的怪物数组对象和我们游戏里的不同,因为它直接就是一个全局变量,它没有偏移,直接就是一个基址。

在这里插入图片描述

用x32dbg打开该exe文件,转到MessageBeep函数那里下断,运行后点击按钮就会中断在这里:

在这里插入图片描述

在双击右下角堆栈窗口中的返回地址,就可以返回到我们代码那里了:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里直接就能看到怪物数组,在内存窗口中我们看一下怪物数组的对象,它首地址里面有一个注释vftable(当然游戏中是没有这个注释的),实际上就是虚函数表指针:

在这里插入图片描述

通过这个虚函数表指针0132898C,进到该地址处才是虚函数表:

在这里插入图片描述

这里面每一个其实都是一个函数的地址:

在这里插入图片描述

这里面大部分都是我们继承的CDialog基类里面的成员函数,这是我们为了模拟分析虚函数表才选择它来继承:

在这里插入图片描述

某一种怪物它们的虚函数指针一般都是相同的,所以说我们还可以用这个虚函数表来区分不同类型的对象,比如说玩家的和怪物的虚函数表指针有可能不同,如果它们真是同一个怪物类的话,那有可能就是相同的,而且很多功能也可能在这个虚函数表里面。

这对我们分析对象是有很大帮助的,因为一旦我们得到类似地址进去之后,就能发现这可能是一个对象的头部,因为这种对象的头部可能会存在类似这种一大片的函数指针。

我们来看一下游戏中的玩家对象(角色对象):

在这里插入图片描述

在这里插入图片描述

现在eax就是我们的角色对象,我们转入00A326C8里面看看,因为理论上来说00A326C8就是所谓的虚函数表指针:

在这里插入图片描述

在这里插入图片描述

这些每一个都是一个函数的头部,所以说,我们在区分对象的时候,如果它的头部是一个虚函数表指针,那么它具有这种非常明显的特征,而00A326C8就是虚函数表地址(也就是虚函数表指针),而且我们看到角色对象里面的成员函数有很多,有的是用来绘图的,有的是用来获取某些数据的(血量、属性等)。

我们要看到对象具有这种特征,我们这节课主要就是要了解怪物对象或者是玩家对象,如果它是一个对象,它的第一个地址进去就是一片函数,这样能够让我们确定找到的这个东西它是一个对象,比如说怪物对象或者玩家对象。

你就简单的知道,对象的第一个地址进去,里面的一大片全部都是函数的地址,就可以确定它就是一个对象;
就是说,这个指针可以帮助我们确定我们找到的这个是对象的首地址,因为对象的首地址它具有这个特征。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个0x1372C88是怪物数组的基址,0x1D0是怪物类的大小。

在这里插入图片描述

在这里插入图片描述

我们从上面两张图可以看到,怪物数组[0]和怪物数组[3]这两个对象,它们具有相同的虚函数表指针0136898C,因为它们是同一个类。

在这里插入图片描述

在这里插入图片描述

我们看到这些对象虚函数表指针下面都是大片的0,这是因为我们没有为该对象里面的成员写数据,我们给其中某些成员赋值来对比看看:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里简单介绍一下xdbg条件断点:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

条件断点一般是下载频繁断下的地方,例如窗口过程,还可以结合逻辑与&&、逻辑或||、逻辑非!来添加多个判断条件。

分析对象数组

在这里插入图片描述

我们就从0x60C1F0进去用xdbg分析怪物对象数组。

在这里插入图片描述

我们要分析怪物数组的话,可以从怪物的名字去找怪物对象,这样慢慢的追到怪物数组,另外我们也可以通过玩家对象的来源去追这个怪物数组。
前面我们也说了,大部分游戏中怪物和玩家对象、NPC、掉落的地面物品,一般都是放在同一个数组或者链表里面的,所以说玩家对象是从0x601CF0这个CALL里面返回的,但是这个玩家对象是不是也包含在怪物数组里面呢。
所以我们可以从玩家指针的来源去找这个怪物数组,所以我们要从0x60C1F0这个CALL里面来找。

打开角色信息,断在该CALL开头,我们一直按F8看看最后返回的eax是从哪里来的:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

如果是我们玩家对象,就会有一个明显的特征,就是[玩家指针+0D0]+174是否是玩家的护甲值,所以返回值eax就是从004D4DB0这个查询对象CAL里面来的,现在我们基本上就能够确定这个CALL就是玩家对象的来源,也就是说更深层的CALL就是从004D4DB0里面来返回的我们角色对象。

在这里插入图片描述

该玩家对象首地址中的值00A326C8其实就是之前讲的那个虚函数表首地址,00A326C8里面存放的全都是独立的一个一个的函数:

在这里插入图片描述

我们到4D4DB0那个位置去看看究竟返回值eax是从什么地方返回的:

在这里插入图片描述

我们之前分析过4D4DB0这个CALL的参数,我们怀疑第1个和第2个参数是ID1、ID2,用来查询那个对象数组的,通过这两个参数来查询返回的对象,也就是说通过这个CALL应该也能返回其他的怪物对象。
我们继续往该CALL里面跟一下:

在这里插入图片描述

在这里插入图片描述

可以看到玩家对象是从4D4BB0这个CALL里面来的,所以说关键的那个数组可能还在这一层CALL里面。

在这里插入图片描述

在这里插入图片描述

但是之前我们说过这个CALL,它是一个非常干净的一个CALL,里面没有再调用其他函数,可以从上图看到这里面有一个循环,这就有很大的概率是在循环的做一个遍历。

我们先来看一下4D4BB0这个CALL的参数:

在这里插入图片描述

第1个参数是0xC,第2个参数edx=039FF6B0是ebp-8的地址,从内存窗口转到该地址(如上图所示的039FF6B0),可以看到里面存放的是一个0xC和一个0。

在这里插入图片描述

在4D4BB0函数尾部下断,然后不断点运行,观察发现返回值eax大部分时候不变,但偶尔会变,我们来看看[2CFD05C8+D0]+174里面是什么(如上图左下角内存窗口中的内容),可以看到这个地方明显就不是护甲值了,可能是一个其他的什么东西,我们把这个对象和玩家对象里面的数值做一个比较。

我们在4D4BB0函数尾部这个地方下断,主要是怀疑从这个地方会返回怪物对象或者是其他的对象,但是我们不能直接在4D4BB0这个地方下断,否则游戏会一直断下直到卡死掉,所以需要在上一层下断,然后再在这里下断,按运行断到这个地方。

在这里插入图片描述

在这里插入图片描述

我们在内存窗口中转到[eax+D0]+174来看看,如果是玩家的话这个地方就会是护甲值,此时证明2C318C50是角色对象,如果返回的是其他值的话可能就是怪物对象了:

在这里插入图片描述

我们发现如果不是玩家(此时eax=2D4E79F0,而[2D4E79F0+D0]+174地址处的内容如上图所示为0),这个地方就没有护甲值。

我们再来对比角色对象2C318C50和另一个对象2D4E79F0在内存窗口中的内容:

在这里插入图片描述

在这里插入图片描述

我们可以看到不同对象的虚函数表指针也是不一样的,说明它们属于不同的类型,那么这个对象2D4E79F0究竟是什么呢?
我们之前分析了怪物的名字(/run print(UnitName("target"))),当时我们分析到了两个偏移,+964和+5C:

在这里插入图片描述

我们用这个公式[[eax+964]+5C]来看看这个对象2D4E79F0有没有怪物名字:

在这里插入图片描述

这就确定了这个对象2D4E79F0是怪物对象,所以从4D4BB0这里返回的一个是角色对象,另外一个是怪物对象,当然可能也有其他的对象(坐骑、地面物品、矿物药草之类的),究竟还可以返回哪些类型的对象就需要进一步的分析,从目前来看至少有这两种对象。

接下来我们就要看看这个怪物对象或者玩家对象究竟是从4D4BB0这个CALL里面的什么地方来的:

在这里插入图片描述

我们可以看到ecx是很明显的一个关键,这个edx应该是数组的基址,这个eax应该是某一个数组的下标。
那么ecx+1c是什么呢?我们返回上一层看一下这个ecx的来源:

在这里插入图片描述

从上图我们可以看到ecx最终来源于ds:[edx+8],而edx的来源又与eax这个数组下标相关,eax的来源ds:[D439BC]的内容从左下角内存窗口中看到是0,所以edx=[ecx+eax4]=[ecx+04]=[ecx],所以关键又回到这个ecx=fs:[2C]上了,这个fs:[2C]之前我们也讲过,你就把它看成是一个普通的内存地址就可以了(这个线程的TEB在内存中映射的地址),我们把fs:[2c]代入公式里面,所以edx=[ecx]=[fs:[2c]],即最终ecx=[[fs:[2c]]+8]就得到了一个新的表达式了。

在这里插入图片描述

我们来验证一下:

在这里插入图片描述

内存窗口中我们通过表达式[[fs:[2c]]+8]转到的地址处是136FC3A0,而ecx寄存器的值也正好是136FC3A0。

我们先进4D4BB0这个CALL里面,我们用一个等价替换,把下图中的eax=[ecx+24]用ecx的表达式[[fs:[2c]]+8]替换,得到eax=[[[fs:[2c]]+8]+24],我们跟进4D4BB0这个CALL里面来验证一下(记得要断在这里,才能在游戏的主线程中,得到的fs:[2c]才是正确的):

在这里插入图片描述

可以看到eax的值为1F,而公式[[[fs:[2c]]+8]+24]的值如上图所示确实也是1F,就证明这个公式是正确的,我们接着往下执行:

在这里插入图片描述

证明edx=[ecx+1c]=[[[fs:[2c]]+8]+1c]这个表达式也是正确的,这里的这个edx就是比较关键的地方,就是那个数组的地址了。

我们继续往下看:

在这里插入图片描述

eax的值1F的十进制是31,从这里and eax, esi 来看这个31可能就是最大的数组上限,它把多余的超出31的那部分值给截断掉(例如上图我们把esi=0000000c修改为esi=1234567c,以验证and eax, esi这条指令的效果),最终得到eax=c,来保证是在数组元素个数范围内。
(验证完记得把esi的值恢复为0000000c保证程序的正常环境)

在这里插入图片描述

我们继续来做等价替换:

在这里插入图片描述

在这里插入图片描述

在执行完and eax, esi指令后eax的值为0c,所以实际上eax就是传入的参数1了(eax有可能是下标):

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上图这个公式可能就是最终的怪物数组了,我们把参数1替换为0,最终的公式就是[[[[fs:[2c]]+8]+1c]+8+0*0c],计算得到2D4F5960,而2D4F5960里面的内容是:

在这里插入图片描述

这确实是一个对象,这就证明了我们这个公式确实能取得数组里面的对象。

我们继续往下看代码:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

发现eax在下面的代码中没有被重写过,所以最终返回的就是这个eax的值。

我们再来试一下怪物的名字(名字的偏移是+964,再+5c):

在这里插入图片描述

如果是怪物那么就能读出怪物的名字,如果是玩家或者其他类型的对象,那么有可能是读取不出来的,从上图计算得到的数值可以看到目前是能够正确读取出来,这个索引为0的数组中第一个元素可能就是我们的怪物对象。

在这里插入图片描述

在这里插入图片描述

记得要断在这里,才能在游戏的主线程中,得到的fs:[2c]才是正确的,才能通过上述公式得到正确的结果(否则是访问不到的,必须是在主线程中才行)。

在这里插入图片描述

下标改成2我们就可以看到,这里可能也是一个对象,但是名字显示不了(该对象没有名字或者名字不在这个偏移位置),能够确定它不是怪物对象,我们修改公式来看看它是个什么对象:

在这里插入图片描述

在这里插入图片描述

看起来像是我们的玩家对象,如果是玩家对象的话,我们修改公式来查看玩家的护甲值:

在这里插入图片描述

在这里插入图片描述

结果看不到护甲值,说明这个对象既不是我们的玩家对象,也不是怪物对象,可能是其他类型的对象。

在这里插入图片描述

在这里插入图片描述

我们发现用该公式(此时当索引为2)还能取得NPC的名字,说明NPC和怪物可能是同一个类,为了证明这一点,我们查看一下对象的虚函数表指针:

在这里插入图片描述

在这里插入图片描述

上图这个A34D90是NPC的虚函数表指针。

在这里插入图片描述

我们知道索引为0的是怪物的,而它的虚函数表指针也是A34D90:

在这里插入图片描述

说明怪物和NPC是同一个类。

除了索引0和索引2,剩下的都不是,直到索引为1A的才有内容:

在这里插入图片描述

在这里插入图片描述

索引为1A的对象是无名这个NPC,该对象的虚函数表指针也是A34D90。

在这里插入图片描述

我们分析出来的这个怪物列表其实还不全(涉及到链表),这里我们先讲它的一部分,后面我们还会继续详细化全面分析怪物列表。
代码下面还有一个遍历链表的循环,里面应该还包含有其他的怪物对象,通过这个循环去进行遍历的:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个fs:[2C]是一个特殊的内存地址,也就是这个线程的TEB在内存中映射的地址:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


分析对象数组链表部分

在C++环境里面链表是怎么创建的,而且看一下链表的数据在内存里面是一个什么样的存放方式。

链表的定义

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

链表的数据在内存里面究竟是一个什么样的形式

在这里插入图片描述

// CreateList.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>

//    操作系统 win7 64
//    编译环境 Visual Stuido 2017

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>

typedef int ElementType;        //    定义数据类型,可根据需要进行其他类型定义
//class								//    链表节点的定义
typedef struct ListNode {
	ElementType  Element;        //    数据域,存放数据
	int data1;
	int data2;
	ListNode* Next;        // 链表指针   指向下一个链表节点
	float x,y,z;
}Node, *PNode;

//    链表创建函数定义
PNode CreateList(void) {
	int len;    //    用于定义链表长度
	int val;    //    用于存放节点数值
	PNode PHead = (PNode)malloc(sizeof(Node));    //    创建分配一个头节点内存空间//头节点相当于链表的哨兵,不存放数据,指向首节点(第一个节点)
	if (PHead == NULL)    //    判断是否分配成功
	{
		printf("空间分配失败 \n");
		exit(-1);
	}

	PNode PTail = PHead;    //    链表的末尾节点,初始指向头节点
	PTail->Next = NULL;    //    最后一个节点指针置为空
	printf("请输入节点个数:");
	scanf_s("%d", &len);        //    输入节点个数
	for (int i = 0; i < len; i++) {

		PNode pNew = (PNode)malloc(sizeof(Node));    //    分配一个新节点
		if (pNew == NULL) {
			printf("分配新节点失败\n");
			exit(-1);
		}
		printf("请输入第 %d 个节点的数据:", i + 1);
		scanf_s("%d", &val);    //    输入链表节点的数据

		pNew->Element = val;    //    把数据赋值给节点数据域
		PTail->Next = pNew;    //    末尾节点指针指向下一个新节点
		pNew->Next = NULL;        //    新节点指针指向为空
		PTail = pNew;    //    将新节点复制给末尾节点        
	}
	printf("创建链表成功\n");
	return PHead;    //    返回头节点
}

//    主函数 
int main() {
	PNode List = CreateList();    //创建一个指针,使其指向新创建的链表的头指针  
	printf("List链表头=%p\n", List);
	getchar();
	return 0;
}

在这里插入图片描述

在这里插入图片描述

可以看到List这个链表头里面的数据是未初始化的,它只有Next被初始化为指向首元结点,而首元结点的Element的值就是我们刚才输入的111这个数据,而这个首元结点的Next展开就是指向第二个结点:

在这里插入图片描述

在这里插入图片描述

最后,当Next指针指向NULL的时候,我们遍历到这里的时候整个链表的遍历就结束了。

接着我们在xdbg中分析链表的反汇编:

在这里插入图片描述

添加MessageBeep(1);用来方便定位。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们在内存窗口中查看链表头7BCAE0里面的内容,发现都是一些未知的数据,我们要找到链表头中的指针域:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所以第1个结点的地址就在+C偏移的指针域位置,它的地址就是7BF950:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所以第1个结点中偏移+C位置的值7BF9A8就是第2个结点:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这就是链表的数据结构在内存里面的存放方式。

在这里插入图片描述

上图这是个遍历对象链表的循环,游戏里面采用了这种链表的结构,我们之前讲过的对象数组,里面的元素是一个类对象,该对象里面的成员有可能是一个链表,这么说吧,就是说怪物对象里面有一个成员就是那个链表头。

在这里插入图片描述

在这里插入图片描述

头结点的指针域Next指向第一个结点(数据域中的数值为3),第一个结点的指针域Next指向第二个结点(数据域中的数值为2),第二个结点的指针域Next指向第三个结点(数据域中的数值为1),第三个结点的指针域Next指向NULL,遍历到这里就结束。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1046893.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实现自动化获取1688商品详情数据接口经验分享

获取电商平台商品详情数据&#xff0c;主要用过的是爬虫技术&#xff0c;过程比较曲折&#xff0c;最终结果是好的。我将代码都封装在1688.item_get接口中&#xff0c;直接调用此接口可以一步抓取。 展示一下获取成功示例&#xff1a; 1688商品详情页展示 传入商品ID调用item…

Typora安装无需破解免费使用

Typora简介&#xff1a; 在介绍Typora软件之前&#xff0c;需要先介绍一下MARKDOWN。 MARKDOWN是一种轻量型标记语言&#xff0c;它具有“极简主义”、高效、清晰、易读、易写、易更改纯文本的特点。 Typora 是一款支持实时预览的 Markdown 文本编辑器。它有 OS X、Windows、…

WebDAV之π-Disk派盘 + 墨阅

墨阅是一款专注于帮助用户离线缓存网页文档图书漫画的免费工具APP。您可以利用墨阅收集来自互联网网站平台的公开文章,图片,漫画等,可以对网页样式进行调整,支持自定义动作,批量离线等功能方便用户日常离线。目前支持小说,markdown,图片,pdf,网页等离线功能。支持进行…

在比特币上支持椭圆曲线 BLS12–381

通过使用智能合约实现来支持任何曲线 BLS12–381 是一种较新的配对友好型椭圆曲线。 与常用的 BN-256 曲线相比&#xff0c;BLS12-381 的安全性明显更高&#xff0c;并且安全目标是 128 位。 所有其他区块链&#xff0c;例如 Zcash 和以太坊&#xff0c;都必须通过硬分叉才能升…

忍不住分享,这个卧室太好看了。

&#x1f4dd;项目信息&#x1d477;&#x1d493;&#x1d490;&#x1d48b;&#x1d486;&#x1d484;&#x1d495; &#x1d48a;&#x1d48f;&#x1d487;&#x1d490;&#x1d493;&#x1d48e;&#x1d482;&#x1d495;&#x1d48a;&#x1d490;&#x1d48f;…

科东软件2023上海工博会:一场科技盛宴的完美收官

9月23日&#xff0c;为期5天的中国国际工业博览会&#xff08;下称“工博会”&#xff09;在国家会展中心&#xff08;上海&#xff09;圆满落幕。这是一场集结全球创新力量与科技创新成果的璀璨盛宴&#xff0c;也是推动未来科技与产业发展的新型工业盛会&#xff0c;更是一次…

多线程(概念介绍)

概念 首先&#xff0c;我们引入一些基本的概念&#xff0c;并结合我们以前所学过的知识&#xff0c;初步对这些概念有个大体的理解 1.线程是一个执行分支&#xff0c;执行粒度比进程更细&#xff0c;调度成本更低 2.线程是进程内部的一个执行流 3.线程是CPU调度的基本单位&…

PLSQL使用技巧

连接配置 先找到配置文件tnsnames.ora地址 我的是这个&#xff08;仅供参考&#xff09;&#xff1a;D:\oracle\product\10.2.0\client_1\NETWORK\ADMIN\tnsnames.ora IC (DESCRIPTION (ADDRESS_LIST (ADDRESS (PROTOCOL TCP)(HOST 127.0.0.1)(PORT 1521)))(CONNECT_DATA…

易基因:ChIP-seq揭示组蛋白修饰H3K27me3调控高温下棉花的雄性不育机制|Plant Com

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 气候变化导致极端天气事件更加频繁地发生&#xff0c;包括反常的高温&#xff08;high temperature&#xff0c;HT&#xff09;&#xff0c;HT胁迫对作物的生长发育和产量有严重的负面影…

只需一个简单操作,保障企业电力供应安全性!

随着现代社会对电力供应的不断依赖&#xff0c;不间断电源&#xff08;UPS&#xff09;系统已经成为各种行业的关键基础设施之一。然而&#xff0c;UPS系统的性能监控和管理同样至关重要&#xff0c;以确保它们在需要时能够如期发挥作用。 客户案例 广东某公司是一家制造企业&a…

[python 刷题] 11 Container With Most Water

[python 刷题] 11 Container With Most Water 题目&#xff1a; You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]). Find two lines that together with th…

如何快速学习AdsPower RPA(2)——中级、高级部分

Tool哥继续给大家分享快速学习AdsPower RPA的方法。上一篇在这里&#xff0c;还没看过的小伙伴赶快补课去&#xff1a;如何快速学习AdsPower RPA&#xff08;1&#xff09;——简单、进阶部分 能进入到中级、高级阶段的学习&#xff0c;说明你自学能力超强&#xff01;只要跟着…

联想详解AI导向基础设施 “软硬一体”赋能四大场景

9月25日&#xff0c;联想在杭州举办以“全栈智能 全程陪伴”为主题的新IT思享会&#xff0c;集中展示了联想基于新IT架构的全栈智能产品与服务&#xff0c;引领行业智能变革的强大实力。 当前&#xff0c;以ChatGPT为代表的AI模型席卷全球&#xff0c;不仅实现了AI技术质变性突…

在 Kubernetes 环境中实现证书管理的自动化

原文作者&#xff1a;Jason Schmidt - F5 NGINX 解决方案架构师 原文链接&#xff1a;在 Kubernetes 环境中实现证书管理的自动化 转载来源&#xff1a;NGINX 中文官网 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.cn 有效的 SSL/TLS 证书是现代应用环境的核心要求。但…

Unity制作旋转光束

Unity制作旋转光束 大家好&#xff0c;我是阿赵。 这是一个在很多游戏里面可能都看到过的效果&#xff0c;在传送门、魔法阵、角色等脚底下往上散发出一束拉丝形状的光&#xff0c;然后在不停的旋转。 这次来在Unity引擎里面做一下这种效果。 一、准备材料 需要准备的素材很简…

Django之视图

一&#xff09;文件与文件夹 当我们设定好一个Djiango项目时&#xff0c;里面会有着view.py等文件&#xff0c;也就是文件的方式&#xff1a; 那么我们在后续增加app等时&#xff0c;view.py等文件会显得较为臃肿&#xff0c;当然也根据个人习惯&#xff0c;这时我们可以使用…

Linux下ebtables和iptables

ebtables Ebtables has three tables: filter, nat and broute. The broute table has the BROUTING chain.The filter table has the FORWARD, INPUT and OUTPUT chains.The nat table has the PREROUTING, OUTPUT and POSTROUTING chains. iptables Tables↓/Chains→PREROU…

线上展示越发流行的今天,数字展厅发展有哪些趋势

引言&#xff1a; 二十一世纪的今天&#xff0c;数字化技术在不断的进步&#xff0c;数字展厅也逐渐出现在人们眼前&#xff0c;并成为了现代展示和推广的重要工具。 一&#xff0e;快速了解数字展厅 数字展厅是一种利用数字技术创建的虚拟展示空间&#xff0c;它通过虚拟现实…

Go-Ldap-Admin | Ldap 同步钉钉、企业微信、飞书组织架构实践和部分小坑

目录 一、Docker-compose快速拉起demo测试环境 二、原生部署流程 安装MySQL&#xff1a;5.7数据库 安装openLDAP 修改域名&#xff0c;新增con.ldif 创建一个组织 安装OpenResty 下载后端 下载前端 部署后端 部署前端 三、管理动态字段 钉钉 企业微信 飞书 四、…

GPT如何避免从入门到放弃(一)——认识GPT

第一讲&#xff1a;认识GPT GPT的全称&#xff1a;Generative Pre-trained Transformer——生成式 预训练 变换模型 GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种基于Transformer架构的大型语言模型。它由OpenAI开发&#xff0c;并在不同版本中不断…