时间如梭,岁月似箭啊!~ 转眼又到一年之中最难熬的6 、7月份,最近几个月沉迷游戏虚度光阴 惭愧啊!
原理扯淡:
多说无益,我们直接进入主题。
在过去的几篇笔记里,我们通过利用系统发包函数找Call入手,逐渐理解与实现了如何通过自己发送封包实现功能 并且 避过游戏检测的方法。
理论上其他所有功能都应该通过自己发送封包实现,但是凡事都有例外—— 寻路Call
寻路Call的重要性无需多言,无论是自动打怪 还是 主线脚本 寻路Call都是移动角色的最佳选择。
那为什么不能我们自己去发包实现寻路功能呢?老办法hook明文封包,尝试捕捉一下寻路的封包。(HOOK输出明文包 上篇笔记已分享)
然后机会发现非常奇怪的事情,它并不像我们之前捕捉的吃药包那样 吃一个药 一个包,发一个包 吃一个药,而它则是在寻路的过程中 不间断有规律的发送 包长为0x20的封包,而且看起来它们都没啥区别。
其实它们都不是寻路包,或者说根本没有所谓的寻路封包。
因为比起吃药Call 或者说 释放技能Call,这种正常的功能Call 寻路Call说它是一种功能 更不如说它是一套算法,原因在在于寻路Call本身并不发包!
既然它不发包,那怎么实现功能的?难道服务器上没有角色位置的坐标么?那我们不是可以实现很多变态?
要想了解寻路Call,我们首先需要了解走路Call,顾名思义走路Call 就是游戏里用来移动角色的功能Call,一般点击自己附近的地板 游戏就会调用走路Call使人物直线移动到点击的目的地。
比起寻路Call 走路Call就是个铁憨憨——只能走直线,如果目的地于自身中间有一块障碍物....就会...
角色就会憨在原地不动,如果是点击小地图 调用寻路Call 角色则会绕过石头到达目的地。(当然不一定点击地板就是调用走路Call,小地图就是寻路Call,主要看游戏是怎么设计的,为了更好的体验可能都是寻路Call,我们需要根据实际情况去揣测它)
那寻路Call 咋这么能呢? 它怎么实现曲线移动的?首先,我们需要了解一些小学数学知识——曲线可以看作是由无数条直线组成的,或者说可以拆解成无数条直线!
而寻路Call就是一个 根据地图障碍物与目的地坐标 计算出一条路线,这条路线尽可能由较少的直线组成 且 较短; 然后把每条直线尾端坐标保存在一个数组中,依次调用走路Call传入这些坐标,就达成了绕过障碍物 角色曲线移动的效果。
现在我们为什么要去寻找寻路Call的原因已经明了了,原来我们之前捕捉到的那些包长0x20的封包 全部是走路包,我们大可分析走路包 然后自己发送实现走路功能,可大部分情况下只能直线移动的走路功能 根本无法满足我们的需求,而我们自己实现寻路的话 还需要逆向地图中的障碍物 并实现寻路算法 这未免有些困难,得不偿失 还不如直接调用寻路Call。
说起找Call 你肯定想起了之前分享的如何断点游戏发包 定位功能Call的笔记了,但是寻路Call本身并不发包,再想使用这个快捷的方法可能就不快捷了,所以今天来认识另一种方法—— 关键数据定位功能Call。
何为关键数据? 一个实现功能的函数,里面肯定会访问一些游戏数据(毕竟程序就是数据+算法嘛)同时既然有访问 那么在数据上下访问断点就可以断到对应的函数里面,而我们根据功能Call(函数)的作用,去猜测它一定或者说可能使用到数据或参数——这就是我们要找的关键数据!
那寻路需要什么数据?很好猜,寻路嘛 无非就是移动角色到指定目的地,那目的地坐标它肯定是需要的吧? 不如它怎么知道要到哪去?
那么如何找到这个目的地坐标数据呢?找数据突破口 通常使用CE工具来扫描数值非常的方便。
卵蛋实战:
打开我们的老朋友私服版幻想神域2,服务器游戏都在咱自己电脑上 不会有更新给我们造成困难非常方便练习。
以上游戏我们先随便点一次小地图进行一次寻路:
也许在寻路过程中我们不知道目的地在哪,但是等到寻路结束之后我们肯定知道了,因为我们就站在目的地上。
打开CE以X为标准搜索348 - 368之间的单浮点数,一般来说坐标一般都是浮点数 而寻路这种函数一般不会非常精确 可能到一定范围就停下了,所以我们放宽一些范围搜索。
12万多个可能,不要紧 我们继续重复刚才的过程。同时点小地图开始寻路目的地坐标变化,可以搜索变化的;寻路中或者到达目的地未继续寻路,可以搜索未变化的,以此加快筛选速度。
十几秒的事情,我们最后发现还剩下5个左右的数值 实在筛选不掉了,只要一寻路它们马上一起变化 而且都是一样的数值,分不清那个才是真的 没其他办法了 一个一个下断试试呗~
打开OD附加游戏(下断反汇编分析个人比较喜欢用OD,CE完全也可以看个人喜好),先从第一个开始测试吧,Command里DD它,下硬件访问DWORD断点。
哪我们要找的Call在哪呢?既然这是关键数据那么本层就应该是寻路Call内部,我们Ctrl+F9返回上一层分析即可。(返回之前别忘记把硬件断点给删除了)
按照道理来说它应该就是我们要找的寻路Call了,但是要问真假试试便知,使用老伙计代码注入器——我们注入一段汇编代码调用一下这个Call试试。(详细的使用方法可以看看之前的几篇笔记,这里就不过多介绍和演示了)
可惜的是注入进去调用Call之后...角色它不动,不过小地图上却出现了一个寻路位置的点,我想这应该不是寻路Call,而是在小地图上画点的Call。
那么下一个吧,和上面同意的操作一样——下断、Ctrl+F9返回上层调用处。
这次我们运气就比较不错了,注入调用代码后 角色马上就开始寻路了。
没错这个Call确实可以使用,但是一般不建议在代码封装使用它。因为游戏里面应该还有几个它的封装函数,更加封装的原则越外层的函数 往往越简单越容易调用,这样我们调用也好简单一些,同时调用最外层的参数会更接近游戏自然调用一些,理论上更不容易吃满检测。
很简单几个Ctrl+F9就找到最外层了(具体方法可以参考系列第一篇笔记,利用发包找Call的那篇)
这个Call确实比之前那个简单多了,开头3个push参数固定常量我们不用管,第4个push我们可以看到是一个堆栈地址([ebp-0x1C]) 进去一看是目的地坐标结构体,第5个push 断下看它的值一直是2 我想它应该是一个地图ID,毕竟寻路需要根据地图障碍物计算路线,这种小值还不是常量一般都是ID或者操作方式之类的。 最后它调用了一个函数以获取ecx的值,下断查看这个函数没有参数 可以不用理会。
参数分析完成了,接下来就可以把它封装到我们自己的代码里面。
代码参考:
- bool T人物属性::F寻路(float x, float y)
- {
- this->GetData();
- DWORD ndx = x;
- DWORD ndy = y;
- DWORD nd结构[4] = {0,0,ndx ,ndy};
- nd结构[0] = *(DWORD*)&x;
- nd结构[1] = *(DWORD*)&y;
- DWORD nd结构W = (DWORD)nd结构;
- __try
- {
- //逆向发现地图ID挂在人物对象下
- DWORD nd地图ID = *(DWORD*)(this->nd对象 + 0xC);
- nd地图ID = *(DWORD*)(nd地图ID + 0x3A4);
- __asm
- {
- push 0x1
- push 0x0
- push 0x0
- push nd结构W
- push nd地图ID
- mov ecx, dword ptr ds : [0xF84B74]
- mov eax, 0x006D8340
- call eax
- mov ecx, eax
- mov eax, 0x006F0D60
- call eax
- }
- }
- __except (1)
- {
- F输出调试信息("幻想神域 bool T人物属性::F寻路(float x, float y)异常 \r\n");
- return false;
- }
- return true;
- }
复制代码
|