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:
注意這裡只是簡單加密,線上專案不要搞的這麼簡單…………