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

引言

上一篇 《Socket与站点保密应用 (隐藏链接的视频下载)》大大咧咧地从 WEB 讲 Socket。再到 TCP/IP 等协议,又再讲到 Wireshark 怎样抓IP包分析。最还要复习一下路由与网络的知识。真的涉及面太广了,仅仅能蜻蜓点水一一带过,只是将多领域的知识串烧也是不错的,能够起到一个归纳的作用。这篇针对 Flash 来进行。写作思路以解决这个问题的过程行为线索。

依次展示怎样使用 Flex Air 的 ServerSocket 和 Socket 实现简化版本号的 HTTP server,以及怎样载入外部的 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 server的数据,由于改变了 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 请求数据时。去本地的server取数据了,本地server最多就是产生一条 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.
=========================================================================
看上的server已经变成 127.100.100.10:80 了,处理后让 YueTai VIP 跑跑看,我的小工具叫“月台微挨披”。list 是命令,输入命令 list ID 就获取相应 ID 的视频数据:

=========================================================================
list 2344403
Parsing command: list 2344403
Parse request: GET /main/get-mv-info?flex=true&sc=57dc2e7b639a3d74f87a295f22abc04a&t=4797738&v=1.8.2.2&videoId=2344403 HTTP/1.1

GET: /main/get-mv-info?flex=true&sc=57dc2e7b639a3d74f87a295f22abc04a&t=4797738&v=1.8.2.2&videoId=2344403

parseVideo Info: http://www.yinyuetai.com/main/get-mv-info?flex=true&sc=57dc2e7b639a3d74f87a295f22abc04a&t=4797738&v=1.8.2.2&videoId=2344403

Length: 1753
Video info: [object Object]
CoreVideoInfo: [object Object]
videoUrlModels: [object Object],[object Object],[object Object],[object Object]
========
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值