Androidapk如何加固防止被破解(防止逆向編譯)

奧特曼超人發表於2017-02-20

現在主要工具是接觸SDK,為了防止遊戲包被破解編譯,以及發現加密串,我來分享下以下幾點:
防破解技術主要有四種實現方式:
1.程式碼混淆(ProGuard)技術
2.簽名比對技術
3.NDK .so 動態庫技術
4.動態載入技術
5.第三方平臺加密以及檢測漏洞

這個在 Android 安全之如何反編譯與加密apk包 這篇文章中也提及到了相關的知識點。

  • 第一種: 程式碼混淆技術(ProGuard) 該技術主要是進行程式碼混淆,降低程式碼逆向編譯後的可讀性,但該技術無法防止加殼技術進行加殼(加入吸費、廣告、病毒等程式碼),而且只要是細心的人,依然可以對程式碼依然可以對程式碼進行逆向分析,所以該技術並沒有從根本解決破解問題,只是增加了破解難度。
  • 第二種: 簽名比對技術 該技術主要防止加殼技術進行加殼,但程式碼逆向分析風險依然存在。而且該技術並不能根本解決被加殼問題,如果破解者將簽名比對程式碼註釋掉,再編譯回來,該技術就被破解了。
  • 第三種: NDK .so動態庫技術,該技術實現是將重要核心程式碼全部放在C檔案中,利用NDK技術,將核心程式碼編譯成.so動態庫,再用JNI進行呼叫。該技術雖然能將核心程式碼保護起來,但被加殼風險依然存在。
  • 第四種: 動態載入技術,該技術在Java中是一個比較成熟的技術,而Android中該技術還沒有被大家充分利用起來。
  • 第五種: 第三方平臺使用

    主要講解第四種方法,該技術可以有效的防止逆向分析、被破解、被加殼等問題,動態載入技術分為以下幾步:

  • 將核心程式碼編譯成dex檔案的Jar包

  • 對jar包進行加密處理
  • 在程式主入口利用NDK進行解密
  • 再利用ClassLoader將jar包進行動態載入
  • 利用反射技術將ClassLoader 設定成系統的ClassLoader。

主要優點有:
       1.核心程式碼在被加密的jar中,所以破解者無法解壓出class檔案,如果加密祕鑰被破解者拿到,那將是另外一層面的安全問題了。
      2.該技術也可以有效防止加殼技術,程式碼是動態載入上來的,破解者的殼程式無法加入到已加密的jar包中,及時破解者注入殼程式入口,殼程式因為不在ClassLoader 的jar包中,所以也無法被執行起來,除非破解者替換ClassLoader的jar包,關掉NDK解密程式碼.但這種安裝到手機上,已經不在是我們的應用,使用者一定會將其解除安裝掉。

所以綜合起來比較,第四種動態載入技術是最安全的,但效率問題,本人並沒做嚴格測試,粗略實驗了一下,效率並沒有明顯降低。

//   1.Jar包加密加密解密檔案//  
public static boolean enOrDecryptFile(byte[] paramArrayOfByte,  
        String sourceFilePath, String destFilePath,int mode){  
    File sourceFile = new File(sourceFilePath);  
    File destFile = new File(destFilePath);  
    CipherOutputStream cout = null;  
    FileInputStream in  = null;  
    FileOutputStream out = null;  
    if (sourceFile.exists() && sourceFile.isFile()) {  
        if (!destFile.getParentFile().exists()) {  
            destFile.getParentFile().mkdirs();  
        }  
        try {  
            destFile.createNewFile();  
            in = new FileInputStream(sourceFile);  
            out = new FileOutputStream(destFile);  
            // 獲取金鑰//  
            init();  
            SecretKeySpec secretKeySpec = new SecretKeySpec(defPassword, "AES");  
            Cipher cipher;  
            cipher = Cipher.getInstance("AES");  
            cipher.init(mode, secretKeySpec);  
            cout = new CipherOutputStream(out, cipher);  
            byte[] cache = new byte[CACHE_SIZE];  
            int nRead = 0;  
            while ((nRead = in.read(cache)) != -1) {  
                cout.write(cache, 0, nRead);  
                cout.flush();  
            }  
        }catch (IOException e) {  
            e.printStackTrace();  
            return false;  
        } catch (NoSuchAlgorithmException e) {  
            e.printStackTrace();  
            return false ;  
        } catch (NoSuchPaddingException e) {  
            e.printStackTrace();  
            return false ;  
        }catch (InvalidKeyException e) {  
            e.printStackTrace();  
            return false;  
        }finally{  
                if(cout != null){  
                    try {  
                        cout.close();  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }  
                }  
                if(out != null){  
                    try {  
                        out.close();  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }  
                }  
                if(in != null){  
                    try {  
                        in.close();  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }  
                }  
        }  
        return true;  
    }  
    return false;  
}  

jar用SDKplatform-tools下的dx命令進行dex格式轉化

dx   --dex    --output=生成的目標檔案的地址(絕對路徑)     需要轉化的jar檔案(絕對路徑)
                       例如:dx --dex --output=H:classdex.jar     H:dujinyang-KARL.jar

然後再用加密工具將生成jar檔案進行加密處理

最後通過程式碼動態載入:

File file = new File("/data/data/" + base.getPackageName() + "/.cache/");  
        if (!file.exists()) {  
            file.mkdirs();  
        }  
        try {  
            Runtime.getRuntime().exec("chmod 755 " + file.getAbsolutePath()).waitFor();  
        } catch (InterruptedException e1) {  
            // TODO Auto-generated catch block  
            e1.printStackTrace();  
        } catch (IOException e1) {  
            // TODO Auto-generated catch block  
            e1.printStackTrace();  
        }  
        Util.copyJarFile(this);  
        Object currentActivityThread = RefInvoke.invokeStaticMethod(  
                "android.app.ActivityThread", "currentActivityThread",  
                new Class[] {}, new Object[] {});  
        String packageName = getPackageName();  
        HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(  
                "android.app.ActivityThread", currentActivityThread,  
                "mPackages");  
        WeakReference wr = (WeakReference) mPackages.get(packageName);  
        MyClassLoader dLoader = new MyClassLoader("/data/data/"  
                + base.getPackageName() + "/.cache/classdex.jar", "/data/data/"  
                + base.getPackageName() + "/.cache", "/data/data/"  
                + base.getPackageName() + "/.cache/", base.getClassLoader());  
        try {  
            Class<?>  class1 = dLoader.loadClass("com.example.test.TestActivity");  
            Log.i("b364","----------->class1: "+class1);  
        } catch (ClassNotFoundException e){  
            Log.i("b364","----------->class not found Exception!");  
            e.printStackTrace();  
        }  
        Log.i("b364","------>PackageInfo: "+wr.get());  
        // DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,  
        // libPath, (ClassLoader) RefInvoke.getFieldOjbect(  
        // "android.app.LoadedApk", wr.get(), "mClassLoader"));  
        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",  
                wr.get(), dLoader);  

處理完成,如果在Application中做特別處理也是可行的。之前就有人分析了愛加密的加密方式,不過這裡不做闡述,有興趣可以一起討論。

下篇文章中我們來講講如何 逆向apk的動態庫


相關文章