Unity3D放破解反編譯。DLL加密,mono解密。全程詳解。
Unity3D加密教程
引言 :
為了防止別人通過反編譯來破解修改自己的遊戲專案。可以通過兩種比較成熟的方案來預防。一種是混淆,另一種就是加密(加殼)。由於加殼後的檔案反編譯比混淆後的難度更大,所以我們這裡採取加密的方法對PC平臺和安卓平臺的應用進行加密。
那麼我們去加密什麼呢? Unity通過Mono來達到跨平臺的效果。在Build編譯時會將你編寫的code轉為符合CLI的CIL(Common Intermediate Language,中文:中間語言),並且主要的Code會編譯在Assembly-CSharp.dll裡面,然後再有mono來載入,解析,執行。Mono載入Assembly-CSharp.dll的時候就是讀取檔案到記憶體中,和讀取一個遊戲資原始檔沒多大區別。所以我們要做的就是把這個Assembly-CSharp.dll檔案加密,簡單點的可以修改檔案的一個位元組或者位移一下,破壞其原有的結構。就無法使之通過例如Reflector9VSPro去反編譯,因為此時的Assembly-CSharp.dll已經不是一個正確的dll檔案了。可尷尬的是,別人沒法反編譯出文件內容了,Mono自己也不認識了。這將導致,遊戲無法執行。所以,我們在對其加密的同時也要加入相應的解密演算法。
image.c指令碼在遊戲執行時會去主動載入Assembly-CSharp.dll檔案。那麼我們在image.c相應的方法裡面加入解密演算法,然後從新編譯Mono,生成相應的程式集,來替換待解密專案中的相應檔案。就可以達到遊戲執行正常又安全的效果。
So ,Follow me to see.
Window篇
加密Assembly-CSharp.dll :
需要工具:
1. Reflector9VSPro:反編譯工具:用於成果測試。
2. xxtea:此教程所用的第三方加密解密演算法:用於加密解密。
3. MinGw:使用gcc編譯:用於製作加密工具。
- 首先下載需要工具的工具3。進行安裝配置。可在cmd視窗中用“gcc -v”指令來檢視安裝結果。
- 下載工具2。資料夾中有xxtea.h、xxtea.c兩個檔案。這兩個就是加密解密的程式碼。
- 建立新資料夾“encrypt”,把這兩個檔案放在“encrypt”資料夾中。
- 新建C語言指令碼“EncryptManage.c”,同樣放在“encrypt”中。指令碼內容:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "xxtea.h"
#define SIZE 1024*1024*10
void main()
{
FILEFILE *infp = 0;
if((infp=fopen("Assembly-CSharp.dll","rb"))==NULL)
{
printf("Assembly-CSharp.dll Read Error\n");//開啟操作不成功
return;//結束程式的執行
}
//char buffer[SIZE];
char* buffer = (char*)malloc(sizeof(char)*SIZE);
memset(buffer,0,sizeof(char)*SIZE);
int rc = 0;
int total_len = 0;
total_len = fread(buffer , sizeof(unsigned char) , SIZE , infp);
printf("Read Assembly-CSharp Successfully and total_len : %d \n" , total_len);
//加密DLL
size_t len;
char* key = "123456"; //此處位金鑰。可自由更改
charchar *encrypt_data = xxtea_encrypt(buffer,total_len, key, &len);
printf("Encrypt Dll Successfully and len : %d\n" , len);
//寫Dll
FILE* outfp = 0;
if((outfp=fopen("Assembly-CSharp_encrypt.dll","wb+"))==NULL)
{
printf("Assembly-CSharp_encrypt.dll Read Error\n");//開啟操作不成功
return;//結束程式的執行
}
int rstCount = fwrite(encrypt_data , sizeof(unsigned char) , len , outfp);
fflush(outfp);
printf("Write len : %d\n", rstCount);
fclose(infp);
fclose(outfp);
free(buffer);
free(encrypt_data);
}
- 開啟cmd視窗。cd到encrypt資料夾中。然後使用gcc編譯EncryptManage.c檔案。生成可自動化加密的exe檔案"EncryptManage.exe"。
程式碼為:gcc xxtea.c EncryptManage.c –o EncryptManage
- Build PC平臺的遊戲專案。在生成的資料夾中找到我們需要加密的xxxxx\xxxx_Data\Managed\Assembly-CSharp.dll檔案。把這個檔案同樣放在第二步中建立的encrypt資料夾中。
- 直接點選第二步建立的EcryptManage.exe檔案。執行加密Assembly-CSharp.dll檔案的操作。生成Assembly-CSharp_encrypt.dll檔案。這個就是加密後的檔案。把他從新名為原Assembly-CSharp.dll名稱。放回他所在源目錄。
- 使用Reflector9VSPro工具進行反編譯Assembly-CSharp.dll。如果發現反編譯失敗。證明加密成功。此時可相應的運行遊戲檔案。會發現遊戲也無法正常執行。
重編譯可解密的mono.dll檔案 :
需要工具:
注意:如果你希望編譯順利進行,不把時間浪費在一些坑上。就需要這些工具。
1. Visual Studio 2010:去編譯和修改mono專案:由於mono-unity官方專案使用VS2010建立的,所用使用vs2010會避免很多的衝突,在用VS2015的時候就會碰到配置,引用,平臺工具集型號…等等問題,再加上其他各種未知問題,最後選擇了VS2010版本,才成功。
2. mono-unity-5.6:我在此用的時5.6的包,觀者可根據需求下載相應版本的包:我們通過對它的修改新增,從編譯。生成相應的mono.dll檔案,用於解密。經過測試,可編譯成功的版本有4.6,5.1,5.5,5.6
-
使用上文中的加密檔案xxtea.c和xxtea.h複製到下載的mono的工程目錄裡,具體位置在mono-unity-5.6\mono\metadata資料夾下。
-
然後用vs2010開啟工程檔案mono-unity-5.6/msvc/mono.sln,開啟之後,通過“解決方案資源管理器”找到libmono項,再將複製在mono-unity-5.6\mono\metadata資料夾中兩個xxtea檔案新增到libmono項中,並找到libmono下的image.c,開啟,開始新增解密程式碼。
-
由上述引言可知,image.c就是我們要新增解密程式碼的檔案。首先,新增引用標頭檔案
#include"xxtea.h"
和#include <stddef.h>
。 -
在指令碼中找到方法 “mono_image_open_from_data_with_name”。這個就是載入Assembly-CSharp.dll的入口。那麼我們就在此方法中新增我們的解密程式碼。
程式碼如下:
MonoImage *
mono_image_open_from_data_with_name (charchar *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const charchar *name)
{
MonoCLIImageInfo *iinfo;
MonoImage *image;
charchar *datac;
//第一個引數data指向執行時Assembly-CSharp.dll的記憶體地址
if (!data || !data_len) {
if (status)
*status = MONO_IMAGE_IMAGE_INVALID;
return NULL;
}
//這是新增的程式碼,開始 你也可以換成自己想要的解密方法
if(name != NULL)
{
if (strstr(name, "Assembly-CSharp.dll")) {
char* key = "123456"; //此處金鑰需要對應加密時候所建立的金鑰
size_t len;
char* decryptData = (charchar *)xxtea_decrypt(data, data_len,key, &len);
int i = 0;
for (i = 0; i < len; ++i)
{
data[i] = decryptData[i];
}
g_free(decryptData);
data_len = len;
}
}
//這是新增的程式碼,結束
datac = data;
5. 最後,我們開始編譯工程。
* 開啟 Visual Studio Command Prompt(2010)
* cd 到mono-unity-5.6\msvc目錄中
* 執行msbuild.exe mono.sln /p:Configuration=Release_eglib命令
注意:直接開啟mono.sln解決方案,通過Visual Studio直接生成是編譯不了的,這是個坑。
6.我在這裡用了54秒就編譯成功了。生成的dll位置在mono\builds\embedruntimes\win32\mono.dll。(63位的mono.dll檔案要用Visual Studio x64 Win64 命令提示(2010)去編譯)
7. 最後把你編譯出來的mono.dll檔案複製到你的專案包中,替換Mono資料夾中的源mono.dll檔案。
好了,OK。運行遊戲,完美執行。再用反編譯工具去反編譯Assembly-CSharp.dll。發現無法反編譯。
Android篇
注意:由於windows和ubuntu平臺中對Assembly-CSharp.dll的加密操作是一樣的,所用不做多餘敘述,Assembly-CSharp.dll的加密可以直接使用windows平臺加密後的Assembly-CSharp.dll檔案。 在window篇中我們要使用的是編譯後的mono.dll檔案。但是在Android專案中,我們要使用的是libmono.so檔案。其他需要操作的物件檔案都一樣。所以我們只需要使用Ubuntu32為系統去重編譯可解密的libmono.so檔案即可。
Ubuntu 32位虛擬系統搭建:
需要工具:
1. Ubuntu32位:ubuntu系統:由於windows上面比較麻煩,而且錯誤特別多。而ubuntu64位不選擇的原因是坑很多。用32位系統至少可以讓坑少一半。
2. VMware Workstation :桌面虛擬計算機軟體:用於搭載Ubuntu系統。
3. android-ndk-r10e-linux 32位:基於linux系統的NDK:NDK的版本是根據專案需求下載的,此處操作的mono-unity-5.6的mono專案包,此專案要求使用“r10e”版本的NDK。
- 通過VMware Workstation 搭建Ubuntu虛擬環境。
- 安裝編譯所需相關工具:
- autoconf
- automake
- bison
- gcc
- gettext
- git //編譯時,專案會需要git命令去下載依賴檔案。
- glib >=2.0 //編譯所需庫檔案。
- libtool //如果你是64為的NDK。由於交叉編譯工具是32為的。則需要安裝的libtool也是32為的。所以,這個工具在安裝的時候要使用
sudo apt-get install libtool*
命令安裝,注意要加“ * ”。這樣可以安裝整個libtool-bin庫。非則的話就算安裝了libtool也會一直提示沒有安裝libtool。在32位系統下不存在這個問題。 - make //編譯工具。
- perl //用於執行pl指令碼。
命令為:sudo apt-get install autoconf automake bison build-essential gettext git libglib2.0 libtool* perl
3. 配置NDK的環境,配置步驟:
a. 在終端輸入sudo gedit ~/.bashrc。開啟環境變數配置檔案。
b. 在檔案末端直接加入環境變數:
NDK_ROOT=/home/xxxx/xxxx/android-ndk-r10e
NDK=$NDK_ROOT
ANDROID_NDK_ROOT=$NDK_ROOT
export NDK_ROOT NDK ANDROID_NDK_ROOT
c. 儲存並且使環境生效:` source ~/.bashrc ``
編譯前的檔案修改 :
需要工具:
1. mono-unity-5.6:觀者可根據專案需求下載相應的包。
- 下載mono-unity-5.6檔案之後。這個資料夾的上層級最少要有兩層。如果不夠,則新建空資料夾用於存放此資料夾。
原因:在build_runtime_android.sh檔案中,大概56行有 :KRAIT_PATCH_PATH="${CWD}/../../android_krait_signal_handler/build"
。這個表示,在編譯的時候會下載依賴檔案“android_krait_signal_handler”。如果無法保證上層級在兩層以上,也可以更改這行程式碼。 - 在下載的mono-unity-5.6資料夾中。找到build_runtime_android.sh檔案。具體位置在
\mono-unity-5.6\external\buildscripts
資料夾中。把他放在\mono-unity-5.6\
根目錄中。 - 開啟 build_runtime_android.sh檔案。在15行
perl ${BUILDSCRIPTSDIR}/PrepareAndroidSDK.pl -ndk=r10e -env=envsetup.sh && source envsetup.sh
中。-ndk=r10e
描述的是所需ndk版本。 - 修改build_runtime_android.sh檔案內容:
- 在檔案第6行的
export ANDROID_PLATFORM=android-9
下面新增exportANDROID_NDK_ROOT=/home/xxxx/xxxx/android-ndk-r10e
。為防止不必要的錯誤,手動指定ndk目錄。 - 如果出現無法找到 envsetuo.sh檔案的錯誤。則需要手動指定envsetuo.sh檔案所在目錄。第15行
perl ${BUILDSCRIPTSDIR}/PrepareAndroidSDK.pl -ndk=r10e -env=envsetup.sh && source envsetup.sh
末端的``source中直接指定檔案目錄
source xxxx\xxxx\mono-unity-5.6\envsetup.sh`。 - 在檔案第74行:
-fpic -g -funwind-tables \
中。把-g
改為-O2
(O0,O1,O2,O3分為好幾個壓縮檔次)。通過更改這個可以編譯出release版本。會比debug版本體積更小。 - 找到第154到156行:
註釋掉前兩行。我們只需要armeabi-v7a和x86型別的libmono.so檔案。所以註釋掉可以節省編譯時間,#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"
- 在檔案第6行的
-
修改build_runtime_android_x86.sh檔案內容:
- 在
\mono-unity-5.6\external\buildscripts
資料夾中找到:build_runtime_android_x86.sh
檔案。開啟準備修改。 - 同build_runtime_android.sh的修改一樣。在第6行下面新增NDK目錄:
exportANDROID_NDK_ROOT=/home/xxxx/xxxx/android-ndk-r10e
。 - 修改第71行:
-fpic -g\
。去掉-g
改為-fpic \
。為了防止x86下的手機進入遊戲卡頓的情況。
- 在
-
注意: 如果你下載的是32為的NDK可以忽略此步驟。修改
PrepareAndroidSDK.pm
檔案內容。檔案在/mono-unity-5.6/external/buildscripts/
下面。- 直接翻到最後的第435行中,找到PrepareNDK方法。
sub PrepareNDK
{
my ($ndk) = @_;
my $ndk_root = KaTeX parse error: Expected '}', got 'EOF' at end of input: ENV{NDK_ROOT_ENV};
$ndk_root = ndk_root=~/(.*)/$/);# 讀取NDK目錄下的RELEASE.TXT檔案以檢視NDK版本號 if (-e $ndk_root and open RELEASE, "<", catfile("$ndk_root", "RELEASE.TXT")) { my @content = <RELEASE>; close RELEASE; chomp(@content); my $current = $content[0]; print "\tCurrently installed = " . $current . "\n"; # remove the possible '(64-bit)' from the end #如果你下載的NDK是Linux 64-bit NDK,它的版本號是” r10e-rc4(64-bit) “。那麼將會在次數出錯。程式將一直找不到你的NDK。 #所以需要修改下面程式碼為:`my @curr_arr = split(/\-|\s/, $current)` #或者直接修改NDK目錄下RELEASE.TXT檔案內容為:` r10e (64-bit) `或者 ` r10e` my @curr_arr = split(' ', $current); $current = $curr_arr[0]; if ($ndk eq $current) { print "\tNDK '$ndk' is already installed\n"; return; } .....
}
-
檢查編譯所需環境是否合格。
- 方法一:開啟終端。cd 到mono-unity-5.6目錄中,使用管理員許可權執行autogen.sh檔案。命令為:
sudo ./autogen.sh
。這是個批處理檔案,幫我們檢查編譯mono-unity所需要的環境。如果出現缺失庫的錯誤,那麼根據錯誤進行相應修改和安裝。這個檔案會幫你執行configure,make,make clean,make distclean
等命令。 - 方法二:在終端中cd到mono-unity-5.6目錄中。使用管理員許可權執行
sudo ./configure --prefix=/usr/bin
命令。也是檢查編譯環境是否合格,如何沒有合格,會報錯。如果合格,則會他提示你執行make
指令。到了這步,說明你的環境大致安裝完成了。
- 方法一:開啟終端。cd 到mono-unity-5.6目錄中,使用管理員許可權執行autogen.sh檔案。命令為:
-
開始第一次編譯。管理員身份執行復制在mono-unity-5.6根目錄下的
build_runtime_android.sh
檔案,命令為:sudo ./ build_runtime_android.sh
。不要使用"sudo sh build_runtime_android.sh "去執行。第一次編譯通常情況下都會碰到/usr/bin/env: perl -w: No such file or directory
的錯誤。沒關係。這次編譯只是為了下載krait-signal-handler
依賴檔案。 -
如果你出現上面8所述的
/usr/bin/env: perl -w: No such file or directory
錯誤。那麼開啟剛才下載的krait-signal-handler
資料夾。找到裡面的build.pl
檔案。修改第一行#!/usr/bin/env perl -w
為#!/usr/bin/perl -w
。 -
注意: 如果你下載的是32為的NDK可以忽略此步驟。在修改完上述9中所述的錯誤之後。還需要用
/mono-unity-5.6/external/buildscripts/
目錄下的PrepareAndroidSDK.pm
替換/krait-signal-handler/
目錄下的PrepareAndroidSDK.pm
。
編譯libmono.so檔案及其之後的操作 :
-
開始第二次編譯。 執行命令為:
sudo ./ build_runtime_android.sh
。- 如果出錯:檢視
/mono-unity-5.6/
根目錄下面的config.log
檔案。編譯的錯誤會全部輸出在此檔案中。 - 如果不出錯,那麼恭喜你。在
/mono-unity-5.6/builds
目錄下就是編譯出來的armv7a和x86的libmono.so
檔案。測試完成,那麼加入解密程式碼。然後進行最終編譯。
- 如果出錯:檢視
-
新增解密程式碼。還是找到
image.c
檔案。在“mono_image_open_from_data_with_name”
方法中新增解密程式碼。此處和windows平臺不同的是需要把xxtea.c
和xxtea.h
的程式碼直接合併到image.c
以及image.h
中。 -
再次編譯。成功生成
libmono.so
檔案。然後複製到windows平臺下。替換源遊戲apk檔案中相應的檔案。包括。兩個libmono.so
以及Assembly-CSharp.dll
檔案。