1. 程式人生 > >Unity3D 加密 Assembly-CSharp.dll (Android平臺) 防止反編譯

Unity3D 加密 Assembly-CSharp.dll (Android平臺) 防止反編譯

0、加密的原理
Unity3D 是基於 Mono的,我們平時寫的 C# 指令碼都被編譯到了 Assembly-CSharp.dll ,然後 再由 Mono 來載入、解析、然後執行。
Mono 載入 Assembly-CSharp.dll 的時候就是讀取檔案到記憶體中,和平時讀取一個 遊戲資源 檔案沒什麼區別。
為了防止別人破解,我們會對遊戲資源加密,簡單點的 比如修改檔案的一個位元組 或者 位移一下 。只要簡單的修改一下,破壞原來的檔案資料結構,別人就不能用通用的讀取工具來讀取了。
Mono 讀取 Assembly-CSharp.dll 也是如此,我們只要簡單的 修改 Assembly-CSharp.dll 的一個位元組,就能破壞掉 Assembly-CSharp.dll 的資料結構,然後 Assembly-CSharp.dll 就不再是一個 dll 了,就變成了一個普通的檔案,一個系統都不認識的未知型別的檔案。

Assets\bin\Data\Managed\Assembly-CSharp.dll

在 Android 中,由 libmono.so 來載入 Assembly-CSharp.dll 。
libmono.so 這就是 Mono 了 。

assets\libs\armeabi-v7a
libmono.so
libunity.so

既然 Assembly-CSharp.dll 被我們加密了,那 libmono.so 這個通用的讀取工具就不能再 讀取已經加密的 Assembly-CSharp.dll 了,所以我們也要修改 重新編譯 libmono.so ,給它加上解密函式才行。

Unity3d 是基於 Mono2.0 的,而 Mono2.0是免費開源的。所以基於各種開源協議 ,Unity 官方也將自己修改過的 Mono 開源出來,我們下載過來然後修改 重新編譯出自己的 libmono.so 。
專案託管在 Github 上,專案地址:

https://github.com/Unity-Technologies/mono 

瞭解到一些原理背景後就可以開始進行操作了。
1、安裝ubuntu系統
在 Windows 上面進行編譯比較麻煩……在 Linux 或 Mac 上會比較簡單,網上多數教程都是基於 Mac的,我這裡選擇了最新的 Ubuntu 系統。

Ubuntu 官方提供了 ISO 燒錄工具:

http://www.pendrivelinux.com/downloads/Universal-USB-Installer/Universal-USB-Installer-1.9.6.3.exe  

Ubuntu 系統下載:

http://www.ubuntu.org.cn/download/desktop  
http://old-releases.ubuntu.com/releases/14.04.1/ubuntu-14.04.1-desktop-i386.iso?_ga=1.187436840.1241524278.1457318071  

使用上面那個工具安裝到U 盤然後安裝到 電腦中。
2、下載ANDROID_NDK
安裝完 Ubuntu 後,在 Ubuntu 中 ,注意32 和64位區別
64 位下載 :

連結:http://pan.baidu.com/s/1eRXqy1k 密碼:hy6s  

32位下載 :

連結:http://pan.baidu.com/s/1bWcCnO 密碼:fol6  

sudo su 切換到root安裝

./android-ndk-r10e-linux-x86.bin  

安裝後在安裝目錄裡面找到 RELEASE.txt ,裡面記錄著NDK 完整版本號,修改為 r10e
(Mono的編譯指令碼是讀取這個RELEASE.txt中記錄的版本號,然後和編譯指令碼中填寫的版本號做匹配的,如果不匹配就會去Google下載)
設定環境變數 ANDROID_NDK_ROOT

sudo gedit /etc/bashrc  

新增一行

export ANDROID_NDK_ROOT=/home/captain/Downloads/android-ndk-r10e;  

讓環境變數立即生效

source /etc/bashrc

測試是否新增成功

echo $ANDROID_NDK_ROOT 

3、編譯 Development 版本的 libmono.so
從 Github 下載 unity-mono,我這裡下載的4.6版本,到branch裡面搜4.6

https://github.com/Unity-Technologies/mono/tree/unity-4.6  

解壓,然後拷貝 mono-unity-4.6/external/buildscript/build_runtime_android.sh 到 mono-unity-4.6/ 根目錄中。
切換到mono-unity-4.6/ 目錄中,使用 root 執行 build_runtime_android.sh ,

./build_runtime_android.sh  

會提示沒有安裝Git

然後安裝git:

sudo apt-get install git  

修改 build_runtime_android.sh Line 113 ,改為:

(cd "$KRAIT_PATCH_PATH" && perl ./build.pl)

刪掉第一行

#!/usr/bin/env perl -w  

繼續執行出錯,提示 ANDROID_NDK 版本不對,又要下載,下載又失敗,修改成我們自己的版本。

用命令 找到build.pl

Find / -name "build.pl"  

修改BuildAndroid 函式裡面的 r9 為 自己下載的版本 r10e

然後找到

external/android_krait_signal_handler/jni/Application.mk 

修改裡面的

TOOLCHAIN_VERSION := clang3.3  

TOOLCHAIN_VERSION :=4.8  

因為r10e裡面已經沒有3.3了,只有4.8。
然後繼續編譯
提示沒有 autoreconf
使用下面命令安裝autoreconf:

sudo apt-get update  
sudo apt-get install autoconf  

同樣的方法,順便把下面的包都安裝一下

* autoconf  
* automake  
* bison  
* gcc  
* gettext  
* glib >= 2.0  
* libtool  
* make  
* perl  

都安裝之後,繼續執行指令碼,開始編譯滾程式碼了

過了十多分鐘,編譯好啦,用 find / -name “libmono.so” 找到編譯出來的 so 檔案。

這次編譯出來的是 Development 版本的,現在就可以 把這個 編譯出來的 libmono.so 拷貝到 自己電腦上,在匯出 Android 專案之後,替換掉 libs目錄裡面的 libmono.so 。
然後執行到 手機上測試是否OK,我這裡是測試 OK的。

至於怎麼樣從 Ubuntu 電腦上把 libmono.so 拷貝到 Windows 電腦上,直接用 U 盤就可以。然而我沒有 U盤,所以我在 Ubuntu 上開啟了 SSH 服務,然後在 Windows 上面連線到了 Ubuntu ,然後下載到 Windows 電腦上,比較麻煩。
在Ubuntu上開啟ssh-server ,安裝完畢之後自動會啟動

apt-get install openssh-server 

手動啟動服務

service ssh start  
/etc/init.d/ssh start

檢視是否啟動成功

ps -s | grep ssh  
http://jaist.dl.sourceforge.net/project/filezilla/FileZilla_Client/3.16.0/FileZilla_3.16.0_win64-setup_bundled.exe  

輸入Ubuntu的 ip 帳號 密碼 埠22 進行SFTP連線 。

4、編譯 Release 版本的 libmono.so
修改所有的 build_runtime_android.sh 和 build_runtime_android_x86.sh
找到 -fpic -g 去掉 -g

再次執行 build_runtime_android.sh
開始編譯滾程式碼了,
編譯成功,用 find / -name “libmono.so” 找到編譯出來的 so 檔案。然後同樣 拷貝到 Windows 電腦上,匯出一個 Release 版本的 Android 工程出來,執行到手機上測試。
我這邊測試OK。

5、修改 libmono.so ,增加 解密 函式
找到 /metadata/image.c 這個檔案
找到

mono_image_open_from_data_with_name  

這個函式,這個函式就是用來讀取 dll 的。修改這個函式,新增解密程式碼。

mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)  
{  
    MonoCLIImageInfo *iinfo;  
    MonoImage *image;  
    char *datac;  

    /* 加入 Decrypt */  
    if(name != NULL)  
    {  
        if(strstr(name,"Assembly-CSharp.dll")){  
            data[0]-=1; //這裡注意對應自己的加密方式進行修改。這裡是對第一個位元組 -1 ,對應加密演算法是 +1;  
        }  
    }  

    ………………  
    ………………  
} 

這裡注意對應自己的加密方式進行修改。這裡是對第一個位元組 -1 ,對應加密演算法是 +1;
再次執行 build_runtime_android.sh
開始編譯滾程式碼了,
編譯成功,用 find / -name “libmono.so” 找到編譯出來的 so 檔案。拷貝到Windows 電腦上。
再次匯出一個 Release 版本的 工程,然後執行到 手機上發現,場景正常,但是程式碼都不執行了!!
這是因為 我們修改了 libmono.so ,增加了解密函式,但是當前的 Assembly-CSharp.dll 是沒有加密的!!!,所以 libmono.so 這裡是讀取 Assembly-CSharp.dll 失敗了,所以我們寫的程式碼都沒有執行。

現在已經知道了怎麼增加解密方法,以及編譯 Development 和 Release 版本。
所以現在分別編譯 Development 、 Release 的加密了的 libmono.so 。
每次編譯 都會自動生成 armv7a 、 x86 這兩個 CPU 的 libmono.so 。

我們把 這幾個 libmono.so 拷貝到 Unity 專案中,存放到 Editor 資料夾,可以下載我的例子檢視。

6、加密 Assembly-CSharp.dll
在 Unity3d 中匯出 Android 工程之後,對 Assembly-CSharp.dll 進行加密,然後替換掉 匯出的 Android 工程中的 libmono.so 檔案。
Unity3d 提供了 Android 工程匯出完畢的回撥,我們在這個回撥中 替換掉 匯出的 Android 工程中的 libmono.so 檔案, 如下:
BuildPostprocessor.cs :

/** 
 * 檔名:BuildPostprocessor.cs 
 * Des:在匯出Eclipse工程之後對assets/bin/Data/Managed/Assembly-CSharp.dll進行加密 
 * Author:Captain 
 * **/  


using UnityEngine;  
using UnityEditor;  
using UnityEditor.Callbacks;  
using System.IO;  

public class BuildPostprocessor  
{  
    [PostProcessBuildAttribute(1)]  
    public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)  
    {  
        if (target == BuildTarget.Android && (!pathToBuiltProject.EndsWith(".apk")))  
        {  
            Debug.Log("target: " + target.ToString());  
            Debug.Log("pathToBuiltProject: " + pathToBuiltProject);  
            Debug.Log("productName: " + PlayerSettings.productName);  

            string dllPath = pathToBuiltProject + "/" + PlayerSettings.productName + "/" + "assets/bin/Data/Managed/Assembly-CSharp.dll";  

            if (File.Exists(dllPath))  
            {  
                //加密 Assembly-CSharp.dll;  
                Debug.Log("Encrypt assets/bin/Data/Managed/Assembly-CSharp.dll Start");  

                byte[] bytes = File.ReadAllBytes(dllPath);  
                bytes[0] += 1;  
                File.WriteAllBytes(dllPath, bytes);  

                Debug.Log("Encrypt assets/bin/Data/Managed/Assembly-CSharp.dll Success");  

                Debug.Log("Encrypt libmono.so Start !!");  

                Debug.Log("Current is : " + EditorUserBuildSettings.development.ToString());  

                //替換 libmono.so;  
                if (EditorUserBuildSettings.development)  
                {  
                    string armv7a_so_path = pathToBuiltProject + "/" + PlayerSettings.productName + "/" + "libs/armeabi-v7a/libmono.so";  
                    File.Copy(Application.dataPath + "/MonoEncrypt/Editor/libs/development/armeabi-v7a/libmono.so", armv7a_so_path, true);  

                    string x86_so_path = pathToBuiltProject + "/" + PlayerSettings.productName + "/" + "libs/x86/libmono.so";  
                    File.Copy(Application.dataPath + "/MonoEncrypt/Editor/libs/development/x86/libmono.so", x86_so_path, true);  
                }  
                else  
                {  
                    string armv7a_so_path = pathToBuiltProject + "/" + PlayerSettings.productName + "/" + "libs/armeabi-v7a/libmono.so";  
                    File.Copy(Application.dataPath + "/MonoEncrypt/Editor/libs/release/armeabi-v7a/libmono.so", armv7a_so_path, true);  

                    string x86_so_path = pathToBuiltProject + "/" + PlayerSettings.productName + "/" + "libs/x86/libmono.so";  
                    File.Copy(Application.dataPath + "/MonoEncrypt/Editor/libs/release/x86/libmono.so", x86_so_path, true);  
                }  

                Debug.Log("Encrypt libmono.so Success !!");  
            }  
            else  
            {  
                Debug.LogError(dllPath+ "  Not Found!!");  
            }  
        }  
    }  
}

好了,以上就是所有的 步驟。
再次匯出 Android 工程,然後執行到手機上,測試是否OK,我這邊是測試 OK的。
然後找到 匯出的專案中的 dll 檔案,在下面的路徑:

assets/bin/Data/Managed/Assembly-CSharp.dll  

用 .NetReflector 檢視、或者拖入到 MonoDeveloper 中,發現都無法正常開啟。
這說明我們加密成功了!!

顯示:

File is not a portable executable.DOS header does not contain 'MZ' signature

可以下載我的測試專案來檢視和測試:

我也把它打包成了 Package:

注意這裡只是簡單加密,線上專案不要搞的這麼簡單…………