2011年9月28日星期三

Obfuscate 兩個主從 SWF 後,如何仍能正確執行 method

在 PTT 看到一些人問起,Main.swf 載入 External.swf 後,如何呼叫 External.swf 提供的 method;或是載入之後,如何使用 External.swf 中所包含的 Class 建立物件,並且呼叫該物件所提供的 method。



基本範例如下:

/Main.as,會被編譯成 Main.swf

package
{
 import external.comp.MyComp;
 
 import flash.display.DisplayObject;
 import flash.display.Loader;
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.net.URLRequest;
 
 public class Main extends Sprite
 {
  private var _ldr:Loader;
  
  public function Main()
  {
   trace(this, this.name);
   
   _ldr = new Loader();
   _ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderCompleteHandler);
   _ldr.load(new URLRequest("External.swf"));
   addChild(_ldr);
  }
  
  private function onLoaderCompleteHandler(e:Event):void{
   _ldr.contentLoaderInfo.removeEventListener(Event.COMPLETE, onLoaderCompleteHandler);
   
   var external:DisplayObject = _ldr.content;
   var returnValue:String;
   
   //call External.swf methods
   external["doJob1"]();
   returnValue = external["doJob2"]("Ben");
   trace(this, this.name, "onLoaderCompleteHandler()", "doJob2() return value=" + returnValue);
   
   //create instance
   var MyCompCls:Class = _ldr.contentLoaderInfo.applicationDomain.getDefinition("external.comp.MyComp") as Class;
   var myComp:Sprite = new MyCompCls() as Sprite;
   this.addChild(myComp);
   
   //call instance methods
   myComp["doJob1"]();
   returnValue = myComp["doJob2"]("Ben");
   trace(this, this.name, "onLoaderCompleteHandler()", "instance.doJob2() return value=" + returnValue);
  }
 }
}


/External.as,會被編譯成 External.swf

package
{
 import external.comp.MyComp;
 
 import flash.display.Sprite;
 import flash.events.Event;
 
 public class External extends Sprite
 {
  public function External()
  {
   trace(this, this.name);
   
   new MyComp;
   
   this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  }
  
  private function onAddedToStage(e:Event):void{
   this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
   
   trace(this, this.name, "onAddedToStage()");
  }
  
  public function doJob1():void{
   trace(this, this.name, "doJob1()");
  }
  
  public function doJob2(name:String):String{
   var resp:String = "Hello, " + name;
   trace(this, this.name, "doJob2()", "resp=" + resp);
   return resp;
  }
 }
}



/external/comp/MyComp.as,會被 External.swf 所包含

package external.comp
{
 import flash.display.Sprite;
 import flash.events.Event;
 
 public class MyComp extends Sprite
 {
  public function MyComp()
  {
   trace(this, this.name);
   
   this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  }
  
  private function onAddedToStage(e:Event):void{
   this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
   
   trace(this, this.name, "onAddedToStage()");
  }
  
  public function doJob1():void{
   trace(this, this.name, "doJob1()");
  }
  
  public function doJob2(name:String):String{
   var resp:String = "Hello, " + name;
   trace(this, this.name, "doJob2()", "resp=" + resp);
   return resp;
  }
 }
}


執行結果如下:

[object Main] root1
[object External] instance2
[object MyComp] instance3
[object External] instance2 onAddedToStage()
[object External] instance2 doJob1()
[object External] instance2 doJob2() resp=Hello, Ben
[object Main] root1 onLoaderCompleteHandler() doJob2() return value=Hello, Ben
[object MyComp] instance4
[object MyComp] instance4 onAddedToStage()
[object MyComp] instance4 doJob1()
[object MyComp] instance4 doJob2() resp=Hello, Ben
[object Main] root1 onLoaderCompleteHandler() instance.doJob2() return value=Hello, Ben


但是,如果你的專案需要使用 SWF Obfuscator 混淆過的話,譬如執行了以下 command line:

cd C:\works\MainExternalInteractiveDemo

"C:\Program Files\secureSWF_v3pro_win_build5762\ssCLI.exe" ./bin-debug/Main.swf ./bin-debug -p:most_aggressive -v:10

"C:\Program Files\secureSWF_v3pro_win_build5762\ssCLI.exe" ./bin-debug/External.swf ./bin-debug -p:most_aggressive -v:10

pause

再重新執行的話,就會遇到 flash player error 了:

[object Main] root1
[object External] instance2
[object do] instance3
[object External] instance2 onAddedToStage()
ReferenceError: Error #1069: External 上找不到屬性 doJob1,而且沒有預設值。
at Main/continue()[C:\works\MainExternalInteractiveDemo\src\Main.as:32]




因此,這裡提供第二種做法,

/Main2.as,會編譯成 Main2.swf

package
{
 import external.comp.MyComp;
 
 import flash.display.DisplayObject;
 import flash.display.Loader;
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.net.URLRequest;
 import flash.utils.describeType;
 
 public class Main2 extends Sprite
 {
  private var _ldr:Loader;
  
  public function Main2()
  {
   trace(this, this.name);
   
   _ldr = new Loader();
   _ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderCompleteHandler);
   _ldr.load(new URLRequest("External2.swf"));
   addChild(_ldr);
  }
  
  private function onLoaderCompleteHandler(e:Event):void{
   _ldr.contentLoaderInfo.removeEventListener(Event.COMPLETE, onLoaderCompleteHandler);
   
   var external:DisplayObject = _ldr.content;
   var returnValue:String;
   
   //call External.swf methods
   methodCall(external, "job1");
   returnValue = methodCall(external, "job2", ["Ben"]);
   trace(this, this.name, "onLoaderCompleteHandler()", "doJob2() return value=" + returnValue);
   
   var myCompClassName:String = methodCall(external, "getMyCompClassName");
   trace(this, this.name, "onLoaderCompleteHandler()", "getMyCompClassName() return value=" + myCompClassName);
   
   //create instance
   var MyCompCls:Class = _ldr.contentLoaderInfo.applicationDomain.getDefinition(myCompClassName) as Class;
   var myComp:Sprite = new MyCompCls() as Sprite;
   this.addChild(myComp);
   
   //call instance methods
   methodCall(myComp, "job1");
   returnValue = methodCall(myComp, "job2", ["Ben"]);
   trace(this, this.name, "onLoaderCompleteHandler()", "instance.doJob2() return value=" + returnValue);
  }
  
  private function methodCall(owner:Object, methodName:String, args:Array=null):*{
   trace(this, this.name, "methodCall()", "owner=" + owner, "methodName=" + methodName, "args=" + args);
   
   var xml:XML = describeType(owner);
   var find:XMLList = xml["method"]["metadata"].(@name=="PublicMethod")["arg"].(@key=="name");
   for(var i:int=0; i<find.length(); i++){
    if(find[i]["@value"] == methodName){
     var realMethodName:String = XML(XML(XML(find[i]).parent()).parent())["@name"];
     var returnValue:* = owner[realMethodName].apply(owner, args);
     trace(this, this.name, "methodCall()", "realMethodName=[" + realMethodName + "]", "return value=" + returnValue);
     return returnValue;
    }
   }
   return null;
  }
 }
}


接下來,這裡要額外特別說明一點,因為我使用到自定 Metadata 的技術,所以下面這個專案的編譯參數須增加設定:

-keep-as3-metadata+=PublicMethod



/External2.as,會編譯成 External2.swf

package
{
 import external.comp.MyComp2;
 
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.utils.describeType;
 
 public class External2 extends Sprite
 {
  public function External2()
  {
   trace(this, this.name);
   
   new MyComp2;
   
   this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  }
  
  private function onAddedToStage(e:Event):void{
   this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
   
   trace(this, this.name, "onAddedToStage()");
  }
  
  [PublicMethod(name="job1")]
  public function doJob1():void{
   trace(this, this.name, "doJob1()");
  }
  
  [PublicMethod(name="job2")]
  public function doJob2(name:String):String{
   var resp:String = "Hello, " + name;
   trace(this, this.name, "doJob2()", "resp=" + resp);
   return resp;
  }
  
  [PublicMethod(name="getMyCompClassName")]
  public function getMyCompClassName():String{
   var resp:String = String(describeType(MyComp2)["@name"]).replace("::", ".");
   trace(this, this.name, "getMyCompClassName()", "resp=" + resp);
   return resp;
  }
 }
}

/external/comp/MyComp2.as,會被 External2.swf 所包含

package external.comp
{
 import flash.display.Sprite;
 import flash.events.Event;
 
 public class MyComp2 extends Sprite
 {
  public function MyComp2()
  {
   trace(this, this.name);
   
   this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  }
  
  private function onAddedToStage(e:Event):void{
   this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
   
   trace(this, this.name, "onAddedToStage()");
  }
  
  [PublicMethod(name="job1")]
  public function doJob1():void{
   trace(this, this.name, "doJob1()");
  }
  
  [PublicMethod(name="job2")]
  public function doJob2(name:String):String{
   var resp:String = "Hello, " + name;
   trace(this, this.name, "doJob2()", "resp=" + resp);
   return resp;
  }
 }
}


執行結果如下:

[object Main2] root1
[object External2] instance2
[object MyComp2] instance3
[object External2] instance2 onAddedToStage()
[object Main2] root1 methodCall() owner=[object External2] methodName=job1 args=null
[object External2] instance2 doJob1()
[object Main2] root1 methodCall() realMethodName=[doJob1] return value=undefined
[object Main2] root1 methodCall() owner=[object External2] methodName=job2 args=Ben
[object External2] instance2 doJob2() resp=Hello, Ben
[object Main2] root1 methodCall() realMethodName=[doJob2] return value=Hello, Ben
[object Main2] root1 onLoaderCompleteHandler() doJob2() return value=Hello, Ben
[object Main2] root1 methodCall() owner=[object External2] methodName=getMyCompClassName args=null
[object External2] instance2 getMyCompClassName() resp=external.comp.MyComp2
[object Main2] root1 methodCall() realMethodName=[getMyCompClassName] return value=external.comp.MyComp2
[object Main2] root1 onLoaderCompleteHandler() getMyCompClassName() return value=external.comp.MyComp2
[object MyComp2] instance4
[object MyComp2] instance4 onAddedToStage()
[object Main2] root1 methodCall() owner=[object MyComp2] methodName=job1 args=null
[object MyComp2] instance4 doJob1()
[object Main2] root1 methodCall() realMethodName=[doJob1] return value=undefined
[object Main2] root1 methodCall() owner=[object MyComp2] methodName=job2 args=Ben
[object MyComp2] instance4 doJob2() resp=Hello, Ben
[object Main2] root1 methodCall() realMethodName=[doJob2] return value=Hello, Ben
[object Main2] root1 onLoaderCompleteHandler() instance.doJob2() return value=Hello, Ben


混淆:

cd C:\works\MainExternalInteractiveDemo

"C:\Program Files\secureSWF_v3pro_win_build5762\ssCLI.exe" ./bin-debug/Main2.swf ./bin-debug -p:most_aggressive -v:10

"C:\Program Files\secureSWF_v3pro_win_build5762\ssCLI.exe" ./bin-debug/External2.swf ./bin-debug -p:most_aggressive -v:10

pause



再次執行結果:

[object Main2] root1
[object External2] instance2
[object case] instance3
[object External2] instance2 onAddedToStage()
[object Main2] root1 methodCall() owner=[object External2] methodName=job1 args=null
[object External2] instance2 doJob1()
[object Main2] root1 methodCall() realMethodName=[do] return value=undefined
[object Main2] root1 methodCall() owner=[object External2] methodName=job2 args=Ben
[object External2] instance2 doJob2() resp=Hello, Ben
[object Main2] root1 methodCall() realMethodName=[ else] return value=Hello, Ben
[object Main2] root1 onLoaderCompleteHandler() doJob2() return value=Hello, Ben
[object Main2] root1 methodCall() owner=[object External2] methodName=getMyCompClassName args=null
[object External2] instance2 getMyCompClassName() resp=break. case
[object Main2] root1 methodCall() realMethodName=[getMyCompClassName] return value=break. case
[object Main2] root1 onLoaderCompleteHandler() getMyCompClassName() return value=break. case
[object case] instance4
[object case] instance4 onAddedToStage()
[object Main2] root1 methodCall() owner=[object case] methodName=job1 args=null
[object case] instance4 doJob1()
[object Main2] root1 methodCall() realMethodName=[do] return value=undefined
[object Main2] root1 methodCall() owner=[object case] methodName=job2 args=Ben
[object case] instance4 doJob2() resp=Hello, Ben
[object Main2] root1 methodCall() realMethodName=[ else] return value=Hello, Ben
[object Main2] root1 onLoaderCompleteHandler() instance.doJob2() return value=Hello, Ben


值得觀察的地方是,在 methodCall() 中的 log 訊息,realMethodName 表示 External2.swf 或其 MyComp2 所提供的 method 已經被混淆更名後,我們仍然能透過 metadata 找到對應的 method 參考,然後正確的執行。

更值得注意的是,混淆後,MyComp2 的完整類別名稱也已經不是 external.comp.MyComp2 了,而成為 "break. case" 這種看不懂的位置,所以我們也應該要以這個新的字串去映射到對應的類別。

0 意見: