Unity3D放破解反編譯。DLL加密,mono解密。全程詳解。

KitStar發表於2017-05-18

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編譯:用於製作加密工具。

  1. 首先下載需要工具的工具3。進行安裝配置。可在cmd視窗中用“gcc -v”指令來檢視安裝結果。
  2. 下載工具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
  1. Build PC平臺的遊戲專案。在生成的資料夾中找到我們需要加密的xxxxx\xxxx_Data\Managed\Assembly-CSharp.dll檔案。把這個檔案同樣放在第二步中建立的encrypt資料夾中。
  2. 直接點選第二步建立的EcryptManage.exe檔案。執行加密Assembly-CSharp.dll檔案的操作。生成Assembly-CSharp_encrypt.dll檔案。這個就是加密後的檔案。把他從新名為原Assembly-CSharp.dll名稱。放回他所在源目錄。
  3. 使用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

  1. 使用上文中的加密檔案xxtea.c和xxtea.h複製到下載的mono的工程目錄裡,具體位置在mono-unity-5.6\mono\metadata資料夾下。

  2. 然後用vs2010開啟工程檔案mono-unity-5.6/msvc/mono.sln,開啟之後,通過“解決方案資源管理器”找到libmono項,再將複製在mono-unity-5.6\mono\metadata資料夾中兩個xxtea檔案新增到libmono項中,並找到libmono下的image.c,開啟,開始新增解密程式碼。

  3. 由上述引言可知,image.c就是我們要新增解密程式碼的檔案。首先,新增引用標頭檔案#include"xxtea.h"和#include <stddef.h>

  4. 在指令碼中找到方法 “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。

  1. 通過VMware Workstation 搭建Ubuntu虛擬環境。
  2. 安裝編譯所需相關工具:
    • 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:觀者可根據專案需求下載相應的包。

  1. 下載mono-unity-5.6檔案之後。這個資料夾的上層級最少要有兩層。如果不夠,則新建空資料夾用於存放此資料夾。
    原因:在build_runtime_android.sh檔案中,大概56行有 :KRAIT_PATCH_PATH="${CWD}/../../android_krait_signal_handler/build" 。這個表示,在編譯的時候會下載依賴檔案“android_krait_signal_handler”。如果無法保證上層級在兩層以上,也可以更改這行程式碼。
  2. 在下載的mono-unity-5.6資料夾中。找到build_runtime_android.sh檔案。具體位置在\mono-unity-5.6\external\buildscripts資料夾中。把他放在\mono-unity-5.6\根目錄中。
  3. 開啟 build_runtime_android.sh檔案。在15行perl ${BUILDSCRIPTSDIR}/PrepareAndroidSDK.pl -ndk=r10e -env=envsetup.sh && source envsetup.sh中。 -ndk=r10e描述的是所需ndk版本。
  4. 修改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行:
      #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檔案。所以註釋掉可以節省編譯時間,
  1. 修改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下的手機進入遊戲卡頓的情況。
  2. 注意: 如果你下載的是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 = 1if(1 if(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;
     		}
        .....
    

    }

    
    
  1. 檢查編譯所需環境是否合格。

    • 方法一:開啟終端。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指令。到了這步,說明你的環境大致安裝完成了。
  2. 開始第一次編譯。管理員身份執行復制在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依賴檔案。

  3. 如果你出現上面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

  4. 注意: 如果你下載的是32為的NDK可以忽略此步驟。在修改完上述9中所述的錯誤之後。還需要用/mono-unity-5.6/external/buildscripts/目錄下的PrepareAndroidSDK.pm替換/krait-signal-handler/目錄下的PrepareAndroidSDK.pm

編譯libmono.so檔案及其之後的操作 :

  1. 開始第二次編譯。 執行命令為:sudo ./ build_runtime_android.sh

    • 如果出錯:檢視/mono-unity-5.6/根目錄下面的config.log檔案。編譯的錯誤會全部輸出在此檔案中。
    • 如果不出錯,那麼恭喜你。在/mono-unity-5.6/builds目錄下就是編譯出來的armv7a和x86的libmono.so檔案。測試完成,那麼加入解密程式碼。然後進行最終編譯。
  2. 新增解密程式碼。還是找到image.c檔案。在 “mono_image_open_from_data_with_name”方法中新增解密程式碼。此處和windows平臺不同的是需要把xxtea.cxxtea.h的程式碼直接合併到image.c以及image.h中。

  3. 再次編譯。成功生成libmono.so檔案。然後複製到windows平臺下。替換源遊戲apk檔案中相應的檔案。包括。兩個libmono.so以及Assembly-CSharp.dll檔案。

從新打包APK :

需要工具:

  1. Apktool:反編譯apk和從新打包apk工具。工具相關教程:Android apk反編譯及重新打包流程



這裡寫圖片描述

Hello ,I am Edwin


首先謝謝大家的支援,其次如果你碰到什麼其他問題的話,歡迎來 我自己的一個 討論群559666429來(掃掃下面二維碼),大家一起找答案,共同進步 同時歡迎各大需求商入住,釋出自己的需求,給群內夥伴提供副職,賺取外快。

這裡寫圖片描述


相關文章