文章目录前言 对于游戏逆向来说,核心需求其实就只有两个
对于追踪游戏数据来说,单纯从一个寄存器或者内存往上追踪到这个数据的基址是较为简单的,难点在于如何通过这一个数据,找到整个数据结构。
这里先抛出三个问题
- 对于人物背包来说,可以通过背包物品的数量所在的地址追踪到基址,但是怎么通过物品数量找到整个背包的数据及其附带属性?
- 对于人物血量来说,可以通过血量所在地址追出一个基址,但是怎么通过血量的地址从而追到整个人物的属性?
- 同样对于人物血量来说,怎么通过人物血量找到周围所有事物包括NPC 周围怪物等等的属性及坐标?
想要解决上述的问题,就必须学会如何逆向分析程序中的数据结构,上述三个问题,对应了三种数据结构,分别是数组 链表和二叉树。这三个数据结构也是游戏里最常用的。
我们先用口袋西游,来学习一个简单版本的数组。
逆向背包数组一维背包数组 这一次我们要逆向的目标是整个背包的物品,通过数据追踪的方式找到所有的背包物品及属性。切入点可以是背包中任意一个物品的属性,比如数量。
008 数据结构逆向—数组(简单版)
首先搜索背包中血药的数量
008 数据结构逆向—数组(简单版)
第二次搜索即可得到唯一一个结果,然后在OD中对这个地址下硬件写入断点,断点断下
008 数据结构逆向—数组(简单版)
断下的位置上一句就是写入的代码,[ecx+0x14]是当前物品的数量。那么我们就要往上追ecx,由于这个位置已经是函数头的位置了,我们需要返回上一层继续找ecx
008 数据结构逆向—数组(简单版)
ecx来源于esi,而esi来源于[eax+ebx*4]。这是一个典型的数组下标访问的代码。C++代码如下:
int arr[10];
DWORD iNum=a[i];
eax相当于是arr数组的首地址,ebx相当于是数组的下标,通过对下标取不同的值可以访问到不同的数组成员。
接着我们在这个位置下断点,观察eax和ebx的值
008 数据结构逆向—数组(简单版)
ebx的值正好和当前药品所在的第几个格子数,然后再吃另外一个药。ebx的值和当前物品的背包格子数也是一样的。
也就是说整个背包的物品是用一个数组来存放的,数组的下标代表的是物品所在的格子数。
问题在于,当前的背包有三个,分别是普通 任务和时装,理论上来说应该有三个数组,那么另外两个在哪呢?继续往上追就能得到答案。
先来回顾一下当前物品的偏移表达式
然后我们继续往上追eax
008 数据结构逆向—数组(简单版)
eax来源于[edi+C],edi来源于ecx,所以
血药数量=[[[ecx+0xC]+i*4]+0x14]
返回上一层,继续追ecx
008 数据结构逆向—数组(简单版)
ecx,来源于esi
血药数量=[[[esi+0xC]+i*4]+0x14]
008 数据结构逆向—数组(简单版)
esi来源于[esp+0x1C]
008 数据结构逆向—数组(简单版)
[esp+0x1C]来源于eax,那么
血药数量=[[[eax+0xC]+i*4]+0x14]
继续追eax,eax作为返回值来源于上一个call,进入call内部
008 数据结构逆向—数组(简单版)
这个call的内部代码的分支比较多,这里可以先单步F7走一遍,然后用减号键回退的方式整理代码执行流程
008 数据结构逆向—数组(简单版)
eax来源于[ecx+0xAD8]
血药数量=[[[[ecx+0xAD8]+0xC]+i*4]+0x14]
008 数据结构逆向—数组(简单版)
返回上层ecx来源ebp,ebp来源ecx,表达式不变,继续返回上一层函数追ecx
008 数据结构逆向—数组(简单版)
ecx来源于上上层函数的eax,eax作为返回值,继续进上面的call,追eax
008 数据结构逆向—数组(简单版)
血药数量=[[[[[ecx+8]+0x28]+0xAD8]+0xC]+i*4]+0x14
二维背包数组 返回上层继续找ecx
008 数据结构逆向—数组(简单版)
这里我们又看到了一个数组的访问代码,在这个地方下个断点,发现eax的值为0
血药数量=[[[[[[ebp+0*4+1C]+8]+0x28]+0xAD8]+0xC]+i*4]+0x14
这个地方大概是一个二维数组,eax是数组下标,0代表第一个普通背包,1代表第二个任务背包,2代表第三个时装背包。
继续往上追ebp
008 数据结构逆向—数组(简单版)
ebp来源[eax+0x8],eax来源于[ebx+4],ebx来源ecx
血药数量=[[[[[[[[ecx+4]+0x8]+0*4+1C]+8]+0x28]+0xAD8]+0xC]+i*4]+0x14
继续找ecx
008 数据结构逆向—数组(简单版)
来源[edi+0x68]
血药数量=[[[[[[[[[edi+0x68]+4]+0x8]+0*4+1C]+8]+0x28]+0xAD8]+0xC]+i*4]+0x14
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cLGVTuAQ-1587393010884)(008 数据结构逆向—数组(简单版)].assets/1587390974080.png)
edi来源ecx
血药数量=[[[[[[[[[ecx+0x68]+4]+0x8]+0*4+1C]+8]+0x28]+0xAD8]+0xC]+i*4]+0x14
继续返回上层追ecx
008 数据结构逆向—数组(简单版)
ecx来源ebp
血药数量=[[[[[[[[[ebp+0x68]+4]+0x8]+0*4+1C]+8]+0x28]+0xAD8]+0xC]+i*4]+0x14
008 数据结构逆向—数组(简单版)
ebp来源ecx
血药数量=[[[[[[[[[ecx+0x68]+4]+0x8]+0*4+1C]+8]+0x28]+0xAD8]+0xC]+i*4]+0x14
008 数据结构逆向—数组(简单版)
ecx来源于[ebp+0x1C],而ebp是一个基地址,那么这个偏移表达式就已经追完了
血药数量=[[[[[[[[[[0xD11A50+0x1C]+0x68]+4]+0x8]+0*4+1C]+8]+0x28]+0xAD8]+0xC]+i*4]+0x14
数组结构分析 这里可以把整个偏移表达式分为三部分
背包物品数组首地址=[[[[[[[[[0xD11A50+0x1C]+0x68]+4]+0x8]+0*4+1C]+8]+0x28]+0xAD8]+0xC]
背包背包物品数组下标=i*4
背包物品属性偏移=0x14
接着我们在内存中查看一下数组首地址的内存
008 数据结构逆向—数组(简单版)
这里是一个DWORD类型的对象数组,每一个成员都是一个物品对象,找到第六个我们的血药的对象地址,数据窗口跟随
008 数据结构逆向—数组(简单版)
其中+14的位置是当前的血药数量,借用这个数据可以猜测一下+018的位置是3E7,十进制的999,应该是物品的最大数量。其他的每一个成员都是物品的属性
总结 到这里我们就完成了从物品数量到整个背包的数据结构分析的过程,识别数组结构的关键在于是否有汇编通过下标的方式访问内存
mov esi,dword ptr [eax+ebx+4];
这个数组结构仅仅是一个简单版本的,下次我们再来分析一个困难版本的数组。
最后,附上Github地址,里面有游戏下载链接和相关工具,需要请自取:
https://github.com/TonyChen56/GameReverseNote
|