混淆器與序列化

摘要:混淆器與序列化

費盡心力、絞盡腦汁才寫出來的程式,相信每個人都不希望當中的智慧結晶被別人任意取用,可偏偏 .NET 程式是編譯成 IL code,只要 Reflector 利劍在手,便可輕易將程式碼層層剖開,任憑享用。正因為如此,我們需要混淆器(obfuscator)來保護自己的心血結晶。這篇文章並不是要講如何使用混淆器,而是我在使用混淆器時碰到的一個問題。

舉例來說,假設你寫了一套特殊的文書編輯軟體,你將一份文件封裝程 Document 物件,而 Document 裡面又包含許多 Line 物件,每個 Line 物件又包含數量不等的 Word 物件,Word 裡面又包含其他小物件。這種情況就好像一個大箱子裡面層層包著許多小箱子一樣。

當你要把一份文件(Document 物件)儲存到磁碟,你會怎麼做?(當然這裡舉的例子並不是純文字文件,否則用 StreamWriter 就好了。)一種作法是寫程式將整個 Document 物件的內容輸出至檔案,你可能會先把一些文件資訊輸出成檔頭,然後用好幾個巢狀迴圈分別輸出 Line 物件、每個 Line 裡面的 Word、以及每個 Word 裡面的資料。光想就覺得累了,更何況在載入檔案時,還得反過來,將讀取的資料重建為層層包裹的物件。

如果你知道序列化(serialization),你可能會喜歡用序列化的方法。實作起來非常簡單,首先,將那些需要儲存至檔案的 Document、Line、Word 類別都標上 Serializable Attribute。類似這樣:

    1 using System.Runtime.Serialization;

    2 ......

    3     [Serializable]

    4     public class Document

    5     {

    6         private List<Line> m_Lines;

    7 

    8         [NonSerialized]

    9         private string m_FileName;   // 不需要儲存至檔案的,就標上 [NonSerialized]

   10     }

   11 

   12     [Serializable]

   13     public class Line

   14     {

   15         private List<Word> m_Words;

   16     }   

加上可序列化 attributes 之後,剩下的儲存動作就簡單多了:

    1     using (FileStream fs = new FileStream(filename, FileMode.Create))

    2     {

    3         BinaryWriter bw = new BinaryWriter(fs);

    4         IFormatter fmter = new BinaryFormatter();

    5         fmter.Serialize(fs, doc);  // 這裡的 doc 就是Document 物件

    6 

    7         bw.Flush();

    8         bw.Close();

    9     } 

 

你可以看到,簡單幾行就完成了。而且,注意第 5 行,我們只要傳入 Document 物件而已。這是因為序列化的行為是會「傳染」的,Document 物件裡面包著需要序列化的 Line 物件,Line 又包含需要序列化的 Word 物件,因此一整串的巢狀物件都會輸出至檔案。不管多複雜的巢狀物件,程式的寫法都一樣這麼簡單(如果不使用序列化,而由自己寫程式處理,可就麻煩多了)。 反序列化的動作也一樣簡單。

 

好,目前為止都很完美。開發完成後,你希望不要被別人輕易看見程式內部的寫法,於是你用 Visual Studio 2005 附的 Dotfuscator Communication Edition 將編譯過的 .NET 組件「翻攪」過一遍。雖然 Dotfuscator 已經拿掉很多專業版的功能,但這些被翻攪過的組件還是被處理得挺徹底,裡面的所有類別、變數、方法名稱全都變成了 a、b、aa、ad 這類毫無意義的名稱。看到 Reflector 反組譯之後結果,你相當滿意:

 

 

可是,當你將程式執行起來,並且載入之前儲存的文件檔案時(這裡指的是程式用混淆器翻攪之前所儲存的檔 案),程式就會發生錯誤而異常中止。因為程式用混淆器翻攪之前所儲存的檔案,裡面含有序列化資訊,包括變數值、變數名稱、型別名稱等等。當程式用混淆器翻 攪之後,裡面的類別、變數名稱全都變了,反序列化時找不到原先序列化時的型別、欄位,當然就會發生錯誤。

 

這是使用混淆器時可能會碰到的問題之一。所幸 Dotfuscator 提供了彈性的設定,可以指定哪些類別、變數、方法名稱維持原狀。只要在 Dotfuscator 視窗的 Rename 頁籤中,把那些需要序列化的類別及其成員變數勾選起來,就能解決上述問題。圖就不剪了,若有興趣,你可以在 VS2005 中點選 Tools > Dotfuscator Community Edition,自己動手試試看,然後比較一下混淆前、混淆後的組件用 Reflector 反組譯的結果有何差異。

 

在 Dotfuscator 的一份 FAQ 文件中也有提到這個問題,順便附上連結:

 

I have heard that XML Serialization "serializes an object and uses the names of the members, as determined at runtime, as the XML element names." Is this a problem?