unity 加密、防止反編譯、mono編譯
最近在弄unity的打包安全的問題,下面就記錄下自己搞定整個過程踩過來的坑吧,一方面留個記錄,另一方面給新手一個指引。
為什麼要加密呢
這個問題怎麼說呢?打個比方吧,就好比人為什麼要穿漂亮衣服打扮下自己一樣,無非是不讓別人看到不改看的地方。。。此處省略一萬字。。。
哪些東西要加密呢
其實我看大牛們的部落格,直接了當,直接討論加密方法,原理,新手一開始就搞的雲裡霧裡,這裡主要照顧到新手,老鳥直接自行略過即可。
unity在打包的時候會吧我們寫的C#程式碼直接打包到一個dll中去,具體位置如下:
windows: Data目錄\Managed\Assembly-CSharp.dll Android: assets\bin\Data\Managed\Assembly-CSharp.dll 這裡需要注意下,如果打包的是AndroidStudio工程路徑不太一樣,但差別不大; src\main\assets\bin\Data\Managed\Assembly-CSharp.dll IOS: 這個暫時不討論,ISO目前不考慮加密。
如果unity工程中沒有指令碼檔案那麼就不會有這個Assembly-CSharp.dll,但是這個dll是赤裸裸的暴露的啊,隨便拿個反編譯工具ILSpy、Reflector、de4dot等等,直接就能看到原始碼,是不是很無力,很無解。那麼我們要乾的工作第一步就是加密這個檔案。
加密Assembly-CSharp.dll
思路:打包完後,通過加密演算法加密Assembly-CSharp.dll
關鍵程式碼:以windows為例
[MenuItem("Build/x86")] private static void BuildWin_x86() { BuildWindows(BuildTarget.StandaloneWindows); } [MenuItem("Build/x64")] private static void BuildWin_x64() { BuildWindows(BuildTarget.StandaloneWindows64); } private static void BuildWindows(BuildTarget target) { //打包 string path = EditorUtility.SaveFilePanel(target.ToString(), EditorPrefs.GetString("BuildPath"), PlayerSettings.productName, "exe"); if (string.IsNullOrEmpty(path)) return; BuildPlayerOptions _buildOption = new BuildPlayerOptions(); _buildOption.locationPathName = path; _buildOption.scenes = EditorBuildSettingsScene.GetActiveSceneList(EditorBuildSettings.scenes); _buildOption.target = target; BuildPipeline.BuildPlayer(_buildOption); //加密 EncryptyWindowsAssemblyCSharp(path); } /// <summary> /// windows 平臺加密 /// </summary> /// <param name="path"></param> private static void EncryptyWindowsAssemblyCSharp(string path) { string acsPath = path.Replace(".exe", "_Data") + "/Managed/Assembly-CSharp.dll"; byte[] readByte = File.ReadAllBytes(acsPath); string key = "這裡是你的加密金鑰"; byte[] encrypt_data = Xxtea.XXTEA.Encrypt(readByte, key); //用xxtea加密庫加密 File.WriteAllBytes(acsPath, encrypt_data); }
至此Assembly-CSharp.dll加密的任務完成了,然後你打包,執行,發現
是不是很氣憤,什麼鬼,mono還報錯,這就引出下面的問題在mono中解密Assembly-CSharp.dll。
在mono中解密Assembly-CSharp.dll
首先要知道Assembly-CSharp.dll是在mono上執行的,mono是什麼東西,額,你就把理解為跨平臺的虛擬環境吧,對,沒錯擴平臺。那麼mono在哪裡呢,路徑如下:
windows: Mono\mono.dll Android: libs\armeabi-v7a\libmono.so 或 libs\x86\libmono.so 兩種不同的架構 當然AndroidStudio路徑不太一樣: src\main\jniLibs\armeabi-v7a\libmono.so 或src\main\jniLibs\x86\libmono.so IOS: 這個不討論了。
討論完路徑,再來看如何解密。
思路:修改mono原始碼,增加解密演算法,載入Assembly-CSharp.dll時解密即可。
下面看如何實現解密:
1.下載mono原始碼
輸入上圖關鍵字,選擇自己unity對應的版本,我用的unity是5.6.1版本,所以選擇了unity-5.6分支,然後點選右上角的按鈕直接zip下載就行,用git也行。解壓到本地,備用。
2.下載xxtea加密庫
解壓到本地備用。
3.把xxtea庫新增到mono庫
1.在下載好xxtea後,複製xxtea.c和xxtea.h兩個檔案到開始下載的mono的原始碼裡。
具體位置在mono/mono/metadata資料夾下。
2.然後再用vs2010開啟mono/msvc/mono.sln,將上面的xxtea的兩個檔案新增到libmono裡
(專案右鍵→新增項→現有項→選擇xxtea.h和xxtea.c檔案即可),記得儲存一些解決方案,
或者退出vs,會提示你儲存解決方案,要不然這兩個檔案沒有引入哦。
4.mono中新增解密演算法
<1>找到libmono下的image.c檔案,新增標頭檔案 #include "xxtea.h"
<2>在image.c檔案中找到mono_image_open_from_data_with_name函式,這個函式就是mono載入Assembly-CSharp.dll的入口,我們在這裡新增解密函式。
<3>增加程式碼
//Decrypt
if (strstr(name, "Assembly-CSharp.dll") != NULL)
{
const char *key = "這是上面unity加密Assembly-CSharp.dll的那個金鑰,這裡用來解密"; //記得替換金鑰
data = xxtea_decrypt(data, data_len, key, &data_len);
}
這是新增標頭檔案:
這是解密的函式:
到此新增完了加密演算法,下面開始編譯mono了。
注意:Andorid端因為要在linux或mac下編譯,沒法像vs那樣方便的新增檔案引用了,所以加密檔案xxtea.h需要合併到image.h 中去,xxtea.c需要合併到image.c中去,這樣編譯的時候就不會提示找不到解密方法了。
編譯mono原始碼
windows平臺:
一定要用vs2010編譯啊,用vs2013、vs2015各種死翹翹,。。。心好累哦,沒有vs2010的話,去裝一個,編譯環境直選C++的就行,其他的啥都別裝,要不了太大的空間。
<1>開啟vs 的命令列工具, Visual Studio x64 Win64 命令提示(2010)
<2>進入mono-unity-5.6\msvc目錄
<3>執行 msbuild.exe mono.sln /p:Configuration=Release_eglib
生成的dll在mono-unity-5.6\build\embedruntimes\win64,同時你還要編譯32位的
開啟vs x86 的命令列工具 Visual Studio 命令列提示(2010) ,同樣執行上面的操作
注意:vs2010預設就是x86平臺的,同時相容x64平臺
注意:如果LINK : fatal error LNK1123: 轉換到 COFF 期間失敗: 檔案無效或損壞類似的問題,這是因為按照高版本的vs之後再去安裝的vs2010,cvtres.exe版本錯誤導致的結果,所以凡是能使VS連結器找到正確的cvtres.exe版本的方法都可以解決該問題。
解決辦法: 當前系統中存在兩個cvtres.exe檔案,版本不同。讓VS2010使用.NET 4.5的cvtres.exe程式。重新命名或刪除:(vs2010安裝的位置)C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe。這樣C:\Windows\Microsoft.NET\Framework\v4.0.30319 (.NET 4.5)中的cvtres.exe檔案就可以被VS2010使用。
Android平臺:
樓主用mac編譯的,當然也可以用linux編譯。編譯環境網上有好多教程,我就不細說了,遇到的坑自己度娘就行。
注意:
執行build_runtime_android.sh時需要在mono-unity-5.6這個目錄下
在檔案第74行:-fpic -g -funwind-tables \中。把-g改為-O2(O0,O1,O2,O3分為好幾個壓縮檔次)。通過更改這個可以編譯出release版本。會比debug版本體積更小。
找到第154到156行:
#clean_build "$CCFLAGS_ARMv5_CPU" "$LDFLAGS_ARMv5" "$OUTDIR/armv5" #clean_build "$CCFLAGS_ARMv6_VFP" "$LDFLAGS_ARMv5" "$OUTDIR/armv6_vfp" clean_build "$CCFLAGS_ARMv7_VFP" "$LDFLAGS_ARMv7" "$OUTDIR/armv7a"
註釋掉前兩行。我們只需要armeabi-v7a和x86型別的libmono.so檔案。
修改build_runtime_android_x86.sh檔案內容:
修改第71行:-fpic -g\。去掉-g改為-fpic \。為了防止x86下的手機進入遊戲卡頓的情況。
如果順利的話就會提示如下:
到此mono庫是編譯完成了,但是還沒有玩,android平臺的libmono.so是可以用ida pro神器檢視的,所以需要對libmono.so二次加密,具體參見MOMO大大的教程http://www.xuanyusong.com/archives/3571
替換帶解密功能的mono庫
對,最後一步就是自動化,替換新編譯好的mono庫,接著在加密方法那個類裡寫:
核心程式碼如下:
private static void BuildWindows(BuildTarget target)
{
//打包
string path = EditorUtility.SaveFilePanel(target.ToString(), EditorPrefs.GetString("BuildPath"), PlayerSettings.productName, "exe");
if (string.IsNullOrEmpty(path))
return;
BuildPlayerOptions _buildOption = new BuildPlayerOptions();
_buildOption.locationPathName = path;
_buildOption.scenes = EditorBuildSettingsScene.GetActiveSceneList(EditorBuildSettings.scenes);
_buildOption.target = target;
BuildPipeline.BuildPlayer(_buildOption);
//加密
EncryptyWindowsAssemblyCSharp(path);
//替換mono,用於解密使用
ReplaceMonoDll(path, target);
Debug.Log(target.ToString()+"這是加密的哦,你解不開解不開解不開!");
}
/// <summary>
/// 替換mono
/// </summary>
/// <param name="path"></param>
/// <param name="target"></param>
private static void ReplaceMonoDll(string path,BuildTarget target)
{
switch (target)
{
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
{
string monoPath = path.Replace(".exe", "_Data") + "/Mono/mono.dll";
string encryptMonoPath = Application.dataPath + "/" + AppConst.AppName + "/Editor/mono/" + target.ToString() + "/mono.txt"; //用txt儲存,這個無所謂,我們只關心裡面的內容
byte[] readByte = File.ReadAllBytes(encryptMonoPath);
File.WriteAllBytes(monoPath, readByte); //當然你可以選擇直接複製替換
}
break;
case BuildTarget.Android:
{
//Android的程式碼自己寫哦,需要注意打包出來的是Eclipse還是Android Studio工程
}
break;
default:
break;
}
}
總結
加密只是為了保護自己的勞動成果,如果你選擇開源,那就無所謂了。當然unity5.4版本之後Andorid可以選擇IL2CPP這個黑科技了,不採用mono了,這樣打包之後就是c++程式碼,我們也就不用操心加密這回事了。如果仍然選擇mono就需要本文這種類似的處理了。整個過程是痛苦的,但是成功完成所有步驟那叫一個酣暢淋漓啊。祝君順利,如有不明白的地方歡迎留言交流。
附錄
給出的參考連結: