1. 程式人生 > >unity 加密、防止反編譯、mono編譯

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編譯。編譯環境網上有好多教程,我就不細說了,遇到的坑自己度娘就行。

注意:

  1. 執行build_runtime_android.sh時需要在mono-unity-5.6這個目錄下

  2. 在檔案第74行:-fpic -g -funwind-tables \中。把-g改為-O2(O0,O1,O2,O3分為好幾個壓縮檔次)。通過更改這個可以編譯出release版本。會比debug版本體積更小。

  3. 找到第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檔案。

  4. 修改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就需要本文這種類似的處理了。整個過程是痛苦的,但是成功完成所有步驟那叫一個酣暢淋漓啊。祝君順利,如有不明白的地方歡迎留言交流。

附錄

給出的參考連結: