SWF代码分析与破解之路 (YueTai VIP视频信息获取工具) Socket续篇

引言

上一篇 《Socket与网站保密应用 (隐藏链接的视频下载)》大大咧咧地从 WEB 讲 Socket,再到 TCP/IP 等协议,又再讲到 Wireshark 如何抓IP包分析,最还要复习一下路由与网络的知识,真的涉及面太广了,只能蜻蜓点水一一带过,不过将多领域的知识串烧也是不错的,可以起到一个归纳的作用。这篇针对 Flash 来进行,写作思路以解决问题的过程行为线索。依次展示如何使用 Flex Air 的 ServerSocket 和 Socket 实现简化版本的 HTTP 服务器,以及如何加载外部的 SWF 文件并进行操作。

在 mplayer.swf 内部,使用 FlasCC 集成 C 代码做保护。使用了 Hessian 做对象序列化,Hessian是一个由Caucho Technology开发的轻量级二进制RPC协议。Hessian 自称为 binary web service protocol
,看来加了个 protocol 的都不简单了,好几个平台的版本都出来了。FlasCC 的加密部分似乎和 cmodule.usemd5 包下的 FSM_encode 这个类有关,这里截一片代码出来,看有没人了解一二:

    this.i2 = mstate.ebp + -96;
    this.i2 = this.i2 + 24;
    this.i11 = this.i2 + this.i12;
    this.i14 = this.i8;
    this.i15 = this.i13;
    memcpy(this.i11, this.i14, this.i15);
    mstate.esp = mstate.esp - 8;
    mstate.esp = mstate.esp - 4;
    this.start();
    mstate.esp = mstate.esp + 8;
    this.i2 = this.i13 + 64;

由于没有完全掌握 FlasCC ,所以没有深入,这里路过一下了,还是从 SWF 载入流程出发。通过调试得到,mvplayer 的执行是从这里开始的 com.yinyuetai.mvplayer.player.InPlayer > Player。抓主线,理清了 videoId 是如何和视频信息关联的,主要代码贴一贴,有点长,但这部分是最小功能 F**K CODE 之前,来两张AOA福利滋润一下各位看官:)。


//com.yinyuetai.mvplayer.player.player extends Sprite implements IPlayer
    public function loadByVideoId(id:String) : void
    {
        var key:String = null;
        var url:String = null;
        var req:GetRequest = null;
        var cLoader:* = new CLibInit();
        var cLib:* = cLoader.init();
        var stamp:String = UTCDateCreator.utcfiveminute();
        key = cLib.encode(":" + id + ":" + stamp + ":" + PlayerVersion.version);
        // cLib.encode(":2344403:4797398:1.8.2.2") => ee4fb9b09346bd933dd22e37fe978741 32Byte
        var reg:RegExp = /^(http:\/\/.*?)\/.*/;
        if (ServiceConfig.GET_VIDEOINFO_URL.indexOf("http") >= 0)
        {
            url = ServiceConfig.GET_VIDEOINFO_URL;
        }
        else
        {
            url = RootReference.stage.loaderInfo.url.replace(reg, "$1") + ServiceConfig.GET_VIDEOINFO_URL;
        }
        req = new GetRequest(url);
        req.addEventListener(RequestEvent.RESPONSE, this.onAfterGetVideoInfo);
        req.doRequest({videoId:id, sc:key, t:stamp, v:PlayerVersion.version});
        // http://www.yinyuetai.com/main/get-mv-info?
        // flex=true&sc=ee4fb9b09346bd933dd22e37fe978741&t=4797398&v=1.8.2.2&videoId=2344403
        // Content-Type:binary/hessian; charset=UTF-8
        // data["videoInfo"] as MvSiteVideoInfo;
        return;
    }

    private function onAfterGetVideoInfo(event:RequestEvent) : void
    {
        var videoInfo:MvSiteVideoInfo = null;
        var coreVInfo:CoreVideoInfo = null;
        var pe:PlayerEvent = null;
        var evInfo:ExVideoInfo = null;
        var listItem:PlaylistItem = null;
        var o:Object = event.responseData;
        var isNOK:Boolean = o["error"] as Boolean;
        var msg:String = o["message"] as String;
        if (!isNOK)
        {
            videoInfo = o["videoInfo"] as MvSiteVideoInfo;
            coreVInfo = videoInfo.coreVideoInfo;
            if (coreVInfo.error)
            {
                pe = new PlayerEvent(PlayerEvent.MVPLAYER_ERROR, coreVInfo.errorMsg);
                dispatchEvent(pe);
            }
            else
            {
                evInfo = new ExVideoInfo(videoInfo);
                this.config.exVideoInfo = evInfo;
                listItem = new PlaylistItem();
                listItem.init(coreVInfo);
                this.load(listItem);
                this.model.config.setConfig(coreVInfo);
            }
        }
        else
        {
            pe = new PlayerEvent(PlayerEvent.MVPLAYER_ERROR, msg);
            dispatchEvent(pe);
        }
        return;
    }


//com.yinyuetai.mvplayer.model.ExVideoInfo extends Object
    public var pageUrl:String;
    public var headImage:String;
    public var bigHeadImage:String;

    public function ExVideoInfo(o:Object)
    {
        var reg:RegExp = /^(http:\/\/.*?)\/.*/;
        var srvURL:String = ServiceConfig.REMOTE_SERVER_URL;
        if (o.hasOwnProperty("bigHeadImage") && o["bigHeadImage"])
        {
            if (o["bigHeadImage"].indexOf("http") < 0)
            {
                this.bigHeadImage = (srvURL.indexOf("http") < 0 ? 
                    (RootReference.stage.loaderInfo.url.replace(reg, "$1")) : ("")) + 
                    srvURL + StringUtil.trim(o["bigHeadImage"]);
            }
            else
            {
                this.bigHeadImage = StringUtil.trim(o["bigHeadImage"]);
            }
        }
        if (o.hasOwnProperty("pageUrl") && o["pageUrl"])
        {
            this.pageUrl = o["pageUrl"].indexOf("http") < 0 ? 
                (srvURL + StringUtil.trim(o["pageUrl"])) : (StringUtil.trim(o["pageUrl"]));
        }
        if (o.hasOwnProperty("headImage") && o["headImage"])
        {
            this.headImage = o["headImage"].indexOf("http") < 0 ? (srvURL + StringUtil.trim(o["headImage"])) : (StringUtil.trim(o["headImage"]));
        }
        if (o.hasOwnProperty("secretKeyUri") && o["secretKeyUri"])
        {
            ServiceConfig.secretKeyURL = o["secretKeyUri"];
        }
        return;
    }


//com.yinyuetai.flex.GetRequest extends RequestBase

    public function GetRequest(param1:String = null, param2:URLVariables = null, param3:IUpdatable = null, param4:IResponseDecoder = null) : void
    {
        super(URLRequestMethod.GET, param1, param2, param3, param4);
        return;
    }

//com.yinyuetai.flex.RequestBase extends EventDispatcher

    private var _method:String;
    private var _url:String;
    private var _params:URLVariables;
    private var _updatable:IUpdatable;
    private var _decoder:IResponseDecoder;
    private static var _addFlexParam:Boolean = true;

    public function RequestBase(m:String, url:String, data:URLVariables = null,
            up:IUpdatable = null, deco:IResponseDecoder = null) : void
    {
        this._method = m;
        this._url = url;
        this._params = data;
        this._updatable = up;
        this._decoder = deco;
    }

    protected function onComplete(event:Event) : void
    {
        var requestEvent:RequestEvent;
        var responseData:Object;
        var event:* = event;
        var stream:* = event.target as URLStream;
        var buffer:* = new ByteArray();
        var offset:uint;
        do
        {
            stream.readBytes(buffer, offset, stream.bytesAvailable);
            offset = offset + stream.bytesAvailable;
            var ba:int = stream.bytesAvailable;
        }while (ba > 0)
        stream.close();
        if (!this._decoder)
        {
            this._decoder = new HessianDecoder();
        }
        try
        {
            responseData = this._decoder.decode(buffer);
            if (this._updatable)
            {
                this._updatable.responseData = responseData;
            }
            requestEvent = new RequestEvent(RequestEvent.RESPONSE);
            requestEvent.responseData = responseData;
            dispatchEvent(requestEvent);
        }
        catch (ex:DecodeError)
        {
            requestEvent = new RequestEvent(RequestEvent.ERROR);
            requestEvent.errorMessage = ex.message;
            dispatchEvent(requestEvent);
            if (_updatable)
            {
                _updatable.error(ex.message);
            }
        }
        dispatchEvent(new RequestEvent(RequestEvent.END));
        if (this._updatable)
        {
            this._updatable.end();
        }
        return;
    }// end function


//com.yinyuetai.mvplayer.utils.UTCDateCreator extends Object
    public static function get utcfiveminute() : String
    {
        var r:int = null;
        var d:Date = new Date();
        r = int(d.getTime() / 300000).toString();
        return r;
    }

//package com.yinyuetai.mvplayer.player.PlayerVersion extends Object
    static const VERSION:String = "1.8.2.2";


//com.yinyuetai.mvplayer.ServiceConfig extends Object
    public static const REMOTE_SERVER_URL:String = "http://www.yinyuetai.com";
    public static const GET_VIDEOINFO_URL:String = REMOTE_SERVER_URL + "/main/get-mv-info";
    public static var secretKeyURL:String;

//com.yinyuetai.mvplayer.pojos.MvSiteVideoInfo extends Object
    public var coreVideoInfo:CoreVideoInfo;
    public var secretKeyUri:String;
    public var pageUrl:String;

//com.yinyuetai.mvplayer.pojos.CoreVideoInfo extends Object
    public var artistIds:String = "";
    public var artistNames:String = "";
    public var headImage:String = "";
    public var bigHeadImage:String = "";
    public var duration:int = 0;
    public var error:Boolean = false;
    public var errorMsg:String = "";
    public var like:Boolean = false;
    public var threeD:Boolean = false;
    public var videoId:int = 0;
    public var videoName:String = "";
    public var videoUrlModels:Array = null;

//com.yinyuetai.mvplayer.pojos.VideoUrlModel extends Object
    public var qualityLevelName:String;
    public var videoUrl:String;
    public var bitrateType:int;
    public var bitrate:int;
    public var sha1:String;
    public var fileSize:int;
    public var md5:String;

以上代码通过追踪 loadByVideoId,可以发现 RequestBase 这个很关键的类,它使用了 IResponseDecoder 接口,实现了对象的逆序列化,这个过程是 hessian 类包实现的功能,主要是使用 Hessian2Input.readObject(), hessian-flash-4_0-snap.swc 就是应现在使用的版本,如果使用低版本会不认识MAGIC 0x48:

    Error: unknown code: 0x48 H
        at hessian.io::Hessian2Inpu。

有了这些后面会比较好办,稍为花点心思。先来用 Loader 将 mvplayer.swf 加载往来,然后调用它的 loadByVideoId,所以只需要提供一个 ID 就可以得到连接信息了,一条含有 get-mv-info 的数据请求,FlasCC 的逆向也免了。当然,使用这种方法也面临两个难题:

1. 沙箱安全约束。
2. 本地加载时,不允许使用 allowDomain() 方法,所以需要以服务端加载。

因为沙箱安全约束,即使用加载 mvplayer.swf 也不能使它读取到 yinyuetai.com 服务器的数据,因为改变了 mvplayer.swf 的位置后,原来的 yinyuetai.com 已经变成第三方网站了,所以要在它身上取数据,就要通过 crossDomain.xml 策略文件来授权。这个授权有点问题, 我试了一下本地做了一个 hack 版本:

<cross-domain-policy>
        <allow-access-from domain="*"/>
</cross-domain-policy>

上面这个策略文件就是一个完全开放许可,我需要做的就是把本地 hosts 文件修改一下,加入下面一行:

127.0.0.1   www.yinyuetai.com

再次调用 mvplayer.swf 时,它依然是从 www.yinyuetai.com 请求数据,只是这次它的请求被路由到了本地的环回接口,我只需做一个 HTTP 服务提供上面的策略文件即可以解决授权,当 mvpayer.swf 认为得到授权后,还需要它来获取数据,所以要再次修改 hosts 文件,去掉之前的修改内容,这样就麻烦是有点,不过它真的 WORKING 了!

退一步来考虑,可以不需要 mvplayer.swf 获取数据,只要得到它间接产生的数据链接即可以达到破解的目的了。于是,我做了一个通过 ServerSocket 实现的 HTTP 服务,mvplayer.swf 就从这个 HTTP 服务加载,然后就让它产生一个数据请求,我需要做的是捕捉这个即将产生异常的数据请求。可惜的是,通过全局异常捕捉可以处理文档类 Player 产生的异常,但进入到 RequestBase 类后,Flash UncaughtErrorEvents 全局异常处理就真的异常了,完全不工作。而且使用 AIR 运行时,根本连异常的警告窗都不弹出来,所以有用的数据连接完全处理不了:

=========================================================================
Error #2044: Unhandled SecurityErrorEvent:. text=Error #2048: Security sandbox violation: http://127.0.0.1/mvplayer.swf cannot load data from http://www.yinyuetai.com/main/get-mv-info?flex=true&sc=54e4b6daef63eb263d80e97ae4bb775a&t=4797652&v=1.8.2.2&videoId=2344403.
    at com.yinyuetai.flex::RequestBase/doRequest()
    at com.yinyuetai.mvplayer.player::Player/loadByVideoId()
    at Main/parseCMD()
    at Main/doKeyUp()
=========================================================================

到这里,问题似乎变得更复杂了,难道非要走上抓 IP 包的不归路?好吧,看 TCPViewr 这个小工具是怎么做的,看看如何自己写代码做一个 Tiny Sniffer,这只能先想想的法子,基本上也是一条不归路吧,以后有空再去搞。先要以简单的方法来解决问题,想想,想想,再想想!真的还是有的,而且极为便利,可以说随手拈来。那就是让 mvplayer.swf 请求数据时,去本地的服务器取数据了,本地服务器最多就是产生一条 404 回复,因此就不会产生不可以处理的异常了,太好了,就这样干了,使用 Hex Workshop 来找找 www.yinyuetai.com 来改掉:

=========================================================================
00033F40 74 74 70 3A 2F 2F 70 70 6C 73 69 73 2E 63 68 69 ttp://pplsis.chi
00033F50 6E 61 63 61 63 68 65 2E 6E 65 74 2F 66 69 6C 65 nacache.net/file
00033F60 11 52 45 4D 4F 54 45 5F 53 45 52 56 45 52 5F 55 .REMOTE_SERVER_U
00033F70 52 4C 18 68 74 74 70 3A 2F 2F 31 32 37 2E 31 30 RL.http://127.10
00033F80 30 2E 31 30 30 2E 31 30 3A 38 30 18 52 45 4D 4F 0.100.10:80.REMO
00033F90 54 45 5F 53 54 41 54 49 43 5F 53 45 52 56 45 52 TE_STATIC_SERVER
00033FA0 5F 55 52 4C 18 68 74 74 70 3A 2F 2F 73 2E 63 2E _URL.http://s.c.
=========================================================================
看上的服务器已经变成 127.100.100.10:80 了,处理后让 YueTai VIP 跑跑看,我的小工具叫“月台微挨披”,list 是命令,输入命令 list ID 就获取对应 ID 的视频数据:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值