远方呢 发表于 2018-9-13 11:38

AMF封包通信的游戏辅助易语言编写(逆向分析脱机教程)

本帖最后由 远方呢 于 2018-9-27 13:24 编辑

《AMF模块使用教程1―― 完整AMF封包通信的游戏辅助编写》

内容简介:
本教程针对 AMF客户端通信模块.ec 做基础及进阶性的介绍,以及对几个游戏进行破解分析过程,并使用这个模块在易语言环境下编写辅助程序作出详细说明。


第一章 破解一个Flash网页游戏
1.1找个练练手的游戏

《教程1》对于新手,简单一点的,使用完整AMF封包通信的游戏,其他使用ByteArray写对象后再通过TCPIP连接进行AMF编码字节集通信的,或FLEX外部化类编码的游戏将在《教程2》,《教程3》写。

1.2 破解人人网FLASH网页游戏《泡泡海洋》
网址:http://apps.renren.com/paopaohaiyang?origin=3871
介绍:史上鱼种最多最炫的养鱼游戏!捉鱼、养鱼、卖鱼、钓鱼…
即新版的《泡泡鱼》

FLASH网页游戏主程序一般是后缀为:.SWF的文件,我们想破解它就要先找到它。

工具准备:charles3.7_WIN32版本_带破解补丁.rar 破解DoSWF(E源码).zipASV2010 或 硕思闪客精灵(不建议这个,反编译AS3不太好,分解动画除外)UltraEditIPTOOL (层底网截)AMF完整通信字节集解释器 (自己使用AMF模块对应游戏编写)Firefox 火狐浏览器


我的运行环境windows 7sp1 IE9. Firefox v19
1.登录人人网,开启Charles,点进入应用:泡泡海洋
http://b302.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/**0y8YKc09lO3ynzVB8cuBY6DwQtubpN3da1izW9VTk!/b/dC4BAAAAAAAA&bo=rgAUAQAAAAADEI4!



2.进行一两个游戏操作,网截获一些AMF请求信息,点Charles网截程序按钮 停止记录。
http://b340.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/qfygWrpwlqExGNIZn5gTWtddSzT*OPUaTK2RZeLKRpk!/b/dFQBAAAAAAAA&bo=ugIuAgAAAAADIJE!
AMF请求指令 amfService.getUserInfoAMF amfService.getRaytoonVIPAMFamfService.getAlmanacNumDataAMF等等这些是游戏主程序向服务器发出的操作请求指令,这些字符串一般在FLASH游戏主程序.SWF内,我们找到这个.SWF文件,分析这些指令及其参数,编写程序进行一系列指令自动请求发送操作,这样就达到游戏辅助目的,俗称外挂。
3.Charles内,有两个分页:structure和sequence ,玩VC的就知道这是 结构和 列序 的意思。
4.点 sequence列序,查看有没有 .swf 后缀的文件,
http://b308.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/cYNRq22cEMwQqexhOBzfcxUQgvXGQKMTG3awiUnaTMw!/b/dDQBAAAAAAAA&bo=kgL5AAAAAAADEFw!

找不到!!!!不用心急,Charles也不是万能的,有些在网页JS脚本下的文件请求不一定能截取到。
5.查看返回网页内有没有 .swf 后缀的文件信息, 按 <Ctrl> + F,输入.swf,注意是: (小写句号swf)
http://b338.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/sewIwvzFU.ioAxlaiT1nF0R9pNzK2V5tAndb5RWNn2M!/b/dFIBAAAAAAAA&bo=rQF.AQAAAAADEOY!

点:Find
http://b309.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/PiUkKrRPpJn4eE54aPtK3Pscg5VW1tRMrqP8fSDN4.U!/b/dDUBAAAAAAAA&bo=4wH.AQAAAAADECg!

出现搜索结果框,并把这个Search Results搜索结果框移开一点不挡着Charles为好各个双击查看这些结果
http://b338.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/ODzLllyUhQSqwJfWiX64Iq2nDfTaoCeria2fduuNFwc!/b/dFIBAAAAAAAA&bo=fgKaAAAAAAADENM!

因为是Request (请求),不是我们想要的 Response(响应返回)网页,所以一样的1~4也不用看了。双击第5个
http://b311.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/AemVGbMiYKpmM*OVNBu*0VFh4buGZXERmbIOvwT85.Y!/b/dDcBAAAAAAAA&bo=SALtAAAAAAADEJI!

是我们想要的 Response(响应返回)网页了,看看定位在哪,如果不是可疑的.swf 请求文件,可双击多次这个Search Results搜索结果框的项目,因为网页内可能不只一个.swf字串,双击时可定位到该请求返回的下一个搜索到的字串上。 找到了
http://b301.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/Zq**ol1swa5OdH32OkopcTF5kuefCIId364Q.MXMAQs!/b/dC0BAAAAAAAA&bo=XQLKAQAAAAADEKE!
复制出来
看看<embed name="forIE" src="http://official.ppyimg.cn/media/swf/v30/gameMain3.swf" width="756" height="640" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" wmode="transparent" flashVars="skey=2.0f66314f891ff9ac41116b58805453e5.3600.1362121200-451237832&sns=renren&feed_fun=sendFeed&snsid=451237832" allowScriptAccess="always" ></embed> 一个长度宽度width="756" height="640"的FLASH程序,文件名gameMain3.swf够可疑吧。

6.如何把这个FLASH程序保存到电脑上分析把 http://official.ppyimg.cn/media/swf/v30/gameMain3.swf 复制,打开Firefox火狐浏览器(因为火狐浏览器打开纯.swf的链接后可以另存为保存到电脑上,IE就不行了),粘贴到火狐浏览器的地址栏,回车。加载完成后 点菜单栏 [文件] ->[页面另存为]…,
http://b306.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/O9itbdiF0gmKNDJiheL9BOnYtFMiz27yjPs706tV39I!/b/dDIBAAAAAAAA&bo=ZQEKAQAAAAADEFo!


就D:\泡泡海洋 目录吧


http://b338.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/Nd2qN9XBT7UrCZyolcsYmBlt9I2MnnL8mRZCDfpnlt8!/b/dFIBAAAAAAAA&bo=lQJIAgAAAAADEOg!


7.反编译分析 gameMain3.swf文件:
   打开 ASV2010 把 到目录D:\泡泡海洋 把gameMain3.swf 文件拖进去分析,

ASV2010分析方法:
A.修正混淆非字符变量名:转换 非ANSI字符为ANS字符
http://b339.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/tmvaEwlQlQbOBi4IzLzluSDIevywdIR1QooGlyUJLCA!/b/dFMBAAAAAAAA&bo=eQGqAAAAAAADEOc!
转换后。
http://b340.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/snt.EVqX9UflAAmgGvvrEvu0IbMWchOss3m77yKSHuw!/b/dFQBAAAAAAAA&bo=RwIhAgAAAAADEFM!
哼哼!!DoSWF加密,难不到我们的,我们继续。

8. 反DoSWF加密:(内存破解方式)
   需要工具:SWF to EXE Converter (注意 不支持中文目录路径)破解DoSWF加密工具.exe(破解DoSWF加密工具.e,我编写已经放到群共享里了)因 SWF to EXE Converter,不支持中文目录路径我们就放到目录D:\SWF to EXE Converter上吧,
http://b305.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/7PsGLoWU0ggOWUlIrcpnoFd9IfcdpBp2va9SwsW50hQ!/b/dDEBAAAAAAAA&bo=bAIYAQAAAAADEEI!


开始了:A.复制 gameMain3.swf 到目录D:\SWF to EXE ConverteB.运行 sWf2eXe.exeC.Clear InputD.Add a File 选择 gameMain3.swfE.ConvertF.到D:\SWF to EXE Converter\Output 找到 gameMain3.exe 运行G.运行:破解DoSWF加密工具.exeH.点选进程:gameMain3.exe 点按钮:分析保存,保存后可关闭这些程序了I.破解的SWF保存在 破解DoSWF加密工具.exe运行目录下的SWF目录里,这个示例我放到了D:\SWF to EXE Converter\SWF 下,按大小排列这个目录的SWF文件,大的在前。
http://b310.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/vs5dJTLDn642ulPIZGWdXyKuJ0*HRGnxMUs7OExftp8!/b/dDYBAAAAAAAA&bo=3AHbAAAAAAADEDM!
J.运行 ASV2010 从大到小逐个拖这些SWF到ASV2010分析,忽略错误!点选 Actionscripts 页项查看
http://b311.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/P6a9zSMgdrqGtsSXIjOiUShET01mR87m5UhENdYF6eE!/b/dDcBAAAAAAAA&bo=uwGdAQAAAAADEBM!
就是这个,破解DoSWF成功, 当然不忘了 修正混淆非字符变量名:转换 非ANSI字符为ANS字符
http://b311.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/jnXrdcPSml59PtKrh8poFeIUBZeIXuGMU8iTNU8g.Yw!/b/dDcBAAAAAAAA&bo=QwEhAQAAAAADEFc!
转换后
http://b85.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/zWHCvsy7GyKiIbZ.T73Gw6jto5wgXsOlEvDZNe8TbE4!/b/dFUAAAAAAAAA&bo=6AEHAgAAAAADENk!
还记得这里的amfService, this.+.getUserInfoAMF 吗,是上述的请求指令请求服务名啊!! K.保存SWF成果:菜单栏 Files -> Save as…   另存为,gameMain3_V30_CR.swf,因为它的网址及版本路径是http://official.ppyimg.cn/media/swf/v30/gameMain3.swf ,V30版,修定是3,CR代表破解,命名随便吧自己看懂就行了。L.保存TEXT文本成果(用于分析):菜单栏 Files -> Save SWF DATA as Text…   另存为,gameMain3_V30_CR_swfdata.txt,M.破解SWF完成。
1.3 分析人人网FLASH网页游戏《泡泡海洋》,同时使用AMF模块设计辅助程序 1.创建一个分析文件(泡泡海洋分析.txt),分析指令的记录都写在里面.2.如何过滤: 打开charles ,重新进入游戏网页 泡泡海洋 ,找到AMF指令请求的项的网址http://b309.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/RXXcRk9DsMrTy4a.ZY17.*m5ze.hReSHU*nVNt6uy10!/b/dDUBAAAAAAAA&bo=ugIuAgAAAAADIJE! 双击右边那个网址就可复制的
http://b339.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/ihMte6YhhcqddUmJ*o3B5hJ6EkAY3pqL5MjcQKo5LW4!/b/dFMBAAAAAAAA&bo=BQLvAAAAAAADEN0!
复制 http://ocean.paopaoyu.cn ,点Charles右边的齿轮按钮
http://b305.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/pc.rlMoZqfabX5EoLrmEL*3jSvCuTi.aySjMvYF71S0!/b/dDEBAAAAAAAA&bo=PAA0AAAAAAADED0!
点选:Recording Setings,在弹出 的Recording Setings框里选择第二项:Include, 按 Add ,增加过滤,Protocol:选择HttpHost:粘贴刚才复制的网址 http://ocean.paopaoyu.cn其他不用填写,按OK
http://b328.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/IT4RAVGD*YqMEtZ9SryalrfsUgnmFxPWlFszN13xdnI!/b/dEgBAAAAAAAA&bo=twGvAQAAAAADEC0!

打勾是使能过滤,不打勾为不过滤。清空后,刷新一下游戏网页,看看是不是清淅很多了。
设置过滤只显示与服务器AMF通信完成。想全部分析时记得取消打勾哦。
3.分析第一个指令:amfService.getUserInfoAMF A.在Charles里分析AMF参数有点吃力,不能全部复制,我们先弄个带拖放的AMF完整通信字节集解释器 (自己使用AMF模块对应游戏编写) 代码如下: .版本 2.支持库 edroptarget .程序集 窗口程序集1 .子程序 __启动窗口_创建完毕 拖放对象1.注册拖放控件 (编辑框1.取窗口句柄 ())拖放对象1.接收文件 = 真 .子程序 _拖放对象1_得到文件.参数 接收到的文件路径, 文本型.局部变量 找到位置, 整数型.局部变量 取出长度, 整数型.局部变量 AMF字节集, 字节集.局部变量 完整AMF信息, AMF封装信息.局部变量 信息, 变体型 找到位置 = 寻找文本 (接收到的文件路径, #换行符, , 假).如果 (找到位置 = -1)    取出长度 = 取文本长度 (接收到的文件路径).否则    取出长度 = 找到位置 - 1.如果结束AMF字节集 = 读入文件 (取文本左边 (接收到的文件路径, 取出长度))AMF解码 (AMF字节集, 信息, 完整AMF信息, )编辑框1.内容 = AMF封装信息到文本格式 (完整AMF信息)
http://b342.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/dzVjIErqXh3x*gNaVwhS0Uvej*IQ0LNhwHiMwMNUqnw!/b/dFYBAAAAAAAA&bo=9AGRAQAAAAADEFA!
B .选择第一个指令,在右边右击,选择Save Request (保存请求)
http://b340.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/J2JEaw6YzXmDfAZWfjHo75i0siYr5B2abSsX6Jt.kfs!/b/dFQBAAAAAAAA&bo=ugK6AQAAAAADIAY!
保存到目录D:\泡泡海洋里,文件名req,到这个目录,把 req 文件拖到 运行着的AMF完整通信字节集解释器.exe 程序中
http://b338.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/bPM0BU2RKWcYsViBtiVF0HYYTXtsncSkhNrIYLZ*aW4!/b/dFIBAAAAAAAA&bo=TwK5AQAAAAADEMA!
解释后是: ---------- AMF封装信息 ----------AMF版本号: 3 信息头 [ ] 信息体 .targetURI: "amfService.getUserInfoAMF"信息体 .responseURI: "/1"信息体 .信息: ["e31a541370ef20317f0a2e13e70bc85e","2.d7d06579f17947eb1136fe50e910472d.3600.1362128400-451237832"]---------- AMF封装信息 结束 ---------- amfService.getUserInfoAMF 指令带有两个参数:参数1:文本型:"e31a541370ef20317f0a2e13e70bc85e"参数2:文本型:"2.d7d06579f17947eb1136fe50e910472d.3600.1362128400-451237832" 这些参数的怎样来的呢,我们去找答案。。。。 C .打开 UltraEdit,把刚才破解的AS3文本文件:gameMain3_V30_CR_swfdata.txt拖到里面,视图着色文件类型选择JAVA 方案,按JAVA指令格式分色显示较为清淅。按 <Ctrl> + F 查找 getUserInfoAMF 在高级里勾选 加亮所有找到的项、勾选 列出包含字串的行,
http://b90.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/QF3mM*FrzWq2t11.dTiESc87EKd1Wv4urt9mzutooRI!/b/dFoAAAAAAAAA&bo=7QFRAQAAAAADEIk!
找到一条代码      public function _P47_():void{            this.hideMouse();            this._P30_(null);            var _local1:PendingCall = this.+.getUserInfoAMF(this._P29_{(this._bubbleFish.skey), this._bubbleFish.skey);            _local1.responder = new Responder(this._P48_, this.errorBack);      } 注:这个ASV2010的修正混淆有点问题了,this.后面的变量字串给修正为+了,Responder给包进去PendingCall了,一个函数里有这样的大括号 { { }所以可以确定 是 反混淆不太完美造成this._P29_后面多了个左大括号{, 自己修理一下后       public function _P47_():void{            this.hideMouse();            this._P30_(null);            var _local1:PendingCall = this.amfSVR.getUserInfoAMF(this._P29_(this._bubbleFish.skey), this._bubbleFish.skey);            _local1.responder = new Responder(this._P48_, this.errorBack);      } 即参数1=this._P29_(this._bubbleFish.skey)参数2=this._bubbleFish.skey 全都与这个 this._bubbleFish.skey 相关,那我们先看看这个this._bubbleFish.skey 是什么神物, 慢慢向上找到 变量 _bubbleFish找到 private var _bubbleFish:_P2_;_P2_是什么类呢,得再找这个类 按 <Ctrl> + F 查找 class _P2_找到两个,但我想要的是有类成员 skey 的类,就只有 public class _P2_ extends EventDispatcher { 成员赋值(有替换过程 全部“ ”换成 “+”)       public function set skey(_arg1:String):void{            var _local2:*;            var _local3:*;            if (_arg1.length != 0){                _local2 = (_arg1.length - 1);                this._P239_ = _arg1;                _local3 = 0;                while (_local3 < _local2) {                  this._P239_ = this._P239_.replace(" ", "+");                  _local3++;                };            };      } 取成员值      public function get skey():String{            return (this._P239_); 是在什么地方被赋值呢,我们找.skey =, 按 <Ctrl> + F 查找.skey = 找到if (!this._bubbleFish.skey){                if (loaderInfo["@doswf__p"]["bd_sig_session_key"] != null){                  this._bubbleFish.skey = ((((loaderInfo["@doswf__p"]["bd_sig_session_key"] + ",") + loaderInfo["@doswf__p"]["bd_sig_user"]) + ",") + loaderInfo["@doswf__p"]["bd_sig_portrait"]);                } else {                  if (loaderInfo["@doswf__p"]["xn_sig_session_key"] != null){                        this._bubbleFish.skey = loaderInfo["@doswf__p"]["xn_sig_session_key"];                        Profile.text_font = "MS PGothic";                  } else {this._bubbleFish.skey = ((loaderInfo["@doswf__p"]["skey"]) || ("skey"));                        if (!ApplicationDomain.currentDomain.hasDefinition("_P1_")){                            return;                        };                  };                };            }; loaderInfo :载入FLASH运行时的参数信息["@doswf__p"] :是DoSWF加壳后的变换 loaderInfo["@doswf__p"]["bd_sig_session_key"]loaderInfo["@doswf__p"]["xn_sig_session_key"]loaderInfo["@doswf__p"]["skey"] 还记得在 第一章 1.2.5分析网页里的FLASH运行参数吗?就是紫色部分:<embed name="forIE" src="http://official.ppyimg.cn/media/swf/v30/gameMain3.swf" width="756" height="640" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" wmode="transparent" flashVars="skey=2.0f66314f891ff9ac41116b58805453e5.3600.1362121200-451237832&sns=renren&feed_fun=sendFeed&snsid=451237832" allowScriptAccess="always" ></embed> 所以没有 loaderInfo["@doswf__p"]["bd_sig_session_key"]所以没有 loaderInfo["@doswf__p"]["xn_sig_session_key"]有参数:loaderInfo["@doswf__p"]["skey"] 这个重要参数this._bubbleFish.skey = 2.0f66314f891ff9ac41116b58805453e5.3600.1362121200-451237832没有空格不用替换我们编写挂时可以提取网页中这个参数用作运行 amfService.getUserInfoAMF 指令带有两个参数:参数1:文本型:"e31a541370ef20317f0a2e13e70bc85e"参数2:文本型:"2.d7d06579f17947eb1136fe50e910472d.3600.1362128400-451237832" 参数2找到了还差参数1参数1=this._P29_(this._bubbleFish.skey) 因为:函数是返回文本的 this._P29_,this    this 所以在这个找到 getUserInfoAMF 的包内(//& (raytoon.bubblefish.&)) 慢慢向下或向上找 function _P29_(或鼠标滚动找)
http://b340.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/FOeUmBXFFkQsJL3Kg7J46SW1PgTJ*MuqnXhxHAyNOqY!/b/dFQBAAAAAAAA&bo=tQGKAAAAAAADEAs!
第一个没有参数的,没有返回的,不是第二个参数是整数而不是文本的,没有返回的,不是第三个参数对象而不是文本的,没有返回的,不是第四个参数是可多个变量的数组的,返回字串的,应该是,看看 private function _P29_{(... _args):String{            var _local3:Object;            var _local4:Number;            var _local5:String;            var _local6:String;            var _local7:String;            var _local8:String;            var _local9:String;            var _local10:String;            var _local2 = "";            for each (_local3 in _args) {                if (_local3 != null){ //非空                  _local2 = (_local2 + _local3.toString());                  //易语言可先转换参数为字串再调用这个函数                } else {                  _local2 = (_local2 + "null");//空参数用字串null代替                };            };//分拆多个参数,易语言可用多个可空参数代替            _local4 = Math.random(); // 取随机小数            _local5 = _P154_._P236_(_local4.toString()); //MD5函数,结合上面就是随机MD5            _local6 = _local5.substr(0, 8);//取文本中间            _local2 = (_local2 + _local6);            _local7 = _P154_._P236_((_local2 + this._bubbleFish._P117_))); // MD5函数            _local8 = _local7.substr(0, 10);            _local9 = _local7.substr(10);            _local9 = _local9.substr(0, (_local9.length - 8));            _local10 = ((_local8 + _local6) + _local9);            return (_local10);      } 这是一段加密通信用的掩码生成过程, //注:MD5函数 易语言中称作 取数据摘要//_P154_ ((._P154_)package ( {    import flash.utils.*;// flash.utils.* 内部类    import _P61_.*;     public class _P154_ {       public static var _P24_*:ByteArray;       public static function _P236_(_arg1:String):String{            var _local2:ByteArray = new ByteArray();            _local2.writeUTFBytes(_arg1);            return (_P164_^(_local2));      }从 function _P164_ 看出是一个FLASH设计系统的MD5生成函数//注:MD5函数 完//从 参数1:文本型:"e31a541370ef20317f0a2e13e70bc85e" 也猜到不少吧 // this._bubbleFish._P117_) 是什么呢因为private var _bubbleFish:_P2_;到_P2_这个类找,按 <Ctrl> + F 查找class _P2_ public class _P2_ extends EventDispatcher {。。。略。。。      public function _P2_(_arg1:SingletonEnforcer){            this._P237_ = new Object();            this._P166_# = new Array(5);            this._P238_ = new Object();          this._P117_) = ["happy$newyear", "happy.newyear"]; //密匙1、密匙2            this._P88_& = ;            super();      } 就是这个值呢!!!!! 我暂时把它称作生成掩码1的: 通信密匙1this._bubbleFish._P117_) = "happy$newyear" 我们编写挂时 这个通信掩码程序改编为易语言程序不难吧!private function _P29_{(... _args):String{   ///加密复杂度低,普通请求加密用private function _P103_(... _args):String{///加密复杂度高,捉鱼、收潜艇鱼请求加密用 函数 _P29_ 《掩码生成程序1》 使用 通信密匙1,普通请求加密用函数 _P103_ 《掩码生成程序2》 使用 通信密匙2,捉鱼、收鱼加密用 基本上编写好这两个掩码生成程序,这个游戏辅助就编写好80%了,余下那些只是编写操作请求指令的体力劳动了。易语言版本的 _P29_.版本 2.支持库 dp1 .子程序 getSigOf, 文本型, , 普通操作用.参数 arg1, 文本型, 可空.参数 arg2, 文本型, 可空.参数 arg3, 文本型, 可空.参数 arg4, 文本型, 可空.参数 arg5, 文本型, 可空.参数 arg6, 文本型, 可空.局部变量 arg, 文本型.局部变量 RandomMD5, 文本型.局部变量 a7, 文本型.局部变量 a6, 文本型.局部变量 a8, 文本型.局部变量 a9, 文本型.局部变量 a10, 文本型.局部变量 a5, 文本型 arg = arg1 + arg2 + arg3 + arg4 + arg5 + arg6.如果真 (arg1 = “”)    arg1 = “null”.如果真结束RandomMD5 = 取数据摘要 (到字节集 (取现行时间 ()))a6 = 取文本左边 (RandomMD5, 8)a5 = arg + a6 + g_SigCodeKeyArr ' //通信密匙1a7 = 取数据摘要 (到字节集 (a5))a8 = 取文本左边 (a7, 10)a9 = 取文本中间 (a7, 11, 取文本长度 (a7) - 10)a9 = 取文本左边 (a9, 取文本长度 (a9) - 8)a10 = a8 + a6 + a9' 输出调试文本 (a10)    返回 (a10)这个指令请求就分析完了,我们还要看看向服务器请求后返回什么,是玩家游戏内的等级经验ID头像昵称等信息,
http://b338.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/jpJe7eJamJPPOYeOGukkEI9Tt91eP*98bPLey7z2iEc!/b/dFIBAAAAAAAA&bo=RwJCAgAAAAADEDA!
把它保存为文件res 并拖到 AMF完整通信字节集解释器 看看
http://b308.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/YIxCyBdZ34jX7gah5*CQXBwQIvHBCusrpt2uhWnV89k!/b/dDQBAAAAAAAA&bo=.wGSAQAAAAADEFw!
---------- AMF封装信息 ----------AMF版本号: 3 信息头 [ ] 信息体 .targetURI: "/1/onResult"信息体 .responseURI: "null"信息体 .信息: {"user_info":{"tutorial_tempo":11,"fish_food":400,"exp":627,"remain_shock_times":100,"shells":6514,"is_exchanged":false,"pearls":60,"first_login":false,"tempo":2,"max_tanks":3,"first_recharge":true,"almanac_level":2,"avatar":"http://hdn.xnimg.cn/photos/hdn121/20120401/0525/h_head_5d47_5f400003470a2f75.jpg","is_vip":"0","next_exp":750,"sns_id":"451237832","nickname":"韦春花","id":1447395,"has_level_up":false}}---------- AMF封装信息 结束 ---------- JSON的 { }代表Object 即对象数据类型,我们完全可以用模块中的 字典类 在 信息体 .信息 这个变体开型变量中 提取这些数据,具体看看游戏辅助的源码。注意那个id ,是玩家在游戏中的id,有部分请求指令需要用到它作参数,如取鱼缸信息,这里先注一下。Sns_id 是窝号,nickname是昵称…不详说了,看看游戏页面对照一下就知道 另:多信息体的请求,可拆分为单个请求如:
http://b338.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/Dlu9ePJsTHN5wIAd1NizAEvNLgmmv4dCz7Gyr4cewSc!/b/dFIBAAAAAAAA&bo=RwI*AgAAAAADEE0!
可逐个逐个拆分发送,如上述可拆分为请求1 :amfService.getRaytoonVIPAMF 发送
http://b338.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/eeSKhpZBqnj8FksxGnqQUty85JCRuHX5ozRWefsE6Ew!/b/dFIBAAAAAAAA&bo=KAKFAAAAAAADEJo!
请求2: amfService.getAlmanacNumDataAMF 发送
http://b243.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/e2o830nBRnzIygld4LMjBYj8bQMOP1e1Y9rZEfeY3y4!/b/dPMAAAAAAAAA&bo=CgJzAAAAAAADEE4!
请求N。。。。 发送N。。。。 作用是一样的4.分析第二个指令:amfService.getMyFishTankListAMF (操作:查询鱼缸列表信息)5.
因为其返回鱼缸ID关联到分析的第三个指令:查看鱼缸里对象信息,所以特找这个指令分析作教程
先看看Charles截取的请求参数

http://b86.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/zSVh1b7uCdzAcegqhbfCsVnCNbGIhcrIcSIlRb8GCWM!/b/dFYAAAAAAAAA&bo=JQKHAAAAAAADEJU!
http://b340.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/EWHlYa8a8i*CDgNRl6NYguTZk1RzK5rFCyu5tvqWnmw!/b/dFQBAAAAAAAA&bo=BQLvAAAAAAADEN0!
UltralEdit代码分析中查找 getMyFishTankListAMF
找到
      public function _P79_(_arg1:uint){
            this.hideMouse();
            var _local2:PendingCall = this.+.getMyFishTankListAMF(this._P29_{(_arg1, this._bubbleFish.skey), this._bubbleFish.skey, _arg1);
            _local2.responder = new Responder(this._P80_, this.errorBack);
      }
修正{后
      public function _P79_(_arg1:uint){
            this.hideMouse();
            var _local2:PendingCall = this.+.getMyFishTankListAMF(this._P29_(_arg1, this._bubbleFish.skey), this._bubbleFish.skey, _arg1);
            _local2.responder = new Responder(this._P80_, this.errorBack);
      }
参数1:this._P29_( _arg1, this._bubbleFish.skey) //通信掩码生成,早OK了参数2:this._bubbleFish.skey   // skey,早OK了参数3:arg1   // 整数 _arg1:uint,要去找它出处
找它出处就要看看谁调用函数 _P79_了,//   _P79_(_arg1:uint)UltralEdit中按 <Ctrl> + F 查找 _P79_
http://b309.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/JKpYPPwkoqYV19v3YABf01HXO5yBAZJs4Awd5Iwb438!/b/dDUBAAAAAAAA&bo=3AGyAAAAAAADEFo!
1和2 一样的,任一个也行了,3是原自己这里,不用了就选择第1行 双击代码
      &.getInstance()._P79_(this._hostId);
再找_hostId =
http://b304.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/SR0mURCWS91QxvwQbdqly99.kyT2GRxYe8SpGnViW7Q!/b/dDABAAAAAAAA&bo=twILAQAAAAADEIo!
找到太多了,有点头痛是吧,不怕还有捷径,因为是 this,等于说明这个 _hostId 变量就在这个包内,鼠标慢慢向上滚动,往回看,找包的开头处
http://b309.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/2cXFsd.wfXUUvk8c76SKuDyvlPA2J7CidGYwo7kCDYo!/b/dDUBAAAAAAAA&bo=CAKSAQAAAAADEKw!
鼠标点一下开头处,我们要从这里开始向下逐个找_hostId =或按 <Ctrl> + F 输入_hostId = ,取消 列出包含字符串的行的勾。找到      public function MyHome(){            this._hostId = _P2_.getInstance()._P55_;            super();            if (_P2_.getInstance()._P273_ != 0){this._hostId = _P2_.getInstance()._P273_;            };_P2_是什么呢,分析第一个指令时找过了,是一个类,想再找?按 <Ctrl> + F 查找 class _P2_getInstance() 是取实例的意思,说明: _P2_ 是一个单实例类,全局只得唯一一个实例存在,我感觉它是用来保存游戏玩家主要信息的主类,一会就知道了。。到这个_P2_类的代码处:    public class _P2_ extends EventDispatcher {      private static var _instance:_P2_;。。。略。。。      public function _P2_(_arg1:SingletonEnforcer){//百度一下,唯一一个实例存在类            this._P237_ = new Object();            this._P166_# = new Array(5);            this._P238_ = new Object();            this._P117_) = ["happy$newyear", "happy.newyear"];            this._P88_& = ;            super();      }      public static function getInstance():_P2_{            if (_P2_._instance == null){                _P2_._instance = new _P2_(new SingletonEnforcer());            };            return (_P2_._instance);      }。。。略。。。      public function set_P55_(_arg1:uint):void{            this._P240_ = _arg1;      }      public function get_P55_():uint{            return (this._P240_);赋值取值函数我们主要看看是谁给这个类实例的 _P55_赋值就知道什么是 _hostId了按 <Ctrl> + F 查找 ._P55_ = 找到      private function getUserInforByEvent(_arg1:BubbleFishCommunicationEvent):void{            var _local4:int;            var _local5:MovieClip;            var _local6:*;            var _local7:HeadPanel;            var _local8:BubbleFishButton;            var _local9:BubbleFishButton;            var _local10:FriendBt;            var _local11:BubbleFishButton;            var _local12:SnsIdMenu;            var _local13:*;            var _local14:MovieClip;            var _local15:BubbleFishButton;            var _local16:BubbleFishButton;            var _local17:BubbleFishButton;            var _local18:Timer;            var _local19:Date;            var _local20:AdPopWindow;            var _local2:Object = _arg1.args;            if (this._header == null){                this._bubbleFish.owner_level = _local2["almanac_level"];                this._bubbleFish._P266_ = _local2["nickname"];                this._bubbleFish._P141_ = _local2["sns_id"];                this._bubbleFish.owner_food = _local2["fish_food"];                this._bubbleFish.;this._bubbleFish._P55_ = _local2["id"];//原形毕露了吧                this._bubbleFish._P271_ = _local2["max_tanks"];                this._bubbleFish.owner_shell = _local2["shells"];                this._bubbleFish.(_P77_ = _local2["remain_shock_times"];                this._bubbleFish.owner_pearl = _local2["pearls"];                this._bubbleFish._P276_ = _local2["is_vip"];                this._bubbleFish.]_P272_ = _local2["is_fan"];                this._bubbleFish.>_P131_ = _local2["power"];                this._bubbleFish._P235_ = _local2["exp"];                this._bubbleFish.&& = _local2["next_exp"];                this._bubbleFish._P242_ = _local2["is_exchanged"];                this._bubbleFish._P251_ = _local2["is_vip"];                this._bubbleFish.qqVipLevel = _local2["vip_level"];                this._bubbleFish._P252_ = _local2["is_year_vip"];                this._bubbleFish.)_P161_ = _local2["is_vip_gift"];                this._bubbleFish._P261_ = _local2["avatar"];                this._bubbleFish._P145_( = _local2["first_recharge"];                _firstLogin = _local2["first_login"];                this._bubbleFish._P81_) = _local2["first_login"];                _local4 = 9;                if (_local2["tempo"] < _local4){                  this._bubbleFish._P268_ = _local2["tempo"];                } else {                  this._bubbleFish._P268_ = _local4;                };                this._bubbleFish._P276_ = _local2["is_vip"];                this._header = new Header();                _head_layer.addChild(this._header);                _P195_.=_P65_ = this._header;                if (Profile.version != _P6_._P8_)){                  if (ExternalInterface.call("getCookie", this._bubbleFish._P141_.toString()) == "0"){                        BubbleFishSound.getInstance().restoration(0);                  };                };            };\\\\\\\\\\\\\\\\\\\\\\\找找 这个事件响应函数 getUserInforByEvent\\\\\\\\\\\\\\\\\\\\\\\\      private function interfaceMcPutToStage(){//事件接口函数            &.getInstance().addEventListener(BubbleFishCommunicationEvent.COM_GET_USER, this.getUserInforByEvent);//加入监听事件getUserInforByEvent            &.getInstance().addEventListener(BubbleFishCommunicationEvent.NEW_ALMANCE, this.newAlmanacFish);//加入监听事件newAlmanacFish            &.getInstance().addEventListener(BubbleFishCommunicationEvent.COM_GET_OTHER_USER, this.getOtherInfoByEvent);//加入监听事件getOtherInfoByEvent            &.getInstance()._P47_();//调用 _P47_ ,_P47_还记得吗,是我们第一个指令getUserInforAMF所在函数请求返回时产生COM_GET_USER事件,有级升时产生NEW_ALMANCE事件。。。\\\\\\\\\\\ 第一个指令啊      public function _P47_():void{            this.hideMouse();            this._P30_(null);            var _local1:PendingCall = this.+.getUserInfoAMF(this._P29_(this._bubbleFish.skey), this._bubbleFish.skey);            _local1.responder = new Responder(this._P48_, this.errorBack);      }\\\\\\\\\\\\\\\\\\\\\\\\响应事件设置,值是_arg1["user_info"]      private function _P48_(_arg1:Object):void{            MouseState.getInstance().showMouse();            this._P228_(BubbleFishCommunicationEvent.COM_GET_USER, _arg1["user_info"]);\\\\\\\\\\\\\\\创建通信事件      private function _P228_(_arg1:String, _arg2:Object):void{            var _local3:BubbleFishCommunicationEvent = new BubbleFishCommunicationEvent(_arg1);_arg1["user_info"] 意思是这个事件返回的_arg1, 只返回 _arg1["user_info"]项目 红色部分 {"user_info":{"tutorial_tempo":11,"fish_food":400,"exp":627,"remain_shock_times":100,"shells":6514,"is_exchanged":false,"pearls":60,"first_login":false,"tempo":2,"max_tanks":3,"first_recharge":true,"almanac_level":2,"avatar":"http://hdn.xnimg.cn/photos/hdn121/20120401/0525/h_head_5d47_5f400003470a2f75.jpg","is_vip":"0","next_exp":750,"sns_id":"451237832","nickname":"韦春花","id":1447395,"has_level_up":false}} 那么 this._bubbleFish._P55_ = _local2["id"];//原形毕露了吧就等于 this._bubbleFish._P55_ = _local2["id"] = 1447395看看,对吧!!
http://b309.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/reA4nTnmfeMOU8pRRoZYMOQqk*s*5H4hJWvshc4pc88!/b/dDUBAAAAAAAA&bo=DgJ4AAAAAAADEEE!
\\\\ 我们可以在第一个指令getUserInfoAMF请求后保存这个id给需要时用了。_local3 = this.+.getMyFishTankObjectListAMF(this._P29_(_arg2, _arg1, this._bubbleFish.skey), this._bubbleFish.skey, _arg1, _arg2); 全部找到了参数1:this._P29_( _arg1, this._bubbleFish.skey) //通信掩码生成,早OK了参数2:this._bubbleFish.skey   // skey,早OK了参数3:arg1   // 整数 _arg1:uint,找到了,自身ID自身ID, getUserInfoAMF请求返回的["user_info"] ["id"] = 1447395提示:可以保存getUserInfoAMF返回的一些重要信息供相关指令使用。getMyFishTankListAMF返回所有鱼缸信息(不包括缸内鱼信息)
http://b302.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/vwKuwfKnKtk*6i5rgvzvEuXTOJFWHbpPltOSV0mRAZk!/b/dC4BAAAAAAAA&bo=oQGIAQAAAAADEBw!
留意一下选中的那个 ID 项目(鱼缸ID),下述getMyFishTankObjectListAMF指令需要用到getMyFishTankListAMF 分析完。


6.分析第三个指令:amfService.getMyFishTankObjectListAMF (操作:看鱼缸里鱼类等信息)先看看Charles截取的请求参数
http://b340.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/Pfdri1o6x4GYbBLkdulsTKRicgsYp1X2IJEb*2uOQIA!/b/dFQBAAAAAAAA&bo=PgLhAAAAAAADEOg!
UltralEdit代码分析中查找 getMyFishTankObjectListAMF找到AS3代码      public function *_P81_(_arg1:uint, _arg2:uint){            var _local3:PendingCall;            this.hideMouse();            _local3 = this.+.getMyFishTankObjectListAMF(this._P29_{(_arg2, _arg1, this._bubbleFish.skey), this._bubbleFish.skey, _arg1, _arg2);            _local3.responder = new Responder(this._P82_, this.errorBack);      } 修正一下_local3 = this.+.getMyFishTankObjectListAMF(this._P29_(_arg2, _arg1, this._bubbleFish.skey), this._bubbleFish.skey, _arg1, _arg2); 参数1:this._P29_(_arg2, _arg1, this._bubbleFish.skey) //通信掩码生成,早OK了参数2:this._bubbleFish.skey   // skey,早OK了参数3:arg1   // 整数 _arg1:uint,自身ID参数4:arg2   // 整数 _arg2:uint,要去找它出处 找它出处就要看看谁调用函数 *_P81_了,//   *_P81_ (_arg1:uint, _arg2:uint)UltralEdit中按 <Ctrl> + F 查找 *_P81_
http://b308.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/A.xE.1V.LLtNnUW.DQZSIICT6wYSHbLIXig*.f07OB4!/b/dDQBAAAAAAAA&bo=owKNAAAAAAADEBk!
1和2 一样的,任一个也行了,3是原自己这里,不用了就选择第1行 双击代码&.getInstance().*_P81_(this._host_id, this._data.id);所以 arg1   = this._host_id    // 原先找过了,是自身IDarg2   = this._data.id    // 快了继续努力 按 <Ctrl> + F 输入this._data.id =找到      protected function changeFishTankData(_arg1:Object){            var _local2:Array;            this._data.star = _arg1["star"];            this._data.isMainFishTank = _arg1["is_first"];this._data.id = _arg1["id"];// 这里            this._data.capacity = _arg1["capacity"];            this._data.fishTank_name = _arg1["name"];            this._data.can_be_trolled = _arg1["can_be_trolled"];            this._data.troll_price = _arg1["troll_price"];            this._data.troll_times = _arg1["troll_times"];            this._data.family_code = _arg1["family"];            this._data.is_shocked = _arg1["is_shocked"];            this._data.is_pool = _arg1["is_pool"];            this._data.price = _arg1["price"];            this._data.isProtected = _arg1["is_protected"];            if (this._data.style != _arg1["style"]){                this._data.style = _arg1["style"];                _local2 = _P195_._P417_.tanks;                _local2[(_P195_._P417_.currMc.index - 1)] = _P195_._P434_.data;                _P195_._P417_.changeTanks(_local2);                this.addStyle();            };      }看到这里,有没有感觉是一个请求的返回呢,所以经验也是很重要的,我就不倒找它来历了
http://b338.photo.store.qq.com/psb?/2317c5e1-2510-4bb7-963f-5eafe1a5d209/Lc*1JR4r72vCvwIlVyudIQuSndgR6AvQQvPDisyW5sE!/b/dFIBAAAAAAAA&bo=oQGIAQAAAAADEBw!
看看!!是不是一样的项目,_local3 = this.+.getMyFishTankObjectListAMF(this._P29_(_arg2, _arg1, this._bubbleFish.skey), this._bubbleFish.skey, _arg1, _arg2); 参数1:this._P29_(_arg2, _arg1, this._bubbleFish.skey) //通信掩码生成,早OK了参数2:this._bubbleFish.skey   // skey,早OK了参数3:arg1   // 整数 _arg1:uint,自身ID参数4:arg2   // 整数 _arg2:uint,getMyFishTankListAMF请求返回的各个鱼缸的ID,指令 getMyFishTankObjectListAMF 分析完成。



土豪通道

天使3号 发表于 2018-9-13 11:43

太乱了。。

远方呢 发表于 2018-9-13 12:06

天使3号 发表于 2018-9-13 11:43
太乱了。。

这样可好?

ajq119 发表于 2018-9-13 12:11

帮顶吧~~~~~~~~~

吾爱打炮 发表于 2018-9-13 12:33

{:1_893:}虽然看不懂

linclon 发表于 2018-9-13 12:44

厉害厉害感谢楼主分享

bnb 发表于 2018-9-13 13:02

这个教程好像不是你做的吧,还有重要的AMF模块都没有

张小坑 发表于 2018-9-13 13:32

群文件共享?搬了能改改么

遗忘|那段情 发表于 2018-9-13 15:59

大神啊,收下膝盖

wazl8890 发表于 2018-9-13 16:00

学习了,谢分享
页: [1] 2 3 4
查看完整版本: AMF封包通信的游戏辅助易语言编写(逆向分析脱机教程)