flash页游seer2折腾日记2

in 折腾一下 with 0 comment

flash页游seer2折腾日记2

前言

这里主要探究一下赛2的登录通讯部分。直接从2开始写了,后面有时间了再把1补上。

flash曾有着辉煌的过去,但是随着漏洞越来越多,且无法有效的解决,正在逐步被html5替代。过去的flash游戏网站或者转型h5、手游或者逐渐没落、淘汰,但是看一看这些“过时的代码”,还是能让人学到一些有用的东西。

用反编译工具反编译下载的flash文件,发现里面有大量ActionScript(.as)文件。我没有系统的学习ActionScript,但是考虑到有学习过Java,刚好as又和Java有相似之处,干脆结合Google,用java语言分析as。

预习

工欲善其事必先利其器,as的基本数据类型还是要了解一下的

as3基元数据类型包括 Boolean、int、Null、Number、String、uint 和 void。

Boolean 数据类型包含两个值:true 和 false,默认值是 false。

int 数据类型在内部存储为 32 位有符号整数,默认值是 0。

Null 数据类型仅包含一个值:null。

Number 数据类型使用IEEE-754标准,为64 位双精度浮点数。

String 数据类型表示一个 16 位字符的序列。字符串在内部存储为 Unicode 字符,并使用 UTF-16 格式。

uint 数据类型在内部存储为 32 位无符号整数,默认值是 0。

void 数据类型仅包含一个值:undefined。无类型变量是指缺乏类型注释或者使用星号 (*) 作为类型注释的变量。您可以将 void 只用作返回类型注释。

ActionScript 核心类还定义下列复杂数据类型:Object、Array、Date、Error、Function、RegExp、XML 和 XMLList。

ActionScript 也是面向对象的语言,遵循面向对象的抽象、封装、继承、多态等特点。

准备

导出脚本资源:

分析知,Action为我们需要的as文件,选中后导出。

SWFDecompiler_7KdjRI6Rog.png

导出后得到as文件,为了方便查看代码,可以使用idea配合插件使用。

idea64_aDye8Kq4lN.png

探秘

this._progressBar.setTitle("正在加载登陆界面");
this._progressBar.setTitle("正在进入游戏");

大致翻一翻,看到了汉字很让人舒心。这正是游戏加载时我们能够见到提示信息,说明我们的方向是正确的。

private function onConfigXMLComplete(event:XMLEvent) : void{
    var _loc_2:* = event.data;
    ......
    this._loginURL = String(_loc_2.login);//设置loginURL为dll/LoginModule.swf
    ......
    return;
}// end function
        
private function loadLogin() : void{
    this._progressBar.setTitle("正在加载登陆界面");
    this._loginLoader = new Loader();
    this._loginLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, this.onLoginBytesComplete);//登录成功事件
    this._loginLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, this.onProgress);//过程错误事件
    this._loginLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, this.onIoError);//通信错误事件
    var _loc_1:* = new URLRequest(this.ROOT_URL + TaomeeVersionManager.getInstance().getVerURLByNameSpace(this._loginURL));//下载dll/LoginModule.swf文件
    this._loginLoader.load(_loc_1); //加载登录界面
    return;
}// end function
<root debug="0">
    ......
    <login>dll/LoginModule.swf</login>
    ......
</root>

结合seer.xml文件,发现基本页面加载完成后,有添加对dll/LoginModule.swf的监听事件。随后的程序则为登录成功、失败处理函数,所以中间经历了LoginModule模块。

核心

故导出LoginModule.swf的as文件,并对其继续分析。

private function showMainLoginView(param1:Function = null) : void{
    ......
    this._loginPanel = new MainLoginPanel(this._loginAgent, true, param1);
    ......
}// end function

private function login(param1:String) : void{
    var loginPanel:MainLoginPanel;
    var onConfirm:Function;
    var actualLogin:Function;
    var net:* = param1;
    var checkPassword:* = function (param1:String) : Boolean{...};
    // end function 检查密码合法性,这里语法上有点类似于JavaScript而不是Java
    onConfirm = function () : void{...};// end function
    actualLogin = function () : void{
        ......
        _account = StringUtil.trim(_accountInputTxt.text);//去空格
        if (_savedPassword != null)
            _password = _savedPassword;
        else
            _password = MD5.hash(MD5.hash(_passwordInputTxt.text));  
        //hash hash,加密传输,看来X米还是挺负责的
        if (net == "Telecom")//电信登录和网通登录,二者调用不同的函数处理
            _loginAgent.telLogin(_account, _password);
        else
            _loginAgent.cncLogin(_account, _password);
        .....
    };// end function
    loginPanel;
    if (this.verifyAccountAndPassword() == false) return;
    if (this.checkPassword(this._passwordInputTxt.text) == false)
    {
        AlarmPwdPanel.show(this, "你的密码太简单啦!英文+数字更加安全哦!", onConfirm, actualLogin);
        return;
    }
    this.actualLogin();
    return;
}// end function

随后调用telLogin、enterTel、checkLoginMethod、digitalAccountLogin以及Connection.send()

依次检查了登录类型是电信还是网通、账号类型是邮箱还是数字,最后调用Connection.send()函数将数据打包发送。

数据处理该过程如下:

public static function send(param1:int, ... args) : void
{
    args = packBody(args);
    var _loc_4:* = packHead(param1, args, args.length);
    var _loc_5:* = new ByteArray();
    _loc_5.endian = Endian.LITTLE_ENDIAN;
    _loc_5.writeBytes(_loc_4);
    _loc_5.writeBytes(args);
    if (_socket.connected == true)
    {
        _socket.writeBytes(_loc_5);
        _socket.flush();
    }
    return;
}// end function
private static function packHead(param1:uint, param2:ByteArray, param3:int) : ByteArray{
    var _loc_4:* = new ByteArray();
    _loc_4.endian = Endian.LITTLE_ENDIAN;
     _loc_4.writeUnsignedInt(Message.HEAD_LENGTH + param3);
     _loc_4.writeShort(param1);
     _loc_4.writeUnsignedInt(_uid);
    _loc_4.writeInt(0);
     var _loc_5:* = 0;
     param2.position = 0;
    var _loc_6:* = 0;
    while (_loc_6 < param3){
        _loc_5 = _loc_5 + param2.readUnsignedByte();
        _loc_6++;
    }
    _loc_5 = _loc_5 % 100000;
    _loc_4.writeInt(_loc_5);
    return _loc_4;
}// end function

private static function packBody(param1:Array) : ByteArray {
    var _loc_3:* = undefined;
    var _loc_2:* = new ByteArray();
    _loc_2.endian = Endian.LITTLE_ENDIAN;
    for each (_loc_3 in param1)
    {
    if (_loc_3 is String)
    {
    _loc_2.writeUTFBytes(_loc_3);
    continue;
    }
    if (_loc_3 is ByteArray)
    {
    _loc_2.writeBytes(_loc_3);
    continue;
    }
    _loc_2.writeUnsignedInt(_loc_3);
    }
    return _loc_2;
}// end function

代码中多处用到了as中常用的数据类型ByteArray。这一数据类型可以读写单个字节、整数、UTF,还能压缩、解压缩字节数组,最麻烦的是大小端编址的问题。Java中没有直接和其对应的数据类型,而DataInputStream不支持小端编址,且为数据流,用于存放数据有太多不便。好在Sun曾对Java的IO进行过优化,编写了ByteBuffer这一数据类型,可以读写字节、整数、字节数组,支持大小端编址。可以利用ByteBuffer实现ByteArray的基本功能。

数据处理的流程图如下:

chrome_3UxtNtS668.png

其中校验码的计算方法为:以无符号字节为单位,计算打包后的数据体之和,最后模100000。

验证

查看源码,获得发送的数据为:

Connection.send(103, password, this.getReviseTmcid(), LoginConfig.productID, 0, this.verifyCodeInfo.getVerifyImgIdData(), this.verifyCodeInfo.getVerifyCodeData(), this.getTopLeftTmcid());
//103为协议号,cid为uint = 65,productID=10,ImgIdData为16位byte0,CodeData6位byte0,TopLeftTmcid长64位,且第一位为“0”

设置代理,获得登陆时的请求数据为:

94, 0, 0, 0, 67, 0, bf, 71, bb, f, 0, 0, 0, 0, 85, 9, 0, 0, 36, 36, 66, 39, 62, 34, 64, 37, 36, 35, 65, 33, 65, 64, 37, 38, 64, 63, 63, 37, 33, 61, 37, 65, 35, 62, 37, 33, 34, 31, 58, 58, 58, 0, 0, 0, a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 //最后三位(58,58,58)略有修改

利用编写的解码工具,得到的结果为

length:148, param_t:103, uid:2639NNNN, zero:0, check:2437
66f9b4d765e3ed78dcc73a7e5b734XXX
65
10
0
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
getMd5(getMd5("****密码***"))//66f9b4d765e3ed78dcc73a7e5b734XXX

解码结果与发送结果一致,思路正确!

吐槽

as这玩意儿有点坑,markdown的代码编码都识别不出来这语言!!!

参考

https://help.adobe.com/zh_CN/as3/learn/WSf00ab63af761f1702761490412937d6fc9b-7ff5.html

Responses