Hacking the Swf Version Byte
September 10th, 2009I recently checked out the Spark Project repository and found myself browsing through some of the projects in the repo. I found an interesting actionscript 3 project called Forcible Loader decided to investigate. The Forcible Loader uses an atypical loading technique for a Flash Swf. Typically one would load a Flash Swf using the Loader class, the ForcibleLoader class however uses the URLStream object. In doing so, the Forcible Loader allows for the byte level manipulation of a swf. From the code I gather its main goal is to allow for loaded swf to be interacted with as if it were a Version 9 or greater swf. I decided to run some experiments and isolate the behaviors of the Flash virtual machine after applying varying byte level manipulations. Here are my findings :Experiment 1 :
- Load a Version 8 Swf with a :Loader and a :URLStream;
- Change the version byte value from 8 to 9 of the :URLStream loaded swf.
- Observe behavior and compare.
Result
- No observable visual difference
- atypicalLoader.contentLoaderInfo.swfVersion reports 9
Experiment 2 :
- Load a Version 8 Swf with a :Loader and a :URLStream;
- Change the version byte value from 8 to 14 of the :URLStream loaded swf.
- Observe behavior and compare.
Result
- No observable visual difference
- atypicalLoader.contentLoaderInfo.swfVersion reports 14
Experiment 3 :
- Load a Version 8 Swf with a :Loader and a :URLStream;
- Change the version byte value from 8 to 200 of the :URLStream loaded swf.
- Observe behavior and compare.
Result
- No observable visual difference
- atypicalLoader.contentLoaderInfo.swfVersion reports 200
The experiments are posted here. In each experiment a version 8 swf is loaded in realtime by a typical :Loader object and an atypical :URLStream object. The URLStream load manipulates the version byte of the swf before casting the bytes to a Loader object.Lets walk through the code that makes this magic happen :
package
{
import flash.net.URLStream;
import flash.net.URLRequest;
import flash.display.Sprite;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.utils.ByteArray;
import flash.utils.Endian;
import flash.text.TextField;
public class ExperimentOne extends Sprite
{
private var stream : URLStream;
private var atypicalLoaderArea : Sprite;
private var typicalLoaderArea : Sprite;
private var typicalLoadComplete : Boolean = false;
private var atypicalLoadComplete: Boolean = false;
private var typicalLoader : Loader;
private var atypicalLoader : Loader;
function ExperimentOne()
{
log("Experiment One")
setupLayout();
loadViaTypicalLoader();
loadViaUrlStream();
}
private function setupLayout():void
{
log("Experiment One.setupLayout()")
typicalLoaderArea = new Sprite();
typicalLoaderArea.x = 0;
atypicalLoaderArea = new Sprite();
atypicalLoaderArea.x = 400;
drawBounding( typicalLoaderArea );
drawBounding( atypicalLoaderArea );
addTextField( "Typical :Loader load", typicalLoaderArea );
addTextField( "Atypical :URLStream load", atypicalLoaderArea);
addChild(typicalLoaderArea);
addChild(atypicalLoaderArea);
}
private function addTextField( s : String, spr : Sprite ) : void
{
var t : TextField = new TextField();
t.htmlText = s;
t.width = 200;
spr.addChild(t);
}
private function drawBounding( s : Sprite):void
{
log("ExperimentOne.drawBounding()")
s.graphics.lineStyle( 3, 0x0 );
s.graphics.beginFill( 0,0);
s.graphics.drawRect(0,0,400,300);
s.graphics.endFill();
}
private function loadViaTypicalLoader() : void
{
log("ExperimentOne.loadViaTypicalLoader()")
typicalLoader = new Loader();
typicalLoaderArea.addChild(typicalLoader);
typicalLoader.contentLoaderInfo.addEventListener( Event.COMPLETE, typicalcompleteHandler)
typicalLoader.load( new URLRequest("http://extralongfingers.com/swf/versionByteManipulation/ExperimentOne/ExperimentOne_Version8_BlueCircle.swf"))
}
private function typicalcompleteHandler(e : Event):void
{
log("ExperimentOne.typicalCompleteHandler()")
typicalLoadComplete = true;
if( typicalLoadComplete && atypicalLoadComplete) discernResults();
}
private function loadViaUrlStream():void
{
log("ExperimentOne.loadViaUrlStream()")
stream = new URLStream();
stream.addEventListener(Event.COMPLETE, completeHandler);
stream.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
stream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
stream.load( new URLRequest("http://extralongfingers.com/swf/versionByteManipulation/ExperimentOne/ExperimentOne_Version8_BlueCircle.swf"))
}
private function completeHandler(e : Event):void
{
log("ExperimentOne.completeHandler()")
var swfBytes : ByteArray = new ByteArray();
stream.readBytes(swfBytes);
stream.close();
swfBytes.endian = Endian.LITTLE_ENDIAN;
updateVersion( swfBytes, 9 );
atypicalLoader = new Loader();
atypicalLoader.contentLoaderInfo.addEventListener( Event.COMPLETE, atypicalcompleteHandler)
atypicalLoader.loadBytes( swfBytes);
atypicalLoaderArea.addChild(atypicalLoader);
}
private function atypicalcompleteHandler( e : Event):void
{
atypicalLoadComplete = true;
if( typicalLoadComplete && atypicalLoadComplete) discernResults();
}
private function updateVersion( b : ByteArray, version : uint ):void
{
b[3] = version;
}
private function discernResults():void
{
log("ExperimentOne.discernResults()")
var typicalLoaderInfo : LoaderInfo = typicalLoader.contentLoaderInfo;
var atypicalLoaderInfo : LoaderInfo = atypicalLoader.contentLoaderInfo;
trace( "typical load : SWF Version : "+String(typicalLoaderInfo.swfVersion));
trace( "atypical load : SWF Version : "+atypicalLoaderInfo.swfVersion);
addSwfVersionInfoText("Swf Version contentLoaderInfo.swfVersion=
"+String(typicalLoaderInfo.swfVersion)+"", typicalLoaderArea )
addSwfVersionInfoText("Swf Version contentLoaderInfo.swfVersion=
"+String(atypicalLoaderInfo.swfVersion)+"", atypicalLoaderArea )
}
private function addSwfVersionInfoText( version : String, s : Sprite):void
{
var t : TextField = new TextField();
t.htmlText = version;
t.width = 400;
t.height = 200;
t.y = s.height;
t.x = s.x;
addChild( t);
}
private function ioErrorHandler( e : IOErrorEvent):void
{
log( e.toString());
}
private function securityErrorHandler( e : SecurityErrorEvent):void
{
log( e.toString());
}
private function log(s : String ) : void
{
trace( s );
}
}
}
function : ExperimentOne() // ConstructorThis function sets up the environment by first using the setupLayout() method to build two "bounding" Sprites. These sprites serve as the holders into which a loaded swf will be added as a child. The left bounding sprite will hold the swf loaded by the typical :Loader object and the right bounding sprite will hold the swf loaded by the atypical :URLStream object. The constructor then proceeds to initiate these two loading operations
function :setupLayout()This function builds the two bounding Sprites that will serve as holders for the :Loader loaded swf and the :URLStream loaded swf. It also appends :TextField labels via the addTextField() method.
function addTextField()This function creates a new text :TextField, sets its htmlText to the s : String parameter, and adds the :TextField as a child of the spr : Sprite parameter
function drawBounding()This function draws a bounding rectangle with dimension 400x300 in the s : Sprite parameterfunction loadViaTypicalLoader()This function creates a typicalLoader object and loads a version 8 swf from a hardcoded url.
function typicalcompleteHandler()This function fires when the typicalLoader object has finished its load of the version 8 swf. It marks the typicalLoadComplete :Boolean variable to true and checks to see whether the other atypical load has completed as well.
function loadViaUrlStream()This function creates a URLStream object and loads the same swf that was loaded in the function loadViaTypicalLoader(). The difference here is that the URLStream object will allow for byte level manipulation of the loaded swf once loading is complete. That complete handler will be where the byte level version hacking takes place
function completeHandler()This function first creates a :ByteArray and copies the bytes from the :URLStream object to the byte array. Once complete it passes this byte array to the updateVersion() method which will alter the fourth byte of the byte array. It then creates the atypicalLoader which will load the bytes of this now altered :ByteArray.
function atypicalcompleteHandler()This event handler will fire when the atypicalLoader.loadBytes( swfBytes ) operation has completed. The atypicalLoaderComplete Boolean will be set to true, and a check will run that determines whether both the typical load and the atypical load have completed.
function updateVersion()This function accept a ByteArray as its first parameter and a :uint as its second parameter. The function expects that the ByteArray being passed is a swf and sets the 4th byte of that byte array equal to the uint passed. This byte is the storage location for a swf's version.function discernResults()This function prints the contentLoaderInfo.swfVersion value for both the typical and atypically loaded swfs. Its seems that based on the results of experiment 1, 2, and 3 it is reasonable to say that this getter function reads the 4th byte of a swf directly. Questions that arise naturally and could be experimented upon further are : What happens if one loads a swf with a :URLStream, changes its version byte, loadBytes into a Loader Object, read the contentLoaderInfo.swfVersion, convert back to Byte Array, alters the 4th byte again, convert back to a :Loader object, re-access the contentLoaderInfo.swfVersion, and print the version? What happens if one sets the byte to a negative number? Is that possible ? I am going to continue along these lines and see what more I can discern regarding these and other questions.
function addSwfVersionInfoText()This function prints the swf version reported by the contentLoaderInfo.swfVersion for each :Loader object, both typical and atypical
If you have any interest in playing with this code and carrying out some of the further investigations I mentioned please feel free to git clone the code from here :
git clone git://github.com/blackberryoctopus/Actionscript-Swf-Loading-Experiments.git ./loadingExperiments





Each view specified on the command line created a mxml file in the root directory of the namespace and a corresponding controller in the com.extralongfingers.mynewapplication.client.controllers directory. If you run the script you will see that each mxml view registers itself with the controller that shares its name. The script also creates an application mxml file call Mynewapplication.mxml in the root of the directory structure. This mxml application serves as the entry point for the app and proceeds to run the BuildViews Command upon initialization wherein all the mxml views are instantiated and proceed to link up with their controllers. I'm planning to clean up this perl a bit in order to modularize things a bit more and hope to submit to cpan soon. Let me know what you think and if you found this useful. I've been doing a lot with code generation lately so come back soon for my AMF PHP remoting gateway generator.