I've taken some immense pains with my more recent sites to keep them from being decompiled. I think I've finally hit on a multi-faceted approach that will really protect my as3 source.
I don't really care much about the code on my home site being decompiled, but I've applied these techniques to it as a way of testing. Can anyone find their way into the source code of the as3 file at joshstrike.com? I'd like to find out if my methods are effective...
dynamic public class MainTimeline extends MovieClip
{
public var fontsLoaded:Boolean;
public var coreBytesLoaded:Number;
public var soundPolicy:SoundLoaderContext;
public var fontBytesTotal:Number;
public var soundstream:URLRequest;
public var t:Timer;
public var coreReq:URLRequest;
public var coreLoaded:Boolean;
public var fontBytesLoaded:Number;
public var coreBytesTotal:Number;
public var coreLoader:Loader;
public var mr:Number;
public var fontLoader:Loader;
public var fontReq:URLRequest;
public function MainTimeline()
{
addFrameScript(0, frame1);
return;
}// end function
public function processFinished() : void
{
var _loc_1:Class;
if (fontsLoaded && coreLoaded)
{
removeChild(this.loadBar);
coreLoader.contentLoaderInfo.removeEventListener(P rogressEvent.PROGRESS, this.coreLoadProg);
fontLoader.contentLoaderInfo.removeEventListener(P rogressEvent.PROGRESS, this.fontLoadProg);
addChild(coreLoader.contentLoaderInfo.content);
_loc_1 = fontLoader.contentLoaderInfo.applicationDomain.get Definition("Fonts") as Class;
Font.registerFont(_loc_1.univers55);
Font.registerFont(_loc_1.univers55oblique);
Font.registerFont(_loc_1.univers65);
Font.registerFont(_loc_1.univers65oblique);
Font.registerFont(_loc_1.Trixie);
Font.registerFont(_loc_1.Poplar);
Font.registerFont(_loc_1.hp14);
coreLoader.contentLoaderInfo.content["univers55font"] = new _loc_1["univers55"];
coreLoader.contentLoaderInfo.content["univers55Ofont"] = new _loc_1["univers55oblique"];
coreLoader.contentLoaderInfo.content["univers65font"] = new _loc_1["univers65"];
coreLoader.contentLoaderInfo.content["univers65Ofont"] = new _loc_1["univers65oblique"];
coreLoader.contentLoaderInfo.content["trixiefont"] = new _loc_1["Trixie"];
coreLoader.contentLoaderInfo.content["labelfont"] = new _loc_1["Poplar"];
coreLoader.contentLoaderInfo.content["hp14font"] = new _loc_1["hp14"];
var _loc_2:* = coreLoader.contentLoaderInfo.content;
_loc_2.coreLoader.contentLoaderInfo.content["initializeAll"]();
this.getUserIP();
}// end if
return;
}// end function
public function coreLoadProg(param1:ProgressEvent) : void
{
if (coreBytesTotal == 0)
{
coreBytesTotal = param1.bytesTotal;
}// end if
coreBytesLoaded = param1.bytesLoaded;
this.updateLoader();
return;
}// end function
public function updateLoader() : void
{
var bt:Number;
var bl:Number;
var ls:Sprite;
bt = coreBytesTotal + fontBytesTotal;
bl = coreBytesLoaded + fontBytesLoaded;
if (coreBytesTotal > 0 && fontBytesTotal > 0 && !this.loadBar)
{
this.loadBar = new Sprite();
this.lbTxt = new TextField();
this.lbTxt.text = Math.round(bl / 1024) + " / " + Math.round(bt / 1024);
this.lbTxt.setTextFormat(new TextFormat("Arial", 9, 0));
this.lbTxt.y = 4;
this.lbTxt.x = 56;
this.loadBar.addChild(this.lbTxt);
ls = new Sprite();
var _loc_2:* = ls;
with (ls)
{
graphics.lineStyle(1, 0, 0.6, true);
graphics.moveTo(0, -2);
graphics.lineTo(0, 6);
graphics.moveTo(50, -2);
graphics.lineTo(50, 6);
graphics.moveTo(100, -2);
graphics.lineTo(100, 6);
}// end with
this.loadBar.addChild(ls);
this.loadBar.y = 6;
this.loadBar.x = 6;
addChild(this.loadBar);
}// end if
if (this.loadBar)
{
var _loc_2:* = this.loadBar;
with (this.loadBar)
{
graphics.clear();
graphics.beginFill(13369344, 0.6);
graphics.drawRect(0, 0, bl / bt * 100, 4);
graphics.endFill();
}// end with
this.lbTxt.text = Math.round(bl / 1024) + " / " + Math.round(bt / 1024);
this.lbTxt.setTextFormat(new TextFormat("Arial", 9, 0));
}// end if
return;
}// end function
public function getUserIP() : void
{
if (ExternalInterface.available && ExternalInterface.objectID != null)
{
ExternalInterface.addCallback("getip", coreLoader.contentLoaderInfo.content["executeCallback"]);
ExternalInterface.call("callExternalInterface");
}
else
{
var _loc_1:* = coreLoader.contentLoaderInfo.content;
_loc_1.coreLoader.contentLoaderInfo.content["executeCallback"]("Not.a.browser.!!!");
}// end else if
return;
}// end function
public function gotFontLoad(param1:Event) : void
{
fontsLoaded = true;
this.processFinished();
return;
}// end function
public function failedLoad(param1:IOErrorEvent) : void
{
return;
}// end function
public function gotCoreLoad(param1:Event) : void
{
coreLoaded = true;
this.processFinished();
return;
}// end function
public function fontLoadProg(param1:ProgressEvent) : void
{
if (fontBytesTotal == 0)
{
fontBytesTotal = param1.bytesTotal;
}// end if
fontBytesLoaded = param1.bytesLoaded;
this.updateLoader();
return;
}// end function
YES! Thank you, thank you!!! I knew it could be done. What decompiler did you use?
I'd heard that the latest ******* decompiler can get around GuardBunny, but it's unclear how exactly it's doing it. It might just be searching forward for the GuardBunny class name. Maybe it's actually short-circuiting try/catch statements. I'm assuming all AS3 decompilers still have to actually run the bytecode in a VM in order to function; am I wrong about that?
Anyway, I've devised what I'm hoping is a devilishly more sophisticated approach that, rather than relying on external variables or an external class, takes advantage of the VM's own security protections. Care to give this one a try???
I used the HP security tool. I highly doubt it's specifically looking for GuardBunny, so I'm guessing that it does not in fact have to run the bytecode.
And by the way, the end goal here isn't to stop the NSA from cracking my code; just to fuxr with enough different decompilers to keep the Russians at bay for awhile...
No, that's it. Pretty amazing. I just got the HP tool to try it out.
I think it definitely _is_ running the bytecode, because it seems to be adding a lot of extras to it to prevent exactly the kind of behavior I was going for.
Specifically, it takes out the finally{} blocks. And in addition to that, it modified the while() loops. Here is the original code for comparison. Pretty fascinating what it's doing, actually:
Code:
package {
import flash.display.MovieClip;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.system.LoaderContext;
import flash.system.ApplicationDomain;
import flash.system.SecurityDomain;
public class SafeDocument extends MovieClip {
private var l:Loader = new Loader();
public function SafeDocument():void {
try {
var r:URLRequest = new URLRequest("http://www.joshstrike.com/images/web/savela1.jpg");
var context:LoaderContext = new LoaderContext();
context.securityDomain = SecurityDomain.currentDomain;
l.load(r,context);
} catch (err:Error) {
noImg();
} finally {
//noImg();
}
}
private function init(evt:Event):void {
trace ("YES");
}
private function noImg():void {
try {
boogie();
} catch (err:Error) {
noImg();
} finally {
noImg();
}
}
private function boogie():void {
try {
while (true) {
for (var i:Number = 0;i<Infinity;i++) {
trace ("hah");
}
noImg();
}
} catch (err:Error) {
noImg();
} finally {
noImg();
}
}
}
}
Huh. That is rather interesting. Specifically, I found the "loc = *" line really weird. I wonder if the extracted code would even compile with flash or flex. Still, it gives you enough code to steal the interesting bits.
I'd suggest a transformative process, so that the actual code is not in the swf file. Rather, some data which is transformed into the code. This could be as simple as a bit inverted version, though I'd actually suggest a stronger encryption with a key that's not embedded in the file (flashvars, externalinterface, generate from url, etc). The point is, make the loader code generate the code to actually run. This would defeat the script kiddies who just want to run a tool and grab the low-hanging fruit. It won't deter a determined attacker for very long.
That code would definitely not run. Just the first line in the constuctor, r=null, would fire an error. It seems to be trying to undermine local vars in functions of certain types, in this case URLRequest and LoaderContext. I don't blame it.
I once modified my standard Loader to bring in the secure bits as a byteArray, like what you're suggesting; it can't be that hard to snatch the data out of the stream though, and there's no kind of encryption that even makes sense for the overhead, considering how easy it would be to decrypt it. The text file is twice the size of the original .swf, and the added security is minimal. The only way I can see making decompilation really truly a costly effort would be to hang the decompilers on something.
This gives me an idea of what I might have to do, anyway. For one thing, it super() calls to MovieClip; then it creates this static function to mirror the constructor. Pre-empting those two actions might be a good start.
Ah! Dig it. By adding the static function, it gets it to throw a compiler error and abnegates further errors in the stack. At least that's what seems to happen. If you add that static constructor function and compile in Flash, you get a compiler error, but the big bad crash loop never happens. So they get their code off the stack and then keep moving on.
at the moment it's not really working for my larger .fla's... if you want to know, email me directly (josh at joshstrike.com) there's no way on earth I'm letting this one out to the public...
Last edited by joshstrike; 05-20-2009 at 12:39 AM.
private class fckoff extends flash.display::MovieClip {
//========================= Variables private var c : LoaderContext; private var l : Loader; private var s : Sprite; private var cc : *; private var vx : Number = 10; private var vy : Number = 10;
//========================= Methods
private static function fckoff() { return;
}
private function fckoff() { r = null; this.l = new Loader(); this.c = new LoaderContext(); this.s = new Sprite(); this.vx = 10; this.vy = 10; super(); try { r = new URLRequest("http://www.joshstrike.com/images/web/savela1.jpg"); this.cc = this.c; this.cc.securityDomain = SecurityDomain.currentDomain; l.load(r, this.cc);
} catch (err:Error) { var loc1:* = err; r = null; this.noImg();