2014年2月8日 星期六

Obfuscator for Windows Phone

公司最近開始計劃將幾個專案在往後較大的變版時利用 WPF 重新開發,加上目前有一些 Windows Phone App 已經在線上,所以試用了一些除了 .Net Framework 所開發的 dll 與 exe 之外,還可以混淆 Windows Phone (副檔名為 xap) 的方案。

直接先講最重點的部份,價格與知名度,最知名也最多人用的是 Visual Studio 隨附的 Dotfuscator,雖然 Visual Studio 就有附 Dotfuscator 但是版本為 Community Edition 版,無法混淆 Windows Phone 8 的執行檔,而 Marketplace Apps 這個版本又不能用在 WPF 專案上面,所以必須購買 Professional Edition 才可以滿足我們的需求,Dotfuscator 這套軟體是 PreEmptive 公司所開發的,價格方面必須註冊後送出試用申請來取得試用序號及詢價,可試用 15 天,價格方面 PreEmptive 似乎有做一些變動,印像中以前是付一次的價格直接採買 Professional 版本,這次再詢價拿到的價格是以年為單位,每年一筆金額,當然金額相較於直接買斷便宜十倍以上,Dotfuscator 是此次我試用過的 n 款工具中最滿意的,但價格也最貴,詳細的金額就不公開說明了 ( 可以殺價 )。

列出幾套可支援 WPF 及 Windows Phone 8 的工具:DotfuscatorCrypto ObfuscatorEazfuscator.NetSmartAssemblyDeepSea Obfuscator 等等,還有其他更多方案,當時沒有全部都找遍,選擇哪個方案還有另一個重點,必須可以併到 CI 的流程中,所幸用起來都沒有太大的問題,直接將該工具的專案檔餵給執行檔就可以了。

其中 DeepSea Obfuscator 是完全免費的,如果只需要混淆非 Silverlight、WinRT 的 .Net 專案,還有另一套免費的 Confuser 可以選擇。

以下簡單比較一下 Windows Phone 專案在免費的 DeepSea 與需收費的 Dotfuscator Professional 之差異,先看一下混淆之前的程式碼,有一個 interface、一個 struct、一個 class 的宣告及兩個按鍵、一個 delegate 處理流程。

public interface BaseInterface
{
    int BaseInterfaceID
    {
        get;
        set;
    }
}

public struct SampleStruct : BaseInterface
{
    public int SampleStructID;

    public int SampleStructProperty
    {
        get;
        set;
    }

    public int BaseInterfaceID
    {
        get;
        set;
    }
}

public class SampleClass : BaseInterface
{
    public int SampleClassID;

    public int SampleClassProperty
    {
        get;
        set;
    }

    public int BaseInterfaceID
    {
        get;
        set;
    }
}

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void OnReadTextButtonClick(Object sender, RoutedEventArgs e)
    {
        String url = "http://www.microsoft.com";
        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
        webRequest.AllowReadStreamBuffering = false;
        webRequest.BeginGetResponse(GetResponseCompleted, webRequest);
    }

    private void GetResponseCompleted(IAsyncResult asynchronousResult)
    {
        HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
        String htmlCode = null;
        if (response.StatusCode == HttpStatusCode.OK)
        {
            Stream cometStream = response.GetResponseStream();
            Byte[] readBuf = new Byte[327680];
            Int32 nRead = cometStream.Read(readBuf, 0, 327680);
            htmlCode = Encoding.UTF8.GetString(readBuf, 0, nRead);
        }
    }

    public delegate void TestEvent(Int32 testId, String testName);
    private TestEvent OnTest = null;
    private void OnDelegateButtonClick(object sender, RoutedEventArgs e)
    {
        OnTest += OnTestHandler;
        OnTest(100, "Ascii");
    }

    private void OnTestHandler(Int32 id, String name)
    {
        MessageBox.Show(String.Format("Id :: {0}, Name :: {1}", id, name));
    }
}

接下來看看 DeepSea 將此 xap 混淆過再反組譯出來的程式碼,不太需要仔細看應該就看得出來,只要是 class 的範圍內 Method 都變多了,再來仔細看一下會發現,所有具備 !!1, !!2, !!0 這種驚嘆號開頭參數的 Method 前面都被加上了 [SecuritySafeCritical] 這樣的 Attribute,這種 Method 的內容都很長,但這些皆為完全沒作用的假 Method,甚至沒有被其他任何地方呼叫,因為實在是太長了,我只了挑一些貼上。

再來觀察一下具備正常參數的 Method 可以發現被使用 switch case 與加入數個 Label 利用 goto 混淆過,但有點耐心還是勉強可以看懂邏輯。

public interface BaseInterface
{
    // Properties
    int BaseInterfaceID { get; set; }
}

[StructLayout(LayoutKind.Sequential)]
public struct SampleStruct : BaseInterface
{
    public int SampleStructID;
    private int u;
    private App.i b;
    public int SampleStructProperty { get; set; }
    public int BaseInterfaceID { get; set; }
}

public class SampleClass : BaseInterface
{
    // Fields
    private int o;
    public int SampleClassID;
    private l x;

    // Methods
    public SampleClass();
    [SecuritySafeCritical]
    internal static void j<!!0, !!1>(!!1, !!0, int, int) where !!0: Uri;
    [SecuritySafeCritical]
    internal static object m<!!0, !!1>(!!1, !!0, short, short) where !!0: string where !!1: Type;
    [SecuritySafeCritical]
    internal static void p<!!0>(!!0, bool, short, int) where !!0: HttpWebRequest;
    [SecuritySafeCritical]
    internal static Application r(char, short);
    [SecuritySafeCritical]
    internal static string v<!!0, !!1, !!2>(!!1, !!2, !!0, int, int) where !!1: string;

    // Properties
    public int BaseInterfaceID { get; set; }
    public int SampleClassProperty { get; set; }

    // Nested Types
    internal sealed class l
    {
        // Fields
        internal int g;

        // Methods
        internal l();
    }
}

public class MainPage : PhoneApplicationPage
{
    // Fields
    private TestEvent b;
    internal StackPanel j;
    private App.k o;
    internal Grid p;
    internal StackPanel x;

    // Methods
    public MainPage();
    private void f(IAsyncResult result1)
    {
        // This item is obfuscated and can not be translated.
        int num3 = 0;
        while (true)
        {
            IDisposable disposable;
            object obj3;
            object obj4;
            switch (num3)
            {
                case 1:
                case 5:
                    return;
 
                case 2:
                {
                    obj3 = AppResources.o<WebResponse>(disposable as HttpWebResponse, '?', 0x1c4);
                    obj4 = new byte[0x50000];
                    num3 = 7;
                    continue;
                }
                case 3:
                case 7:
                {
                    int count = ((Stream) obj3).Read((byte[]) obj4, 0, 0x50000);
                    Encoding.UTF8.GetString(obj4 as byte[], 0, count);
                    num3 = 1;
                    continue;
                }
                case 4:
                case 6:
                {
                    if (App.l.u<HttpWebResponse>((HttpWebResponse) disposable, '?', 900) != 200)
                    {
                        goto Label_0074;
                    }
                    num3 = 2;
                    continue;
                }
            }
            object asyncState = (HttpWebRequest) result1.AsyncState;
            disposable = AppResources.u<IAsyncResult, WebRequest>((HttpWebRequest) asyncState, result1, 0x3dd, '?');
            num3 = 4;
        }
    }

    private void h(int num1, string text1)
    {
        MessageBox.Show(SampleClass.v<object, string, object>(App.l.s(null, 0x4de63e7f, 2), num1, text1, 0x3d5, 0x3d3));
    }

    [SecuritySafeCritical]
    internal static XmlLanguage l<!!0>(!!0 local1, short num5, short num1) where !!0: string
    {
        // This item is obfuscated and can not be translated.
        int num;
        object language;
        int num2;
        int num4;
        goto Label_0044;
    Label_0002:
        switch (num4)
        {
            case 0:
                num++;
                goto Label_0080;
 
            case 2:
                return (language as XmlLanguage);
 
            case 3:
            {
                if ((num2 % 2) != 0)
                {
                    goto Label_0096;
                }
                num4 = 2;
                continue;
            }
            case 4:
                goto Label_0080;
 
            case 5:
                break;
 
            case 6:
            {
                switch ((((num1 ^ num5) - 7) ^ num))
                {
                    case 0:
                        goto Label_005D;
                }
                num4 = 10;
                continue;
            }
            case 7:
            case 8:
            {
                language = XmlLanguage.GetLanguage(local1);
                num4 = 0;
                continue;
            }
            case 9:
            case 11:
            {
                num4 = 6;
                continue;
            }
            case 10:
            {
                language = null;
                num4 = 0;
                continue;
            }
            default:
            {
                num4 = 5;
                continue;
            }
        }
    Label_0044:
        num = 0;
        num4 = 6;
        goto Label_0002;
    Label_0080:
        num2 = num1 * num1;
        num2 = num1 + num2;
        num4 = 3;
        goto Label_0002;
    }

    private void o(object, RoutedEventArgs)
    {
        int num2 = 1;
        while (true)
        {
            WebRequest request;
            switch (num2)
            {
                case 0:
                case 2:
                    SampleClass.p<HttpWebRequest>((HttpWebRequest) request, false, 0x283, 0x28f);
                    ((HttpWebRequest) request).BeginGetResponse(new AsyncCallback(this.f), (HttpWebRequest) request);
                    return;
 
                case 3:
                    return;
            }
            request = App.l.e<string>(App.l.s(null, 0x4de63e54, 4) as string, 0x2e, '.');
            num2 = 0;
        }
    }

    [SecuritySafeCritical]
    internal static string q<!!0, !!1, !!2>(!!1, !!2, !!0, char, int) where !!0: CultureInfo where !!1: ResourceManager where !!2: string;
    [SecuritySafeCritical]
    internal static void r<!!0, !!1>(!!1, !!0, char, short) where !!0: XmlLanguage where !!1: FrameworkElement;
    [SecuritySafeCritical]
    internal static Delegate s<!!0, !!1>(!!1, !!0, char, short) where !!0: Delegate where !!1: Delegate;

    private void t(object, RoutedEventArgs)
    {
        int num2 = 2;
        while (true)
        {
            switch (num2)
            {
                case 0:
                case 1:
                case 3:
                case 4:
                    this.b(100, App.l.s(null, 0x4de63e72, 7));
                    return;
            }
            this.b = (TestEvent) s<Delegate, Delegate>(this.b, new TestEvent(this.h), '\x00a2', 0xa2);
            num2 = 0;
        }
    }

    [SecuritySafeCritical]
    internal static UIElement v<!!0>(!!0 local1, char ch1, short num1) where !!0: Application
    {
        // This item is obfuscated and can not be translated.
        int num;
    Label_0044:
        num = 0;
        int num4 = 1;
        while (true)
        {
            DependencyObject obj2;
            int num2;
            switch (num4)
            {
                case 0:
                case 4:
                    goto Label_0044;

                case 1:
                {
                    switch ((((ch1 ^ num1) - 0) ^ num))
                    {
                        case 0:
                            goto Label_005D;
                    }
                    num4 = 9;
                    continue;
                }
                case 2:
                case 11:
                {
                    num4 = 1;
                    continue;
                }
                case 3:
                {
                    if ((num2 % 2) != 0)
                    {
                        goto Label_0097;
                    }
                    num4 = 10;
                    continue;
                }
                case 5:
                {
                    num++;
                    num2 = ch1 * ch1;
                    num2 = ch1 + num2;
                    num4 = 3;
                    continue;
                }
                case 6:
                {
                    obj2 = local1.get_RootVisual();
                    num4 = 5;
                    continue;
                }
                case 9:
                {
                    obj2 = null;
                    num4 = 5;
                    continue;
                }
                case 10:
                    return (UIElement) obj2;
            }
            num4 = 0;
        }
    }

    // Nested Types
    public delegate void TestEvent(int testId, string testName);
}

接下來是 Dotfuscator Professional 所混淆過再利用反組譯工具所曝露出來的程式碼,可以發現並沒有被多加奇怪的 Method,且部份 Method 連混亂的程式碼也無法被翻譯出來,反組譯工具上直接在該 Method 的內容列了 // This item is obfuscated and can not be translated.


public interface BaseInterface
{
    // Properties
    int BaseInterfaceID { get; set; }
}

[StructLayout(LayoutKind.Sequential)]
public struct SampleStruct : BaseInterface
{
    public int SampleStructID;
    [CompilerGenerated]
    private int eval_a;
    [CompilerGenerated]
    private int eval_b;
    public int SampleStructProperty { get; set; }
    public int BaseInterfaceID { get; set; }
}

public class SampleClass : BaseInterface
{
    // Fields
    [CompilerGenerated]
    private int eval_a;
    [CompilerGenerated]
    private int eval_b;
    public int SampleClassID;

    // Methods
    public SampleClass();

    // Properties
    public int BaseInterfaceID { get; set; }
    public int SampleClassProperty { get; set; }
}

public class MainPage : PhoneApplicationPage
{
    // Fields
    private bool _contentLoaded;
    internal StackPanel ContentPanel;
    internal Grid LayoutRoot;
    private TestEvent OnTest;
    internal StackPanel TitlePanel;

    // Methods
    public MainPage();

    private void eval_a(IAsyncResult A_0)
    {
        // This item is obfuscated and can not be translated.
        int num2 = 0;
        switch (num2)
        {
            default:
                switch (0)
                {
                    case 0:
                        goto Label_0034;
                }
                break;
        }
        while (true)
        {
            HttpWebResponse response;
            switch (num2)
            {
                case 0:
                {
                    Stream responseStream = response.GetResponseStream();
                    byte[] buffer = new byte[0x50000];
                    int count = responseStream.Read(buffer, 0, 0x50000);
                    Encoding.UTF8.GetString(buffer, 0, count);
                    num2 = 2;
                    continue;
                }
                case 1:
                    switch ((0x36e6ff97 == 0x36e6ff97))
                    {
                        case 2:
                            return;
                    }
                    break;

                case 2:
                    return;

                default:
                {
                    goto Label_0034;
                    if (1 != 0)
                    {
                    }
                    response = ((HttpWebRequest) A_0.AsyncState).EndGetResponse(A_0);
                    num2 = 1;
                    continue;
                }
            }
            if (0 != 0)
            {
            }
            if (response.get_StatusCode() != 200)
            {
                return;
            }
            num2 = 0;
        }
    }

    private void eval_b(int A_0, string A_1)
    {
        // This item is obfuscated and can not be translated.
        switch ((0x35c33eee == 0x35c33eee))
        {
        }
        if (((0 == 0) ? 0 : 1) != 0)
        {
        }
        MessageBox.Show(string.Format("Id :: {0}, Name :: {1}", A_0, A_1));
    }

    private void eval_c(object A_0, RoutedEventArgs A_1)
    {
        // This item is obfuscated and can not be translated.
        switch ((0x2d334b60 == 0x2d334b60))
        {
        }
        if (((0 == 0) ? 0 : 1) != 0)
        {
        }
        this.OnTest = (TestEvent) Delegate.Combine(this.OnTest, new TestEvent(this.eval_b));
        this.OnTest(100, "Ascii");
    }

    private void eval_d(object A_0, RoutedEventArgs A_1)
    {
        // This item is obfuscated and can not be translated.
    }

    // Nested Types
    public delegate void TestEvent(int testId, string testName);
}

相較之下混淆的品質 Dotfuscator 好很多,且不會增加額外的 Method 造成執行檔尺寸變大。

沒有留言:

張貼留言