可輕易反組譯是採用中介語言(.NET, Java)平台的共有特性,也是實務應用的資安隱憂,面對這個問題,最有效的解決方案是 -- 混淆器(Obfuscator)。

混淆器的運作原因,是解析編譯好的DLL或EXE檔,將其轉換成執行結果相同的組件,差別在於私有類別、屬性、方法、欄位、參數名稱都已改到面目全非,難以閱讀理解;更進一步還可以打亂程式碼的排列流程(執行順序不變)、加密程式碼中的字串常數,讓反組譯的程式碼亂如咒語天書,令有意破解者卻步,至少要讓對方追程式追到流涕痛哭。天下沒有破解不了的程式,混淆器的目標在於逼迫絕大部分的破解者儘早放棄,即使被破解也要對方付出極其可觀(甚至難以想像)的高昂代價。

印象中,專業等級的.NET混淆器價格不斐,甚至如PreEmptive限定由銷售團隊洽談應用狀況再決定報價(嗯,有沒有人跟我一樣覺得毛毛的?),最近在估評其他軟體時看到Red Gate的SmartAssembly,標準版約1,000 USD、專業版約1,500 USD,價格貼近公司採購的其他開發元件,便決定實測看看。註: SmartAssembly有個有趣的開發者版本,不到200 USD,限制混淆後的程式只能在開發者自己的機器上執行,且程式會在七天後爆炸失效,目的是讓開發者測試混淆後的程式是否執行正常(某些混淆技巧可能導致程式不正常,記得要實測及適度調整混淆參數),故實務上全公司可共用一套標準版/專業版,再視需要採購開發版,若不用開發版,每次上測試台前再統一混淆亦是可行策略。

SmartAssembly的操作還算簡單,新增一個專案,選取DLL或EXE,設定混淆參數,建置就可以得到混淆版。

SmartAssembly可混淆對象包含: Windows Form, WPF, Console, Silverlight XAP, 程式庫(DLL), .NET Web Service, ASP.NET Web bin下的DLL… 等,原則上只要是.NET編譯出的組件(Assembly,DLL或EXE)都可以處理。除了混淆功能外,SmartAssembly還有一些"額外"功能,例如:

  1. Strong Name Siging
    為混淆後的元件加上強式簽名防止變造
  2. Automated Error Reporting
    程式當掉時蒐集錯誤資訊建立當機報告(背後會送到Red Gate的Web Service,並可透過SmartAssembly UI瀏覽,見前圖左側選單的Reporting項目) [參考]

    一旦程式出錯,會彈出以下對話框(透過SDK,對話框可以客製化)
  3. Feature Usage Reporting
    可以統計程式執行環境、各功能被使用的次數(也是透過Red Gate的Web Service蒐集,第一次執行前徵求使用者同意可傳送統計資訊較不會有爭議)
  4. Dependencies Merging
    將參照的DLL也合併起來混淆(可提高混淆程度),並非所有DLL都適用,3rd Party的元件可考慮改用嵌入(Embedding)做法
  5. Dependencies Embedding
    將參照DLL嵌入程式中(預設會加密、壓縮)但不進行混淆,第一次執行時解密、解壓縮還原,能減少部署檔案數目。
  6. Pruning
    移除沒用到的Metadata,例如事件名稱、屬性、方法參數等,讓程式碼更難解讀,也有助於減少程式檔體積。[參考]
  7. Resource Compression and Encryption
    資源壓縮及加密,第一次執行時還原,能縮小檔案體積。
  8. Other Optimization
    減少保留但未使用的記憶體、自動seal所有類別禁止繼承

回到重點 -- 混淆,SmartAssembly透過以下技巧讓反組譯的程式碼難以解讀,包含:

  1. 將類別、方法名稱改成看不懂的怪字
  2. 把類別的欄位名稱亂改一通,甚至讓不同類別用相同的欄位名稱
  3. 將類別A的方法搬到類別B底下,但風險較高,有時會出錯,宜斟酌使用
  4. Control Flow Obfuscation,把程式碼的先後順序調亂,但執行順序保持不變
    題外話: 以上四招我曾見過有人能徒手運用,直接寫出沒人看得懂的程式碼,堪稱"人體混淆"大絕 orz
  5. 動態Proxy: 一律透過執行期間產生的Proxy呼叫參照DLL,如此Assembly一旦被篡改,程式就會壞掉無法執行
  6. 字串加密: 預設.NET組件中的字串被直接儲存,檢視檔案二進位內容時就看得到,SmartAssembly支援將字串內容加密混淆
  7. 加上註記禁止MSIL Disassembler(ildasm)對檔案進行反組譯
  8. 如果有pdb的話,混淆StackTrace中出現的原始碼檔案路徑

接著來實測一番,以下是簡單的Console程式:

using System;
using System.Reflection;
using Boo;
using Foo;
 
namespace ObfuscationTest
{
    class Program
    {
        static string dllPath = @"d:\Newtonsoft.Json.dll";
        static void Main(string[] args)
        {
            Assembly asm = Assembly.LoadFile(dllPath);
            Type t = asm.GetType("Newtonsoft.Json.JsonConverter", true);
            foreach (var pi in t.GetProperties())
            {
                Console.WriteLine(pi.Name);
            }
            BooClass boo = new BooClass() { Name = "Jeffrey" };
            FooClass foo = new FooClass() { Name = "Darkthread" };
            Console.WriteLine("{0} / {1}", boo.Name, foo.Name);
            string res = Console.ReadLine();
            if (!string.IsNullOrEmpty(res))
                throw new ApplicationException("My Bad! XD");
        }
    }
}

使用反組譯工具解析ObfuscationTest.exe檔,可以看到幾乎原汁原味的原始程式碼:

接著我們透過SmartAssembly惡搞一番,另外Build成ObfuscationTestSA.exe,再用反組譯工具試著打開。嗯,很好,程式碼已經被整到連他娘都不認得!


註: 我是選擇不加密字串常數,好不容易才透過程式碼中"Jeffrey"、"Darkthread"找到main()的所在位置,試過連字串都加密,我選擇直接投降~

評估之後,SmartAssembly看來是值得考慮的.NET混淆器解決方案。如果擔心專案中的程式經混淆會出問題,倒是可以先用試用版(功能完整,但編譯結果只能在有安裝程式的機器執行,七天後失效)實測評估後再考量。


Comments

# by 小黑

請問版主,是否有加密 JavaScript 與 HTML5 的方法阿,雖說在網路的世界是沒有秘密的,但是就是不想讓他們這麼輕易的取得,希望黑哥指引?

# by e-Cheng

hi, 小黑,JavaScript可以試試GWT,編出來的script也是很難讀

Post a comment