|
前言摩尔庄园这个游戏想必很多人都听过,这是一款儿童网页游戏,我从初中就开始玩了,通过这个游戏我也结识了不少朋友,给我留下了难以忘怀的美好记忆。当时这个游戏非常火热,经常是几十个服务器爆满的状态。我记得当时我每天都会打开摩尔庄园看看摩尔时报,喂喂拉姆,种种地,钓钓鱼,完成日常任务。但后来同类型的游戏(如洛克王国、奥比岛、奥拉星等)层出不穷,英雄联盟这类大型游戏又火热起来,或许是因为这些,摩尔庄园的玩家逐渐减少。终于,摩尔庄园停止了更新,摩尔时报停在了2015年2月6日,标题是回忆庄园。现在,摩尔庄园的服务器从最多时候的一千多个变成了30个(实际上只有一个服务器,IP是123.206.131.236)且非常不稳定。我想冲个超拉祭奠一下我的回忆,可谁知充值的网页都打不开了。再这样下去的话估计离关闭服务器不远了,于是我写下这篇文章,算是和摩尔庄园做一个告别仪式,同时也希望相关游戏研发人员看到能做出相应改进。
关于网页游戏网页游戏一般值无需下载,打开网页就能玩的游戏。网页游戏的作弊一般都是模拟操作或者分析封包协议模拟封包的,因为代码是在虚拟机中运行的,内存修改很难找到基址。目前的网页游戏的框架一般是Flash,也有Unity3D的,由于Unity3D需要安装客户端或者插件,所以占极少数,也有个别网页游戏是HTML5代码写的,这种暂不讨论。摩尔庄园则是一款Flash游戏,网页属于客户端,与服务器通信就会产生封包,我们只需要构建出封包数据,就可以实现游戏内的所有动作,通过修改封包数据,还可以实现很多客户端本身没有的功能(如刷金币刷道具等)。因此网页游戏为了安全都会将封包加密再发送,而又为了减轻服务器的负担,封包的加密不会很复杂,且封包的加密算法一般都可在本地缓存的swf文件中找到。目前有许多Flash加密技术,如Alchemy、DoSwf等,但Flash类似Java,是一种跨平台,解释型语言,AS代码被编译成字节码代码运行在AS的虚拟机中,所以代码最终都会被解密。而且加密Flash的成本很高,这为攻击者带来可乘之机。
关于摩尔庄园封包加密据我所知封包加密算法更新过三次(内部小更新忽略不计)。从最初的没有验证,可以随意发送封包不掉线,到后来加入了封包序列段,从00递增到FF循环,如果封包序列不符合则断开连接。再后来序列段的递增改为加密算法,序列段的值和包体及上一个封包的序列号有关。最后,有序列段验证的同时加密了包体(封包尾部)。
加密算法寻找过程游戏有很多子类和模块,我们需要找到发送封包的函数,Flash中发送封包一般都是通过重写Socket类进行的,所以我们可以搜索这个类名(flash.net.Socket)找到最底层的封包发送类,然后通过调用关系找到封包加密的算法。
几种思路:
1.提取缓存目录中的swf文件反编译分析。
2.通过截包得到的swf文件反编译分析。
3.由于缓存中的swf很多是加密过的,我们可以在一个进程里打开游戏的网页,内存中搜索swf文件头,提取swf文件并全部保存到同一个目录中,然后批量反编译成as代码,在文件夹搜索关键字socket,定位到swf文件中分析。
经过分析找到发送封包代码所在文件:TaomeeCoreDLL.SWF 同时找到几个关键基本类:com.core.socketlogic.baseSocket.BaseSocketcom.mole.net.SocketImpl 接着我们搜索哪里实现了这个类的功能
找到了:com.core.socketlogic.baseSocket.MoleSocket 分析重载的send方法:
这个方法就是用来发送封包的,我们先来看看封包是什么样子:我们可以在游戏中执行相同动作,然后通过通过封包工具查看封包:
这个封包是多次喊话数字1的封包十六进制文本(打码部分是账号的十六进制),我们可以看出只有第5位(这里我们以一个字节一位)在变化,其他部分都一样。由此可以断定这一位就是封包验证段,也就是序列号。 我们可以从代码中分析封包的结构:
public static const HEAD_LEN:uint = 17; override public function send(param1:uint, param2:Array, param3:String = null) : void { var _loc4_:ByteArray = null; var _loc5_:uint = 0; if(connected) { _loc4_ = this.createBody(param2,param3); _loc5_ = HEAD_LEN + _loc4_.length; this._seq = this.versionEvent(param1,_loc5_,this._seq,_loc4_); _loc4_ = this.encryptBody(_loc4_,param3); _loc5_ = HEAD_LEN + _loc4_.length; writeUnsignedInt(_loc5_); MsgHead.Version = this._seq; writeByte(MsgHead.Version); writeUnsignedInt(MsgHead.Command); writeUnsignedInt(MsgHead.UserID); writeUnsignedInt(MsgHead.Result); writeBytes(_loc4_,0,_loc4_.length); flush(); this.parselogger(LoggerType.OUTPUT,param1,_loc4_.length); } }
封包一位前面是 17加上 包体长度,那么可以推测出包头的长度就是17然后是MsgHead.Version(这个来自函数versionEvent返回值,后面分析)接着是MsgHead.Command,推测是封包的命令标识,相同的动作这个是一样的,接着是MsgHead.UserID,这个也就是米米号,再接着是Result(返回码),最后是包体_loc4_,是createBody的返回值。
我们重点需要分析的是MsgHead.Version和包体的生成,因为其他的都是固定的值。从代码中可以看到MsgHead.Version的值来自this._seq,也就是上面调用的this.versionEvent(param1,_loc5_,this._seq,_loc4_);的返回值。我们分析下versionEvent这个函数:
是一个简单的加密算法,后面还调用了getCrc
也是一个简单的算法,我们可以直接转换成相应的语言调用。转换成易语言代码如图:
知道这个后,剩下的就是包体的加密了: _loc4_ = this.encryptBody(_loc4_,param3);
调用了encryptBody方法,这个方法调用了MessageEncrypt.encrypt我们找这个方法的定义:
如图,调用了com.fcc中的方法:我们定位到该方法:
发现这个旁边还有个MDecrypt,但是代码应该是混淆处理过,还有一些乱七八糟的东西,非常乱,基本无法还原。但是我们需要构建封包就必须解密封包体,然后再调用上面分析的序列号计算方法,再加密封包发送。既然无法还原代码,那我们是否能调用这个方法呢?我之前学过一点Flash,知道通过Loader类可以实现获取其他swf中的类,然后调用。于是设想使用Loader加载这个类,然后通过ExternalInterface,也就是和JS通讯的Callback方式传递参数和返回值。接下来就开始实践了。
实践LoaderFlash Builder新建一个as3项目,然后新建asstest文件夹,把这个函数所在的swf放进去:
使用Embed引入swf文件,然后写一个函数调用返回ByteArray:
定义变量:
实现Loader:
Loader加载完毕事件中,用getDefinition获取类定义,并添加callback。
getClass方法:
其中output是我加的编辑框,用于显示输出文字,方便调试。 最后导出:
测试:测试使用Flash对象加载这个swf,执行脚本的CallFunction函数调用我们添加的callback方法。测试解密方法:
测试加密方法:
都得出正常结果。 至此我们成功攻破了摩尔庄园的封包加密,之后便可以随心所欲地发送和修改封包了。
结语通过我们的分析得知摩尔庄园的封包加密目前来说还是有难度的,但部分关键算法和发送封包的方法都没有进行混淆和加密,类名和变量的命名都一览无余,甚至开发调试用的Log和Trace都没有清除,这为逆向分析者提供了极大的便利。谨以此文,祭奠我在摩尔庄园里度过的快乐童年。 声明本篇内容仅供研究学习,禁止用于非法和商业用途,由此带来的一切后果自行承担,与本文作者无关。
文章已获原作者specher同意转载,如需转载请联系原作者——公众号《Sp软件服务》
*转载请注明来自游戏安全实验室(GSLAB.QQ.COM)
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|