直接先講最重點的部份,價格與知名度,最知名也最多人用的是 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 的工具:Dotfuscator、Crypto Obfuscator、Eazfuscator.Net、SmartAssembly、DeepSea 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 造成執行檔尺寸變大。
沒有留言:
張貼留言