不知道有没有人玩过QQ堂这款游戏,这是一个08年的竞技游戏,玩家通过放"泡泡"与其他玩家进行PK,童年时代特别喜欢玩,只可惜这款游戏后来毁于辅助泛滥。时至今日,win10系统已经不兼容QQ堂这款游戏了,所以我拿了火拼QQ堂这款游戏进行逆向分析,分析当年那些让人眼花缭乱的功能是如何实现的。
通常竞技类游戏的变态功能容易实现,原因就是竞技类游戏往往追求及时反馈以及对游戏的流畅性要求高,因此很多游戏功能的实现逻辑都是放在客户端执行的,通过修改内存或者封包都很容易实现变态功能。而角色扮演类,回合制等游戏则对这些没有太过严格的追求,因此游戏功能的实现逻辑大部分都是放在服务器上运行的,所以变态功能没有竞技类游戏那么好实现,而且要实现变态功能一般都是用封包去找服务器的漏洞。
之前发过一篇"火拼QQ堂穿墙功能的挖掘",实现穿墙,有些数据下面的分析会用到,传送门:https://mp.weixin.qq.com/s/DdEpDFn3sen7Jb3rU-K3iA
这次把火拼QQ堂的人物放泡泡整个的执行流程大体分析一遍,主要是学习了一段时间后,想试试通过反汇编把游戏中某个功能的整个实现流程大体读懂,其中有分析不明白的地方,以后分析出来了会作补充。其中有分析有错误的地方,也请各路大神多多指正。
游戏中有一个内存地址记录了当前泡泡道具的数量,先用CE把这个内存地址找到,当我们在游戏里放个泡泡,游戏肯定要判断我们还能不能放泡或者能放几个泡泡之类的,总之肯定会与这个数据有一定的关联,所以我们从这个数据入手,对他下访问断点:
这条 mov edx,dword ptr [ecx+44] 就是访问当前泡泡道具的数量内存地址的代码,像[ecx+44]这样的寻址方式,这个变量应该是某个结构的成员变量,这个结构可能是人物。为什么可能是人物而不是一定,因为游戏可能把这些道具信息又单独封装成一个结构存放于人物结构之下。不过这个游戏,道具信息是直接放在人物对象下的,所以这个ecx就是人物对象地址。
在上一条代码下断点,开始分析一下这段函数。
回到游戏内放下一个泡泡,单步执行代码并且记录寄存器的数据,同时观察游戏内的数据并作比较。
单步完一遍后,记录一下[ecx+50]是0,[ecx+44]是当前泡泡道具的数量,[edx+eax*8]这个看起来像数组寻址的东西,但是eax一直是0,前面又是lea指令,所以这句代码执行完以后,eax存放的是当前泡泡道具数量。
下面[ecx+24]恒定为5,回到游戏看了一下,最多能放6个泡泡,也就是说就算你吃了7个泡泡道具,但是实际能放出来的泡泡最多只有6个,也就是说+24这个偏移是存能放泡泡数量上限的地址。但是为什么这里是5呢,先不急,再继续看。
下面[ecx+20]恒定为1,这句add eax,dword ptr[ecx+20],意思就是在当前泡泡数量+1,然后对比当前泡泡数量+1与能放泡泡上限,如果当前泡泡数量+1大于能放泡泡数量上限了,则当前泡泡数量写为能放泡泡数量上限。
继续单步,经过观察,[ecx+90]是记录当前场内已经放置了多少个泡泡,他传给ecx了,继续往下看,edx清零,然后比较eax(当前泡泡道具数量+1)与当前场内已经放置的泡泡数量(这里就是为什么[ecx+24]为5,但是能放泡泡数量的上限是6的原因),并根据比较结果设置dl,然后通过al返回结果。
下面上图,清晰一点:
所以,直接对泡泡道具数量进行修改是不能实现"无限泡泡"的,要实现无限泡泡,要从这段程序入手,简单粗暴一点,就是一直让这个函数返回1即可。
分析完这段函数,返回上一层去调用它的地方看看,应该可以看到更多的流程和细节,返回出去后:
选中的这句代码即为刚才的call,通过刚才的分析,这就是一个判断当前能否放泡泡的call,如果不能放泡泡,下面的跳转将直接跳到这段程序的结尾。
在这里下断,程序不会自动断下,只有按下空格键的时候才会走这里,我要从捕捉键盘消息的地方开始。
在这段子程序头部下断,发现是一直被断下的,也就是说这是一个不断循环的子程序。那么,判断空格键是否按下的地方肯定就在这之下,在判断能否放泡泡call之上,于是乎在0044760B处发现这是一个判断空格键是否按下的call。按下空格键时,下面的跳转将不成立,就会执行下面与放泡泡有关的代码。
继续往下分析:
这一段是执行了一个对象的虚函数,并且通过返回值判断是否还要执行下面的内容,如果此跳转成立,将直接跳出此段程序。这个[ebx+4]存放的应该是一个类似于GameManager作用的对象,里面存放了当局游戏的一些信息,因为下面分析中也发现有多处数据也是根据这个结构来访问的。经过测试,没有出现过此跳转成立的情况,也不知道什么时候才会触发,所以并没有深入分析这里。继续往下看:
由前面的分析,edi此时存放的是人物对象地址,+30是人物y坐标地址的偏移,+2C是人物x坐标地址的偏移(在"火拼QQ堂穿墙功能的挖掘"分析过的数据,传送门:https://mp.weixin.qq.com/s/DdEpDFn3sen7Jb3rU-K3iA),这里进行浮点入栈传参,然后调用了一个系统函数,od给我们注释出来了:ftol,这是浮点转换整数有关的,下面又进行了除法运算,算完后结果与游戏中的实际观察,发现是泡泡在地图数组中的"下标位置",所以这个算出来的就是"y"下标;同理,这后面也算得了泡泡的"x"下标。所以这段程序就是根据人物在地图上的浮点数坐标算出泡泡在地图数组内的整数型下标。
继续往后看:
然后y下标被传入调用这段程序的第一个参数,x下标被放入了局部变量里。将他们进行压栈后,又调用了一个成员函数,由ecx的来源可知,这是一个在[ebx+4]结构之下的一个对象的成员函数 :call 004326CB
跟进去看看:
这里大概就是在判断泡泡的位置是否合理,如果不合理,返回0,外面的跳转也就直接跳出这段放泡程序了。
回到上一层继续分析:
这里将人物对象+68取值并再加了一层偏移8c,将里面的值压栈,可知人物对象+68的地方是一个指针,存放了另一个结构的地址。
这个call没有进去分析,其返回值传进ecx里紧接着又调用了一个call,其返回值是泡泡对象的地址(因为我找过场内泡泡对象的数组,所以得以迅速分析出来,这里就不对场内泡泡对象数组做分析了)
接着下面判断泡泡对象是否为NULL,为空就跳到程序结尾。
继续分析,加油…
这里对泡泡对象+10,+14的地方分别写入了值,这两处值没分析出来是做什么用的…
接着往下看,压入泡泡的y、x下标后,调用了泡泡对象的成员函数,跟进去看看:
分析过程如图,发现这也是判断泡泡在地图数组中下标的合理性,合理的话就计算出其地图坐标。但是刚刚上面不是也有一个call是在判断泡泡下标的合理性吗?我把上面那个call下面的判断nop掉后回游戏试试:
发现可以在墙体里放泡泡,或者叠加放很多泡泡,所以第一个判断是判断当前位置是否存在障碍物,如果存在,直接跳出,这里才是在判断是否在地图内,如果合理就计算出泡泡在地图上的坐标。
继续向下看:
这里又把人物对象+68地方存着的值传入ecx,所以+68的地方应该是个指针,存放了某结构地址,然后调用了这个结构的成员函数,跟进去看看:
分析结果如图:
所以这段子程序是通过人物取到人物当前威力有关数据,那么从此得出,威力大小也有上限,单纯修改道具数值也不能实现无限威力,要实现无限威力就把这段程序的限制跳过即可。
而且可以得到人物道具信息的偏移:人物对象地址+68]+48是威力数量的内存地址,观察一下游戏中道具数量的布局:
那么泡泡道具数量的内存地址就应该是人物对象地址+68]+44,鞋子的便是人物对象地址+68]+4C了。
继续向下看,
返回结果ax写入到了esi+12的地方,esi是泡泡对象地址,所以泡泡对象+12的地方是泡泡的威力大小。
继续…
这里Lea ecx,dword ptr [edi+54] 紧接着 call 00447A4D,由于edi是人物对象地址 ,所以这里我认为他是把人物对象+54的成员变量传地址进去(或说是引用传参)而非是一个thiscall调用。进去分析看一下:
这里是一处数组遍历,也就是说人物对象+54的地方放着一个数组(数组的实质就是一片内存的首地址),这也印证了上面的猜想。
这里只知道这个数组存着一些对象,然后通过循环来调用他们的虚函数,这里没有分析出来是干什么的,因为这段循环不知道什么条件才会执行。我就直接让此call下面的判断nop掉,回到游戏发现:
放出的泡泡变成了隐身效果,所以这里是在判断人物有没有吃到那个隐身特效的道具,若是有,则给产生的泡泡添加隐身效果。
继续向下看:
这里的ebx是上面的[ebx+4],所以这里又是其结构下一个成员的成员函数,在此成员函数里看到了调用重载new等的操作,应该是在创建泡泡的ui,实现绘图之类的了。
再往下:
这里调用了一个人物对象的成员函数,跟进去分析:
代码比较简短,就是让人物在场内已放泡泡的数量+1。
继续:
到这里就是加载音频文件与播放音频文件相关的了,所以整个放泡泡的流程大概就是这样。
分析完这个流程,可以发现他并没有一个能够相对简单调用的"放泡call",要实现外部调用放泡泡,需要把这里面不可或缺的call全部提取出来,进行调用。
收获还是很多的。观察以上的流程。可以实现障碍物内放泡(配合穿墙)、泡泡隐身、无限泡泡、无限威力、全屏泡泡等等功能,还能实现什么,全凭想象力发挥了。
C++成品效果:
欢迎对逆向有兴趣的朋友加上我一起学习