Friday, June 24, 2011

Decompiling Facebook's Flash XDComm Receiver..

Within Facebook's all.js file, there is a reference to a _swfPath that refers to http://static.ak.fbcdn.net/rsrc.php/v1/yx/r/WFg56j28XFs.swf. It's used for Internet Explorer installed with Flash to handle cross-domain requests. Ever wonder how it works? Well, you can do a wget and decompile it.

Decompiling the XDComm.swf file: None of the open source Flash decompilers work to decompile Facebook's XDComm Receiver, since it's compiled in Adobe Flex v4 (released apparently in March 2010). You can use the Sothink SWF Decompiler tool, but without paying $70.00, the program doesn't even allow you to export the files to ActionScript. Nonetheless, the unregistered version (as of the 6.00 version) limits you from decrypting more than the first two resources, but there's enough in the tool that allows you to save parts of the code.

Here are flashutils\PostMessage.as and XdComm.as. Hopefully it gives you a better idea of how the Flash cross-domain receiver works.

The files can also be downloaded from here:

http://bit.ly/kAJ7AJ
http://bit.ly/ltGkTF

Apparently the XdComm utilizes the LocalConnection library to communicate with the HTML page. It used the send/receive functionality and the FB.XD._origin variable to determine what the connection name should be used.

In http://hustoknow.blogspot.com/2011/06/how-flash-xdcomm-works-in-facebook.html, we'll walk through in more detail how the Flash XDComm Receiver works with Facebook.

XdComm.as:

package 
{
    import flash.display.*;
    import flash.events.*;
    import flash.external.*;
    import flash.net.*;
    import flash.system.*;
    import flashutils.*;

    public class XdComm extends Sprite
    {
        private var _cache:SharedObject;
        private var _cacheContext:String = "unknown";
        private var postMessage:PostMessage;
        private static var requestIdCount:int = 0;

        public function XdComm()
        {
            XdComm.fbTrace("XdComm Initialized", {});
            Security.allowDomain("*");
            Security.allowInsecureDomain("*");
            this.addEventListener(Event.ENTER_FRAME, this.init);
            return;
        }// end function

        private function init(event:Event) : void
        {
            XdComm.fbTrace("XdComm.init", {});
            this.removeEventListener(Event.ENTER_FRAME, this.init);
            this.postMessage = new PostMessage();
            ExternalInterface.addCallback("sendXdHttpRequest", this.sendXdHttpRequest);
            ExternalInterface.addCallback("setCache", this.setCache);
            ExternalInterface.addCallback("getCache", this.getCache);
            ExternalInterface.addCallback("setCacheContext", this.setCacheContext);
            ExternalInterface.addCallback("clearAllCache", this.clearAllCache);
            ExternalInterface.call("FB_OnFlashXdCommReady");
            return;
        }// end function

        public function sendXdHttpRequest(param1:String, param2:String, param3:String, param4) : int
        {
            var loader:URLLoader;
            var reqId:int;
            var loaded:Function;
            var key:String;
            var value:String;
            var method:* = param1;
            var url:* = param2;
            var requestBody:* = param3;
            var extraHeaders:* = param4;
            loaded = function (event:Event) : void
            {
                var _loc_2:* = loader.data.toString();
                XdComm.fbTrace("Requested completed", {data:_loc_2});
                ExternalInterface.call("FB_OnXdHttpResult", reqId, encodeURIComponent(_loc_2));
                return;
            }// end function
            ;
            XdComm.fbTrace("SendXdHttpRequest", {method:method, url:url, requestBody:requestBody, extraHeaders:extraHeaders});
            if (url.indexOf("https://") != 0 && url.indexOf("http://") != 0)
            {
                url = "http://" + url;
            }
            var host:* = PostMessage.extractDomain(url);
            if (host != "api.facebook.com" && host != "graph.facebook.com" && !/^(api|graph)\.[A-Za-z0-9\-\.]+\.facebook\.com$""^(api|graph)\.[A-Za-z0-9\-\.]+\.facebook\.com$/.test(host))
            {
                return 0;
            }
            var _loc_6:* = XdComm;
            var _loc_7:* = XdComm.requestIdCount + 1;
            _loc_6.requestIdCount = _loc_7;
            var req:* = new URLRequest(url);
            loader = new URLLoader();
            reqId = XdComm.requestIdCount;
            req.method = method;
            req.data = requestBody;
            if (extraHeaders != null)
            {
                var _loc_6:int = 0;
                var _loc_7:* = extraHeaders;
                while (_loc_7 in _loc_6)
                {
                    
                    key = _loc_7[_loc_6];
                    value = extraHeaders[key];
                    req.requestHeaders.push(new URLRequestHeader(key, value));
                }
            }
            loader.addEventListener(Event.COMPLETE, loaded);
            loader.load(req);
            return reqId;
        }// end function

        private function setCacheContext(param1:String) : void
        {
            if (param1 == null)
            {
                param1 = "unknown";
            }
            this._cacheContext = param1;
            return;
        }// end function

        private function clearAllCache() : void
        {
            this.cache.clear();
            this.cache.flush();
            return;
        }// end function

        private function getCache(param1:String) : String
        {
            return this.contextCache[param1];
        }// end function

        private function setCache(param1:String, param2:String) : void
        {
            var _loc_3:* = this.contextCache;
            _loc_3[param1] = param2;
            this.cache.flush();
            return;
        }// end function

        private function get cache() : SharedObject
        {
            if (this._cache == null)
            {
                this._cache = SharedObject.getLocal("cache");
            }
            return this._cache;
        }// end function

        private function get contextCache() : Object
        {
            var _loc_1:* = this.cache.data[this._cacheContext];
            if (_loc_1 == null)
            {
                _loc_1 = new Object();
                this.cache.data[this._cacheContext] = _loc_1;
            }
            return _loc_1;
        }// end function

        public static function traceObject(param1:Object, param2:int = 0, param3:String = "")
        {
            var _loc_6:* = undefined;
            var _loc_7:String = null;
            var _loc_4:String = "";
            var _loc_5:int = 0;
            while (_loc_5 < param2)
            {
                
                _loc_4 = _loc_4 + "\t";
                _loc_5++;
            }
            for (_loc_6 in param1)
            {
                
                param3 = param3 + (_loc_4 + "[" + _loc_6 + "] => " + param1[_loc_6]);
                _loc_7 = traceObject(param1[_loc_6], (param2 + 1));
                if (_loc_7 != "")
                {
                    param3 = param3 + (" {\n" + _loc_7 + _loc_4 + "}");
                }
                param3 = param3 + "\n";
            }
            if (param2 == 0)
            {
            }
            else
            {
                return param3;
            }
            return;
        }// end function

        public static function fbTrace(param1:String, param2:Object) : void
        {
            traceObject(param2);
            return;
        }// end function

    }
}

...and postMessage.as:
package flashutils
{
    import flash.events.*;
    import flash.external.*;
    import flash.net.*;
    import flash.utils.*;

    public class PostMessage extends Object
    {
        private var currentDomain:String;
        private var callback:String;
        private var connection:LocalConnection;
        private var connectionName:String;

        public function PostMessage()
        {
            this.connection = new LocalConnection();
            this.connection.client = this;
            this.connection.connect(Math.random().toString());
            ExternalInterface.addCallback("postMessage_init", this.init);
            ExternalInterface.addCallback("postMessage_send", this.send);
            return;
        }// end function

        public function getCurrentDomain() : String
        {
            if (!this.currentDomain)
            {
                try
                {
                    this.currentDomain = ExternalInterface.call("self.document.domain.toString");
                    this.fbTrace("getCurrentDomain", {currentDomain:this.currentDomain});
                }
                catch (e)
                {
                    logError("getCurrentDomain error", e);
                }
            }
            return this.currentDomain;
        }// end function

        public function onFacebookDomain() : Boolean
        {
            return /(^|\.)facebook\.com$""(^|\.)facebook\.com$/.test(this.getCurrentDomain()) || /(^|\.)fbcdn\.net$""(^|\.)fbcdn\.net$/.test(this.getCurrentDomain());
        }// end function

        public function init(param1:String, param2:String) : void
        {
            var cb:* = param1;
            var name:* = param2;
            this.fbTrace("init", {cb:cb, name:name});
            try
            {
                if (!this.onFacebookDomain() && PostMessage.extractDomain(name) != this.getCurrentDomain())
                {
                    this.logError("init", "name must be a URL on the current domain: " + name);
                }
                else
                {
                    this.callback = cb;
                    if (name == this.connectionName)
                    {
                        return;
                    }
                    this.connectionName = name;
                    name = encodeURIComponent(name);
                    this.connection = new LocalConnection();
                    this.connection.client = this;
                    this.connection.connect(name);
                }
            }
            catch (e)
            {
                logError("init", e.toString());
            }
            return;
        }// end function

        public function send(param1:String, param2:String) : void
        {
            var msg:* = param1;
            var name:* = param2;
            this.fbTrace("send", {name:name, msg:msg});
            if (!this.connection)
            {
                this.logError("send", "connection has not been initialized.");
                return;
            }
            try
            {
                name = encodeURIComponent(name);
                this.connection.send(name, "recv", msg);
            }
            catch (e)
            {
                logError("send", e.toString() + ". name: " + name + ", msg: " + msg);
            }
            return;
        }// end function

        public function recv(param1:String) : void
        {
            var deliverMessage:Function;
            var msg:* = param1;
            deliverMessage = function (event:TimerEvent) : void
            {
                var evt:* = event;
                try
                {
                    ExternalInterface.call(callback, encodeURIComponent(msg));
                }
                catch (e)
                {
                    logError("recv", e.toString());
                }
                return;
            }// end function
            ;
            this.fbTrace("recv", {msg:msg});
            var timer:* = new Timer(1, 1);
            timer.addEventListener(TimerEvent.TIMER, deliverMessage);
            timer.start();
            return;
        }// end function

        private function logError(param1:String, param2:Object) : void
        {
            XdComm.fbTrace("Error: XdComm.PostMessage." + param1, param2);
            return;
        }// end function

        private function fbTrace(param1:String, param2:Object) : void
        {
            XdComm.fbTrace("XdComm.PostMessage." + param1, param2);
            return;
        }// end function

        public static function extractDomain(param1:String) : String
        {
            return /^\w+:\/\/([^\/:]*)""^\w+:\/\/([^\/:]*)/.exec(param1)[1];
        }// end function

    }
}

2 comments:

  1. Did you know that you can create short links with AdFocus and receive cash from every click on your shortened urls.

    ReplyDelete

  2. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a .Net developer learn from Dot Net Training in Chennai. or learn thru Dot Net Training in Chennai. Nowadays Dot Net has tons of job opportunities on various vertical industry.
    or Javascript Training in Chennai. Nowadays JavaScript has tons of job opportunities on various vertical industry.

    ReplyDelete