2011年9月15日星期四

Robotleg 與 Obfuscator

在 ActionScript Developer's Guide to Robotlegs 書中,看到一段話,Robotleg 中只要求 Custom Command 具有 execute() 方法,除非為了便利性才需要繼承自 Robotleg 的 Command 類別,Robotleg 會透過映射去執行 execute() 方法。這件事情讓我小小擔心是否 "SWF 混淆" 會造成影響。



以下一個簡單的範例:

MyFirstRobotlegApp.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" 
      xmlns:mx="library://ns.adobe.com/flex/mx"
      xmlns:ben="idv.ben.*">
 <fx:Declarations>
  <ben:MyContext id="myContext" contextView="{this}" />
 </fx:Declarations>
 
 <s:Button id="btn1" label="Test" x="200" y="200" />
 
</s:Application>


idv.ben.MyContext.as

package idv.ben
{
 import flash.display.DisplayObjectContainer;
 
 import idv.ben.controller.Btn1ClickCommand;
 import idv.ben.controller.StartupCommand;
 import idv.ben.event.MyEvent;
 import idv.ben.mediator.MyMediator;
 
 import org.robotlegs.base.ContextEvent;
 import org.robotlegs.mvcs.Context;
 
 public class MyContext extends Context
 {
  public function MyContext(contextView:DisplayObjectContainer=null, autoStartup:Boolean=true)
  {
   super(contextView, autoStartup);
  }
  
  override public function startup():void{
   mediatorMap.mapView(MyFirstRobotlegApp, MyMediator);
   mediatorMap.createMediator(contextView);

   commandMap.mapEvent(ContextEvent.STARTUP, StartupCommand, ContextEvent, true);
   commandMap.mapEvent(MyEvent.UI_BTN1_CLICK, Btn1ClickCommand, MyEvent);
   
   dispatchEvent(new ContextEvent(ContextEvent.STARTUP));
  }
 }
}

idv.ben.mediator.MyMediator.as

package idv.ben.mediator
{
 import flash.events.MouseEvent;
 
 import idv.ben.event.MyEvent;
 
 import org.robotlegs.mvcs.Mediator;
 
 public class MyMediator extends Mediator
 {
  [Inject]
  public var viewComp:MyFirstRobotlegApp;
  
  override public function onRegister():void{
   viewComp.btn1.addEventListener(MouseEvent.CLICK, on_btn1_click);
  }
  
  private function on_btn1_click(e:MouseEvent):void{
   dispatch(new MyEvent(MyEvent.UI_BTN1_CLICK));
  }
 }
}

idv.ben.controller.StartupCommand.as

package idv.ben.controller
{
 import flash.utils.describeType;
 
 import org.robotlegs.base.ContextEvent;
 
 public class StartupCommand
 {
  [Inject]
  public var event:ContextEvent;
  
  public function execute():void{
   trace(describeType(this)["@name"], "execute()", event);
  }
 }
}

idv.ben.controller.Btn1ClickCommand.as

package idv.ben.controller
{
 import flash.utils.describeType;
 
 import idv.ben.event.MyEvent;
 
 public class Btn1ClickCommand
 {
  [Inject]
  public var event:MyEvent;
  
  public function execute():void{
   trace(describeType(this)["@name"], "execute()", event);
  }
 }
}

idv.ben.event.MyEvent.as

package idv.ben.event
{
 import flash.events.Event;
 
 public class MyEvent extends Event
 {
  static public const UI_BTN1_CLICK:String = "uiBtn1Click";
  
  public function MyEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
  {
   super(type, bubbles, cancelable);
  }
 }
}

以上程式碼,簡單來說,進入主程式後會建立 Context,觸發 ContextEvent.STARTUP 事件執行 StartupCommand,另外會註冊 MyMediator 並於註冊時設定 btn1 的 MouseEvent.CLICK 處理函式,當發生時會送出 MyEvent.UI_BTN1_CLICK 給 Robotleg 的 shared eventDispatcher 讓有興趣的人處理,使得事先註冊好的 Btn1ClickCommand 被執行。



尚未混淆之前,執行畫面如下:




接下來,我使用 secureSWF 3.6 進行混淆,先使用 Standard 的 Protection Preset,若選擇 Most Aggressiv,受影響的範圍會更大,會影響到 Robotleg framework 中的其它程式碼。


選完 Standard 之後,預設我自定的 Custom Command 的 execute() 不會被勾選,這裡我先 故意 打勾:


開始進行混淆~

混淆後發現,secureSWF 會自動將我 Custom Command 的 execute() 取消勾選,我無法得知 secureSWF 的分析判斷依據。

這個版本的 swf 執行後當然也會是正常:


為了強迫測試 execute() 被混淆後的情況,我只好自己加 rule 將 idv.ben.controller.* 下的類別、method name 都進行更名:


這樣一來,測試結果 果然就出問題了:

Error: Command Class does not implement an execute() method - [class _-f]


這個範例只是要提醒注意,若 secureSWF 會幫我們避掉 execute() 最好 (可能在 Robotleg framework 中有使用到 execute,所以 secureSWF 避掉),但若是哪天 secureSWF 沒有避掉的話,就需要自己加 rule 來避掉這個問題了。

下面附上 Robotleg 的 CommandMap 片段程式碼,可幫助了解它執行 command (Object 類別) 的 execute() 的部分:

public function execute(commandClass:Class, payload:Object = null, payloadClass:Class = null, named:String = ''):void
  {
   verifyCommandClass(commandClass);
   
   if (payload != null || payloadClass != null)
   {
    payloadClass ||= reflector.getClass(payload);
    injector.mapValue(payloadClass, payload, named);
   }
   
   var command:Object = injector.instantiate(commandClass);
   
   if (payload !== null || payloadClass != null)
    injector.unmap(payloadClass, named);
   
   command.execute();
  }

0 意見: