android 防止反編譯的若干方法

xiangzhihong8發表於2016-05-31

第一種方式:混淆策略

混淆策略是每個應用必須增加的一種防護策略,同時他不僅是為了防護,也是為了減小應用安裝包的大小,所以他是每個應用發版之前必須要新增的一項功能,現在混淆策略一般有兩種:

對程式碼的混淆

我們在反編譯apk之後,看到的程式碼類名,方法名,已經程式碼格式看起來不像正常的Android專案程式碼,那麼這時候就會增加閱讀難度,增加破解難度,像這樣的程式碼混淆:


我們一般現在的破解檢視Java層程式碼就是兩種方式:

一種是直接先解壓classes.dex檔案出來,使用dex2jar工具轉化成jar檔案,然後再用jd-gui工具進行檢視類結構

一種是使用apktool工具直接反編譯apk,得到smali原始碼,閱讀smali原始碼

不過這種程式碼混淆有時候在一定程度上能夠增加混淆策略,但是有時候也不是很安全,因為我們知道我們在破解的過程中一般是找程式的入口,那麼這些入口一般都是Application或者是MainActivity之類的,但是這些Android中的元件類是不能進行混淆的,所以我們還是有入口可尋,能夠找到入口程式碼,然後進行跟蹤。

2、對工程資源的混淆

我們上面說到了對程式碼的混淆能夠增加一定的程式碼閱讀難度,有時候我們為了防止資源的保護也是可以做混淆的,這個資源混淆原理這裡就不多解釋了,微信團隊已經將這個功能開源,不瞭解的同學可以轉戰github檢視:

https://github.com/shwenzhang/AndResGuard

主要是對資源的混淆

第二種方式:應用的簽名

我們知道Android中的每個應用都是有一個唯一的簽名。但是這個簽名在之前是可以被偽造,並實現為此打包的。為了防止應用被二次打包,或者是需要破解我們的apk的操作,在入口處新增簽名驗證,如果發現應用的簽名不正確就立即退出程式,我們可以在應用啟動的時候獲取應用的簽名值,然後和正規的簽名值作比對,如果不符合就直接退成程式即可。

 public static String getSign(){
      Context context= StockApplication.getInstance();
	   try {
		   PackageInfo packageInfo=context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
		   Signature[] signatures=packageInfo.signatures;
		   StringBuilder builder=new StringBuilder();
		   for (Signature signature:signatures){
			   builder.append(signature.toCharsString());
		   }
		   return builder.toString();
	   } catch (PackageManager.NameNotFoundException e) {
		   e.printStackTrace();
	   }
	   return "";
   }
我們可以在application啟動的時候,如果不是我們app的簽名,那麼app直接退出

 private void checkSign() {
      if (!Utils.isMyApp()){
         //直接退出
        }
    }
public static boolean isMyApp(){
     String signStr=getSign();
     return SIGN.equals(signStr);
   }

第三種方式:修改Naitve函式名

這個方法其實不太常用,因為他的安全措施不是很強大的,但是也是可以起到一定的障眼法策略,在說這個知識點的時候,我們先來了解一下so載入的流程:

在Android中,當程式在java層執行System.loadLibrary("jnitest");這行程式碼後,程式會去載入libjnitest.so檔案,與此同時,產生一個"Load"事件,這個事件觸發後,程式預設會在載入的.so檔案的函式列表中查詢JNI_OnLoad函式並執行,與"Load"事件相對,當載入的.so檔案被解除安裝時,“Unload”事件被觸發,此時,程式預設會去在載入的.so檔案的函式列表中查詢JNI_OnUnload函式並執行,然後解除安裝.so檔案。需要注意的是,JNI_OnLoad與JNI_OnUnload這兩個函式在.so元件中並不是強制要求的,使用者也可以不去實現,java程式碼一樣可以呼叫到C元件中的函式,之所以在C元件中去實現這兩個函式(特別是JNI_OnLoad函式),往往是做一個初始化工作或“善後”工作。可以這樣認為,將JNI_ONLoad看成是.so元件的初始化函式,當其第一次被裝載時被執行(window下的dll檔案也可類似的機制,在_DLL_Main()函式中,通過一個swith case語句來識別當前是載入還是解除安裝)。將JNI_OnUnload函式看成是解構函式,當其被解除安裝時被呼叫。由此看來,就不難明白為什麼很多jni C元件中會實現JNI_OnLoad這個函式了。 一般情況下,在C元件中的JNI_OnLoad函式用來實現給VM註冊介面,以方便VM可以快速的找到Java程式碼需要呼叫的C函式。(此外,JNI_OnLoad函式還有另外一個功能,那就是告訴VM此C元件使用那一個JNI版本,如果未實現JNI_OnLoad函式,則預設是JNI 1.1版本)。

應用層的Java類別通過VM而呼叫到native函式。一般是通過VM去尋找*.so裡的native函式。如果需要連續呼叫很多次,每次都需要尋找一遍,會多花許多時間。此時,C元件開發者可以將本地函式向VM進行註冊,以便能加快後續呼叫native函式的效率.可以這麼想象一下,假設VM內部一個native函式連結串列,初始時是空的,在未顯式註冊之前此native函式連結串列是空的,每次java呼叫native函式之前會首先在此連結串列中查詢需要查詢需要呼叫的native函式,如果找到就直接使用,如果未找到,得再通過載入的.so檔案中的函式列表中去查詢,且每次java呼叫native函式都是進行這樣的流程,因此,效率就自然會下降,為了克服這樣現象,我們可以通過在.so檔案載入初始化時,即JNI_OnLoad函式中,先行將native函式註冊到VM的native函式連結串列中去,這樣一來,後續每次java呼叫native函式時都會在VM中的native函式連結串列中找到對應的函式,從而加快速度

第四種方式:反除錯異常檢測

這種方式其實是為了應對現在很多破解者使用IDA進行動態方式除錯so檔案,從而獲取重要的資訊,如果還不知道如何使用IDA進行動態除錯so檔案的同學可以檢視這篇文章:Android中使用IDA進行動態除錯so檔案 ,看完這篇文章之後,我們可以知道IDA進行so動態除錯是基於程式的注入技術,然後使用Linux中的ptrace機制,進行除錯目標程式的,那麼ptrace機制有一個特點,就是如果一個程式被除錯了,在他程式的status檔案中有一個欄位TracerPid會記錄除錯者的程式id值,比如:


檢視檔案:/proc/[myPid]/status

在第六行,有一個TracerPid欄位,就是記錄了除錯者的程式id

那麼我們就可以這麼做來達到反除錯的功效了,就是我們可以輪訓的遍歷自己程式的status檔案,然後讀取TracerPid欄位值,如果發現他大於0,那麼就代表著自己的應用在被人除錯,所以就立馬退出程式。原理知道了,程式碼實現也很簡單,這裡用pthread建立一個執行緒,然後進行輪訓操作:

使用pthread_create建立一個執行緒,執行緒啟動之後執行thread_function函式


看看thread_funcation函式:


開始輪訓,讀取TracerPid欄位的值,發現大於0,就立馬退出程式,我們執行結果看看:






相關文章