Monday, December 3, 2012

Flash AS3 Class injection via Loader

I discovered a new technique for hacking flash games a month or so ago. I've been referring to it as class overriding (simply because class injection could be used to refer to injecting bytecode into a SWF).
It's a fairly simple technique that allows you to have extreme control over a loaded SWF.
I was going to do this tutorial on an actual game, but unfortunately there weren't any games that were good examples, so instead I modified my Unhackable Engine base (designed to be the simplest game possible for testing new security measures/ect in a semi-real environment and easy to port to other languages, the original AS3 version is under 90 lines of code, and the AS2 version is less than 60).
I'm going to assume you have some experience with AS3 and flash. If you don't, the source for this tutorial will be available.

Be warned, this SWF was botched together as fast as possible, and modified to function in weird ways specifically for the tutorial. Don't judge me on the quality of it's code.

Download the "game" used in this tutorial here.

 Anyways, to the tutorial:

The first thing we do is we start with a basic loader template that will load the SWF.
Create a new document class (I called mine Overrider), and put this code in it:
 package {  
      import flash.display.MovieClip;  
      import flash.display.Loader;  
      import flash.net.URLRequest;  
      import flash.system.ApplicationDomain;  
      import flash.system.LoaderContext;  
      public class Overrider extends MovieClip {  
           private var ldr:Loader = new Loader();  
           public function Overrider() {  
                this.addChild(ldr);  
                var urlReq:URLRequest = new URLRequest("unhackable.swf");  
                ldr.load(urlReq,new LoaderContext(false,ApplicationDomain.currentDomain));  
           }  
      }  
 }  

This will load the SWF into our SWFs ApplicationDomain, meaning that classes will be loaded into the same space, and when flash player tries to load a class that already exists in the current ApplicationDomain, the new class is dropped, and the old one remains (which is how this hack works).
Now, compile and run that, and you will get an error.

 TypeError: Error #1009: Cannot access a property or method of a null object reference.  
      at Unhackable()  

let's open unhackable.swf with a decompiler, and see what the problem is.
We know from the error that the error is within Unhackable(), so let's look at the code:

 public class Unhackable extends MovieClip  
 {  
   ...  
   public function Unhackable()  
   {  
     this.player = new Player(int(Math.random() * 300) + 50, int(Math.random() * 200) + 50);  
     this.money = new Money(int(Math.random() * 300) + 50, int(Math.random() * 200) + 50);  
     this.score = new ScoreKeeper();  
     addChild(this.score);  
     addChild(this.money);  
     addChild(this.player);  
     stage.addEventListener(Event.ENTER_FRAME, this.gameLoop);  
     return;  
   }  
   ...  
 }  

Ah, there's our problem. Let me explain for those of us who are unfamiliar with the bugs in the Loader class...
When A SWF is loaded normally (without the Loader class), the document class's constructor is called after it has been added to the stage and therefore has the stage property set. When a SWF is loaded with the Loader class, the document class's constructor is called before it has had it's stage property set, so that refference to the stage:

 stage.addEventListener(Event.ENTER_FRAME, this.gameLoop);   

will throw an error, because the stage variable is still a null object reference (it's null), and as the error states, you cannot access a method of a null object reference.

Luckily, we can fix this quite easily with class overriding.

Create a new class in your default package called Unhackable and copy and paste the code the decompiler generated for that class over.

Now, when you compile that, you'll get about a half-dozen errors. this is ok, and completely normal in class overriding. You see, that class imports other classes (Player, Money, ScoreKeeper) that we don't have in our project. The first solution you probably think if would be to decompile and add every class in the SWF, but that would be a pain in the ass and counterproductive, so we're gonna hack our way through this. the question we need to answer is: How do we access a class we can't import? The solution is simple: using the getDefinitionByName function in flash.utils.*.
so, replace:

 import UnhackableEngine.*;  

with:

 import flash.utils.*;  

getDefintionByName will dynamically find the class for us whenever we need it. so, with a little modification we can fix this class.
replace:

     private var player:Player;  
     private var money:Money;  
     private var score:ScoreKeeper;  

with:

           private var Player:Class = getDefinitionByName("UnhackableEngine.Player") as Class;  
           private var Money:Class = getDefinitionByName("UnhackableEngine.Money") as Class;  
           private var ScoreKeeper:Class = getDefinitionByName("UnhackableEngine.ScoreKeeper") as Class;  
           private var player:Object;  
           private var money:Object;  
           private var score:Object;  

Also, since there are no references to  this new class you've added, by default it won't compile into your SWF, so go back to your document class, and under the class variable definitions, add:

 private var unhackable:Unhackable;  

When you try and compile, you'll get some more errors now, but that's OK, cause we're gonna have to replace the function they're in anyway.
So, onto replacing the function, fixing the stage reference problems and adding the needed type casting.
First, we're gonna move all the code in the constructor to another function, and make that function be called when the object has been added to the stage (using the ADDED_TO_STAGE listener).
replace:

           public function Unhackable()  
           {  
                this.player = new Player(int(Math.random() * 300) + 50,int(Math.random() * 200) + 50);  
                this.money = new Money(int(Math.random() * 300) + 50,int(Math.random() * 200) + 50);  
                this.score = new ScoreKeeper();  
                addChild(this.score as DisplayObject);  
                addChild(this.money as DisplayObject);  
                addChild(this.player as DisplayObject);  
                stage.addEventListener(Event.ENTER_FRAME, this.gameLoop);  
                return;  
           }  

with:

           public function Unhackable()  
           {  
                this.addEventListener(Event.ADDED_TO_STAGE,addToStage);  
                return;  
           }  
           private function addToStage(e:Event)  
           {  
                this.player = new Player(int(Math.random() * 300) + 50,int(Math.random() * 200) + 50);  
                this.money = new Money(int(Math.random() * 300) + 50,int(Math.random() * 200) + 50);  
                this.score = new ScoreKeeper();  
                addChild(this.score);  
                addChild(this.money);  
                addChild(this.player);  
                stage.addEventListener(Event.ENTER_FRAME, this.gameLoop);  
           }  

And now we add some type casting to fix the last 3 errors.
replace:

                addChild(this.score);  
                addChild(this.money);  
                addChild(this.player);  

with:

                addChild(this.score as DisplayObject);  
                addChild(this.money as DisplayObject);  
                addChild(this.player as DisplayObject);  

and compile. with any luck, you have no errors, it compiles, loads the SWF and runs perfectly!
If not, try download the source of the finished code and try and figure out what went wrong.

Congratulations! you've successfully overrided your first class!

Now, onto doing something useful.

We'll double player speed and make it so that each "money" pickup gives us 1,000 points.
First, we'll start with the money hack, because the player speed hack is a little more complicated (If you're feeling confident/bored, you can skip to the player speed hack, as it teaches more important stuff).

This one is simple and straightforward.
you'll need a new package (class locations need to be mirrored) called UnhackableEngine, and in there a new class called ScoreKeeper.

Copy and paste the ScoreKeeper code from the decompiler, add this line of code to your document classes variable definitions to make sure it gets compiled in:

 private var scoreKeeper:ScoreKeeper;  

and compile.
You'll get an error.
Don't worry, it's easy to fix.

Our problem is that variables are not being casted from int/whatever to string, so the compiler is getting shitty with us.

replace the two occurrences of:

 score.text = gameScore;  

with:

 score.text = String(gameScore);  

and compile.
It should compile and work without errors.

Now, let's take a look at the addMoney function:

     static function addMoney()  
     {  
       var _loc_2:* = gameScore + 1;  
       gameScore = _loc_2;  
       score.text = String(gameScore);  
       return;  
     }  

seems easy enough to modify. Let's chuck some 0s in there

     static function addMoney()  
     {  
       var _loc_2:* = gameScore + 1000;  
       gameScore = _loc_2;  
       score.text = String(gameScore);  
       return;  
     }  

and boom, money pickups now give us 1000 score.

Onto player movement, and advanced class overriding!
I'm sure you know the drill by now.
Create a new class in the UnhackableEngine package named Player.
C+P the code from the decompiler, add a reference to the class in your document class, try to compile and see what errors you get.

Ooh, we get a new, nasty one.

The definition of base class Thing was not found.

We have two options here, overriding the Thing class will solve this problem, but imagine if Thing extended another custom class, which extended another custom class, which ended up being a class chain 20  classes long? it would be impractical to override them all.

But how can you possibly get around it? you can't just dynamically extend classes. That goes directly against AS3s standards. Luckily, in AS3 there's this nasty little thing called the Proxy class that goes against all of AS3s conventions, and makes this possible.

Although we cannot actually dynamically extend classes, we can pretend we do using the Proxy class to dynamically pretend we have properties of classes which we actually don't.

I'm going to warn you now, everything's about to get messy. Which is why I'm stalling.

I guess I'll explain the Proxy class a little, seems like it's the best way to teach this.

The Proxy class allows you to dynamically change the behavior of an object. When set up, you can make it so that if a property is accessed and doesn't exist in an object, a proxy function is called that can deal with it. we use this functionality to make an object act as if it has the properties of another object by making any attempt to access a property that doesn't exist on the object go to the object it is pretending to extend.

Hopefully, the code will help explain what I mean for those who are still confused...

First thing we need to do is make the Player class extend the Proxy class.
replace:

 public class Player extends Thing  

with:

 public class Player extends Proxy  

now, add these variables to the Player classes variable definitions:

           public var extendedClass:Class = getDefinitionByName("UnhackableEngine.Thing") as Class;  
           public var extended:Object  

add these functions to the class:

  override flash_proxy function getProperty(name:*):*
  {
   return extended[name];
  }
  
  override flash_proxy function setProperty(name:*,value:*):void
  {
   extended[name] = value;
  }

  override flash_proxy function callProperty(name:*, ...rest):*
  {
   extended[name].apply(rest);
  }

these functions are the backbone of our Proxy hack. We don't actually need most (or any) of them. in most situations, you'll need them, though. There are more Proxy functions you may need in other circumstances, but these are the main ones.

We're gonna have to do quite a bit of modifying to make this class work with us.
Any internal references in the player class to the class it extends need to be changed.

replace:

     public function Player(param1, param2)  
     {  
       fillColour = 11141120;  
       this.playerX = param1;  
       this.playerY = param2;  
       this.addEventListener(Event.ADDED_TO_STAGE, this.addListeners);  
       super(param1, param2);  
       return;  
     }  

with:

           public function Player(param1, param2)  
           {  
                extended = new extendedClass(param1, param2);  
                extended.fillColour = 11141120;  
                extended.drawStuff();  
                this.playerX = param1;  
                this.playerY = param2;  
                extended.addEventListener(Event.ADDED_TO_STAGE, this.addListeners);  
                return;  
           }  

and

     public function doMove()  
     {  
       this.playerX = this.playerX + this.xVel * 5;  
       this.playerY = this.playerY + this.yVel * 5;  
       drawX = this.playerX;  
       drawY = this.playerY;  
       drawStuff();  
       return;  
     }  

with:

           public function doMove()  
           {  
                this.playerX = this.playerX + this.xVel * 5;  
                this.playerY = this.playerY + this.yVel * 5;  
                extended.drawX = this.playerX;  
                extended.drawY = this.playerY;  
                extended.drawStuff();  
                return;  
           }  

and

     private function addListeners(event:Event) : void  
     {  
       stage.addEventListener(KeyboardEvent.KEY_DOWN, this.keyDownListener);  
       stage.addEventListener(KeyboardEvent.KEY_UP, this.keyUpListener);  
       return;  
     }  

with:

           private function addListeners(event:Event):void  
           {  
                extended.stage.addEventListener(KeyboardEvent.KEY_DOWN, this.keyDownListener);  
                extended.stage.addEventListener(KeyboardEvent.KEY_UP, this.keyUpListener);  
                return;  
           }  

As you can tell, the Proxy hack can be a bit messy. sometimes, it's better to just override the class it extends as well. cause now where up to the next problem: type casting.
A Proxy object can't be casted to anything the object it's pretending to extend can. And the addChild function which the player object is given as a parameter to expects a DisplayObject. luckily, we're already overriding the class that calls the function, so go back to the Unhackable class and
replace:

 addChild(this.player as DisplayObject);  

with:

 addChild(this.player.extended as DisplayObject);  

compile that and it should work error-free.

The Player class is FINALLY set up for modification (again, the proxy hack isn't really made for this situation, but it's useful to know how to do it).

One quick modification and we'll  have double player speed.

change

                this.playerX = this.playerX + this.xVel * 5;  
                this.playerY = this.playerY + this.yVel * 5;  

to

                this.playerX = this.playerX + this.xVel * 10;  
                this.playerY = this.playerY + this.yVel * 10;  

And we're done!

Download the source for this tutorial here.

No comments: