Unity3D DLL加密

Unity3D打包android應用程序時,如果不對DLL加密,很容易被反編譯,導致代碼的泄露。通常的做法是通過加密DLL或者對代碼進行混淆。本文的所要探討的是通過加密的方式來對DLL進行保護,並詳細記錄加密的操作過程。

主要參考

    雨松的博文:http://www.xuanyusong.com/archives/3553

http://csftech.logdown.com/posts/452269-android-unity-encryption

    這兩篇文章已經詳細介紹了加密的過程,但是還是有些坑和有些操作沒有給出。

原理說明

所有的代碼編譯後是在apkassetsbinDataManagedAssembly-CSharp.dll下,要做的就是對這個DLL進行加密,Assembly-CSharp.dll由libmono.so加載,所以需要在libmono.so中對加密過的Assembly-CSharp.dll進行解密,幸好unity提供了mono的代碼可以進行編譯修改。當然對於libmono.so也存在被反編譯的風險,本文暫不考慮。

準備

  • linux系統。本文選擇採用的是Ubuntu14.04,虛擬機也可以,另外可以用Windows + Cygwin進行編譯,不過考慮到這樣做可能踩坑更多,果斷放棄。
  • unity mono源碼,可以在https://github.com/Unity-Technologies/mono下載,branch選擇unity4.6,直接下zip包,或者git下來都可以,下載下來的zip包為mono-unity-4.6.zip。
  • unity3d 4.6版本,本文試驗的是4.6的編譯,注意一定要安裝4.6.6+的版本,否則重編的libmono.so會報錯(坑一)。
  • android ndk, 版本可以根據unity-mono中用的版本來下載,參見unity-mono/external/buildscripts/build_runtime_android.sh, 搜一下ndk=就能找到,本文用到的是r10e,下載下來的ndk為android-ndk-r10e-linux-x86_64.bin。
  • apktools, 用來對apk進行解包簽名打包,2.0以上版本,否則打包是會報錯。

編譯mono

1)為了方便使用root進行編譯,Ubuntu下root默認不開啟,可以使用:

    sudo passwd root

    輸兩次密碼後

    su – 

    進行登錄

2)NDK安裝

    安裝7z 

apt-get install p7zip-full

    解壓

       7z x android-ndk-r10e-linux-x86.bin

    配置環境變量,配置方法有很多,可以修改/etc/profile或者~/.bashrc,這裡直接shell下添加臨時的環境變量,不添加後面編mono時會報找不到NDK

       export ANDROID_NDK_ROOT=/home/xubo/unity-dev/android-ndk-r10e

       export PATH=$ANDROID_NDK_ROOT:$PATH

3)檢查一下mono使用的NDK版本

vi打開mono-unity-4.6/external/buildscripts/build_runtime_android.sh可以找到

perl ${BUILDSCRIPTSDIR}/PrepareAndroidSDK.pl -ndk=r10e -env=envsetup.sh && source envsetup.sh

這裡可以確定當前的unity mono使用r10e來進行編譯的

4)安裝編譯必備的一些包

apt-get install autoconf automake bison build-essential gettext git libglib2.0 libtool perl

5)嘗試第一次編譯

    ./external/buildscripts/build_runtime_android.sh

    報錯:

        /usr/bin/env: perl -w: No such file or directory

    這裡unity-mono編譯的時候會去git 一個包android_krait_signal_handler,在external目錄下,就是這個包報錯,這個包出錯的問題很多,是個巨坑(坑二)。

    打開android_krait_signal_handler/build.pl,將第一行

        #!/usr/bin/env perl -w

    改為

        #!/usr/bin/perl -w

    將下面行

        PrepareAndroidSDK::GetAndroidSDK(undef, undef, "r9");

    改為實際用到的NDK

        PrepareAndroidSDK::GetAndroidSDK(undef, undef, "r10e");

    將buildscripts/PrepareAndroidSDK.pm替        換android_krait_signal_handler/PrepareAndroidSDK.pm

    打開jni/Application.mk將下兩行都刪掉

APP_PLATFORM := android-9  NDK_TOOLCHAIN_VERSION := clang3.3

 否則會報下面的錯誤

make: execvp: /home/xubo/unity-dev/android-ndk-r10e/toolchains/arm-linux-androideabi-4.8: Permission denied

6)嘗試第二次編譯

    configure不通過,打開config.log發現

./configure: line 4546: /home/xubo/unity-dev/android-ndk-r10e/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86/bin/arm-linux-androideabi-gcc: No such file or directory

檢查該目錄,發現文件是存在的,這裡是因為雖然NDK是64位的,但是交叉編譯工具鏈是32位的,安裝一下,而本文採用的編譯機是64位的,安裝一下64位下運行32位可執行文件的包

apt-get install lib32z1 lib32ncurses5 lib32bz2-1.0

7)嘗試第三次編譯,至此我們應該可以編譯成功了,但還沒涉及到加解密,注意編譯需要在mono的根目錄下進行。最終顯示如下則OK:

Build SUCCESS!  Android STATIC/SHARED libraries are found here: builds/embedruntimes/android

加密程序

加密過程可參考上面的鏈接,就是將Assembly-CSharp.dll視作普通的文件,隨便用什麼語言寫個加密的代碼,簡單的可以修改幾個位元組,做偏移啥的,生成一個新的Assembly-CSharp.dll,替換原來的,這樣一般的破解軟件就沒轍了。

MONO解密

上面只是試驗了一下mono的編譯,關於將解密的代碼添加至mono還沒有做。

打開mono-unity-5.3/mono/metadata/p_w_picpath.c,找到mono_p_w_picpath_open_from_data_with_name函數修改如下

MonoImage *  mono_p_w_picpath_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)  {          MonoCLIImageInfo *iinfo;          MonoImage *p_w_picpath;          char *datac;                    //添加如下代碼          if(name != NULL)          {              if(strstr(name,"Assembly-CSharp.dll")){                  //這裡寫下你的解密的代碼,入參data是從Assembly-CSharp.dll讀文件讀出來的                  //被加密的原始數據,通過你的解密代碼生成一段新的data              }          }                    if (!data || !data_len) {                  if (status)                          *status = MONO_IMAGE_IMAGE_INVALID;                  return NULL;          }          datac = data;          if (need_copy) {                  datac = g_try_malloc (data_len);                  if (!datac) {                          if (status)                                  *status = MONO_IMAGE_ERROR_ERRNO;                          return NULL;                  }                  memcpy (datac, data, data_len);          }

MONO正式編譯

正式編譯mono前,還有兩個地方要修改,不修改編譯出來的是debug版本,libmono.so有8M,

打開build_runtime_android.sh, 將下面標紅的-g給去掉,編譯release版本

CFLAGS="  -DANDROID -DPLATFORM_ANDROID -DLINUX -D__linux__   -DHAVE_USR_INCLUDE_MALLOC_H -DPAGE_SIZE=0x1000   -D_POSIX_PATH_MAX=256 -DS_IWRITE=S_IWUSR   -DHAVE_PTHREAD_MUTEX_TIMEDLOCK   -fpic -g -funwind-tables 

同樣build_runtime_android_x86.sh裏面也去掉

Unity3D 簽名

別忘記了,需要unity4.6.6+的版本,本文是在unity4.6.9下測試OK。

製作一個簽名,後面在用apktool重新封包時用得到,用這個簽名對遊戲進行build。

Apktool解包封包

1)(windows下操作)確定apktool目錄下有aapt.exe,apktool.bat,apktool.jar,確定版本是2.0+

2)將生成的包例如1.apk 複製到apktool/下

3)cmd命令行下,進入apktool目錄,執行apktool d 1.apk進行解包,會在apktool下生成與包名相同的文件夾1/

4) 將加密過的Assembly-CSharp.dll覆蓋1assetsbinDataManagedAssembly-CSharp.dll

5) 將編譯過的libmono.so,注意這裡選擇armv7a/,和x86/下的,分別覆蓋1libarmeabi-v7a和1libx86下的libmono.so

6) 封包命令行下執行apktool b -f 1,會在1/下生成dist文件,裡頭就是新封的包,改名為2.apk,並複製到apktool/下

7)簽名,隱去的是你要填的簽名文件名,和別名

    jarsigner -verbose -keystore ****.keystore -signedjar 2_s.apk 2.apk ****

8)2_s.apk就是你加密過的包,進行安裝測試

libmono.so加密

雨松還提到了libmono.so的加密,這裡先不涉及吧,strip動態庫,可能能起到相同的效果。

小結

這樣加密經過測試是OK的,可以防止一般的反編譯軟件進行破解了,對於高手可能還防不住,另外編譯mono有點心驚膽戰,android_krait_signal_handler這個工程是個坑,還是有點擔心編出來的libmono.so有咩有啥隱患,所以這樣弄需要在各種android機子上多測試。