摘要:由於Unity引擎的特殊性,代碼保護實現起來比較復雜,國內外業界都沒有現成的方案。通過在QQ樂團項目中的實踐嘗試,作者提出了壹個具體可行的方案,可以有效地保護代碼邏輯。特此分享給與Unity引擎有關的項目,希望能提供壹些參考。
背景
Unity engine上的程序在Mono運行時上執行,Mono編譯的匯編格式與。凈標準。C#是Unity engine下的主要開發語言,它具有許多高級語言功能,如反射、元數據、內置序列化等。然而,C#也是壹種很容易被反編譯的語言。如果您不采取任何保護措施,則可以通過使用常用工具()輕松獲得可以重新編譯的代碼。凈反射器)。給項目運營帶來巨大風險。
下的常見保護方法。NET平臺是為了混淆編譯的程序集。VisualStudio附帶了壹個混淆工具Dotfuscator,它可以混淆程序集。功能包括名稱修改、進程混淆、字符串加密等。被Dotfuscator混淆的程序集可以避免被常用的反編譯工具破解。變量的表意性被破壞了,函數的內部流程也很混亂(如下【B1】)。它可以有效地保護源代碼。
public class 181:218
{
//字段
publicuint0
public ushort 1;
publicstaticreadonlyuint2
publicstaticreadonlyuint3
//方法
static 181();
public 181();
public 95.02();
public 95.02(ref 515A _ 0,uintA _ 1);
public 95.02(79A _ 0,refuintA _ 1);
public 95.02(ref 79a _ 0,uintA _ 1);
public 95.02(byte【】A _ 0,intA_1,refuintA _ 2);
public 95.02(ref 481A _ 0,intA_1,charA _ 2);
public 95.02(refstringA _ 0,intA_1,charA _ 2);
public 95.02(ref byte【】A _ 0,intA_1,refintA_2,uintA _ 3);
public 95.03(ref 79a _ 0,uintA _ 1);
public 95.03(ref byte【】A _ 0,intA_1,refintA_2,uintA _ 3);
public 95.04(ref byte【】A _ 0,intA_1,refintA_2,uintA _ 3);
}
public 95.00(refsbyteA _ 0,intA_1)
{
//此項被模糊處理,無法翻譯。
goto Label _ 0006
if(1!= 0)
{
}
95.0 local = 95 . 0 . 0;
byte num = 0;
local = this . 0(ref num,A _ 1);
a _ 0 =(sbyte)num;
returnlocal
在Unity引擎下,Mono編譯的程序集采用與相同的格式標準。網。可以直接被Dotfuscator混淆。然而,Unity引擎具有壹些特殊的功能,這使得模糊處理的工作方式不同於壹般的模糊處理。NET程序。第三節將主要討論這些特殊點。
Unity引擎下代碼混亂的特殊性
資源【B2】引用了代碼。Unity可視化編輯功能設計的關鍵是將代碼以組件的形式附加到資源實例中。與傳統遊戲相比,Unity的兩種資源(場景和預設)不僅包括數據,還包括附加在資源上的類對象。換句話說,在這兩種類型資源的存儲格式中,存在唯壹標識代碼類型的數據。混淆過程不能破壞這種對應關系,以便資源上的代碼邏輯可以正確執行。(Unity以這種方式設計的意義不是本文的重點,但將在另壹篇分享個人對Unity視覺編輯的理解的文章中詳細解釋。)
Unity項目發布到Web後,在界面中生成播放器可執行包(*。unity),將編譯匯編和打包這兩個步驟綁定在壹起。我們不能混淆編譯後的程序集,然後像普通壹樣將其鍵入播放器可執行包中。NET程序。
按函數名調用UnityEngine。MonoBehaviour是Unity引擎的重要組件基類。上面的很多方法,Unity都是通過方法名訪問的,比如Awake、Start、Update等等。如果這些方法被混淆地重命名,方法調用將失敗。這個問題相對容易處理,Dotfuscator的重命名功能提供了排除配置。只要我們獲得了從MonoBehaviour繼承的所有類型,就可以生成相應的排除配置並告訴Dotfuscator不要重命名這些方法。生成的配置摘錄如下【B3】:
& lt選項& gtXML序列化& lt/option & gt;
& ltexcludelist>
& lttype name =“CEventMgr | CGameRoot |…|…“regex =“true“exclude type =“false“& gt。
& lt方法名稱=“Update“regex =“true“/& gt;
& lt方法名稱=“late update“regex =“true“/& gt;
& ltmethod name =“fixed update“regex =“true“/& gt;
& ltmethod name =“Awake“regex =“true“/& gt。
& ltcustom attributename =“System。runtime . compiler services . compiler generated attribute“regex =“true“/& gt;
& lt方法名稱=“。*“regex =“true“/& gt;
& lt字段名=“。*“regex =“true“/& gt;
& lt/type & gt;
& lt鍵入name=“。*“regex =“true“& gt;
& ltcustom attributename =“anorenameinobuschase“regex =“true“/& gt。
& lt/type & gt;
& lt鍵入name=“。*“exclude type =“false“regex =“true“& gt;
& lt方法名稱=“。*“regex =“true“& gt;
& ltcustom attributename =“anorenameinobuschase“regex =“true“/& gt。
& lt/method & gt;
& lt/type & gt;
思考
什麽時候迷茫?因為編譯和打包Web項目的過程是捆綁在壹起的,所以政府沒有提供獨立的接口。(之前官方有反饋,但目前官方沒有提供具體方案。自行分析官方包裝格式是不可行的,也是不科學的。剩下的唯壹方法是將代碼編譯成DLL,並在混淆後將其添加到Unity項目中。
沿著這個思路,作者對QQ樂團的項目進行了嘗試。從項目中刪除所有與執行相關的代碼(不包括編輯器擴展的代碼),指定相關的Unity依賴庫,並將其編譯為DLL。然後將該DLL復制到原始項目中。這時,意想不到的事情發生了——項目中所有資源的所有代碼引用都丟失了。為了找到資源到代碼的映射形式,作者調整了Unity編輯器的設置,將資源的序列化格式改為文本格式,並進行了對比分析。通過GUID【B4】發現資源與特定代碼相對應。(如下所示)
m_ObjectHideFlags: 1
m _ PrefabParentObject:{ fileID:0 }
m _ prefabininternal:{ fileID:100100000 }
m _ game object:{ fileID:100000 }
m_Enabled: 1
m_EditorHideFlags: 0
m _ Script:{ fileID:11500000,guid:8ae 38 fa a3 fc9 f 91418a5a 9872 bcc 4 b 0 f,type: 1}
m_Name:
薄荷:1
mFloat:。
雖然中的類型沒有混淆,但GUID已經更改。用資源文件替換新的GUID,引用關系就恢復了。
Unity引擎下的特殊問題可以得到解決。於是順著這個思路,開發了幾個工具,獲取前後guid的對應關系,掃描所有資源替換guid。另壹方面,混淆後,類型的變量名發生了變化,資源中的變量名具有特定的值,因此需要替換資源中的變量名以對應混淆的變量名。這壹切都耗費了大量的精力,最後所有的工具都制作完成了。
然而,人算不如天算,最終導致這種方案走進了死胡同:Unity引擎在處理DLL中的模板類型時存在缺陷——DLL中的模板類型沒有GUID,無法被資源引用。在Unity的官方網站上有少量反饋,官方承認了這個bug,但沒有給出解決方案。但是“QQ樂團”項目在UI操作中廣泛使用模板類型,因此很難擺脫模板的使用。就這樣,這樣壹個不經意的問題終結了這次嘗試。
“帶著鐐銬跳舞”描述的是壹種在各種條件下盡可能追求解決方案的狀態。在總結了之前的失敗後,我們終於找到了壹個切實可行的改進方案,並成功應用於QQ Orchestra的網頁版和微客戶端版。
最終的想法是對項目進行分層。獨立創建壹個不被資源引用的“邏輯層”,包含最敏感的協議分析和各種系統模塊。將邏輯層的代碼獨立編譯成壹個DLL,混合後包含在項目中。邏輯層之外的代碼主要包括資源引用或系統模塊某些接口定義的不敏感內容,暫且稱為“行為層”。為了使邏輯層獨立編譯,我們要求邏輯層可以引用行為層,而行為層只能通過行為層中留下的邏輯層接口訪問邏輯層。這樣,我們保護了我們最重要的代碼,並繞過了資源引用代碼的問題。
該方案對項目架構提出了壹定的要求。首先,要求敏感代碼和資源是獨立的,需要壹個框架來加載每個模塊,而不是直接將模塊代碼附加到場景對象的資源上。第二,它要求層次分明,不允許反向依賴。對QQ樂團項目有利的消息是,QQ樂團從最早就實現了清晰的框架管理方法。因此,分層和實現接口訪問機制花費了壹定的時間,然後成功實現了該方案。
實際混淆步驟。QQ Orchestra使用VisualBuild進行版本構建和發布過程。下面描述版本構建中與混淆相關的過程:
從Unity項目的資產目錄中復制邏輯層的代碼目錄。以及編輯器擴展代碼(避免混淆後編輯器擴展代碼對邏輯層的依賴性喪失而導致編譯錯誤)。
調用Unity.exe命令行來編譯行為層的其余部分:
該函數實際執行:
建立管道。build player(new string【】{“Assets/obfuscated . unity“},“WebPlayerObfuscated“,
構建目標。WebPlayer、BuildOptions。無);
編輯器程序集(即編輯器擴展程序集)無法編譯,這會中斷編譯過程,以避免生成的DLL在BuildPlayer過程結束時被清理。在BuildPlayer之前,您可以故意在編輯器目錄中獲取壹個錯誤的代碼文件。
將生成的行為層DLL復制到邏輯層構建目錄。行為層DLL的路徑在項目的Library/ScriptAssemblies下,有兩個文件,Assembly-CSharp.dll和Assembly-CSharp-firstpass.dll。此外,它還將邏輯層所依賴的其他dll復制到構建目錄中,包括UnityEngine.dll和項目插件目錄中的相關庫。
調用單聲道編譯器mcs編譯邏輯層DLL-CodeGameLogic.dll。編譯命令如下:
生成DotObfuscator的配置文件“WebCfg.xml”。這裏有壹個我自己編寫的工具,掃描CodeGameLogic.dll中的類型,獲取不會混淆的類型和方法的名稱,並將它們添加到配置文件的排出列表中。比如“三。如第3節所示。
調用DotObfuscator對CodeGameLogic.dll進行模糊處理並獲取模糊處理後的codegamelogic . dll:
將困惑的CodeGameLogic.dll復制到項目中並構建項目。這裏需要註意的是,如果您正在構建壹個Web項目,您需要將dll復制到插件目錄中。如果它是壹個獨立的項目,只需將其直接復制到資產目錄。此外,在這種構造中不能有編譯錯誤,因此1需要刪除編輯器目錄中的編輯器擴展代碼。
接下來,將構建的項目與資源合並,您可以獲得壹個完整的混淆版本。
總結:
Unity項目的代碼更容易反編譯。需要註意代碼混亂。
Unity項目的代碼混淆方案實現起來更具限制性。本文介紹的方案是作者目前所知的唯壹可用的混淆方案。對項目的層次結構有強制性要求。最好在項目開始時考慮如何對項目進行分層,並將需要保護的內容放在混淆的層中。