近些年來移動 APP 數量呈現爆炸式的增加,黑產也從原來的PC端移到了移動端,伴隨而來的逆向攻擊手段也愈來愈高明。本篇章主要介紹應用加固的最基礎的四種方式:1.proguard 混淆 2.簽名比對驗證 3.ndk 編譯 .so 動態庫 4.代碼動態加載php
原文地址:APK反逆向之二:四種基本加固方式html
應該大多數開發者都不會關注應用會不逆向破解,並且如今有第三方廠商提供免費的加固方案,因此 apk 應用的安全性就所有依賴於第三方。可是若是第三方加固方案被破解那麼 apk 就陷於被動,因此咱們也能夠經過一些手段來加固應用自己邏輯,或者數據的加密。java
最多見的加固就是 proguard 混淆,經過混淆能夠加大邏輯分析的難度。對於數據的加密經過 ndk 開發動態庫基本上能夠防止大部分的破解者,逆向 native 跟逆向 java 不是一個級別的。android
下面介紹的四種加固方式成本比較低,開發者能夠很快開發完成,而且也不會影響應用的兼容性。git
ProGuard 是一個 SourceForge 上知名的開源項目。官網網址是:http://proguard.sourceforge.net/。github
java 編譯後的字節碼很容易被反編譯,基本上 java 編譯器均可以好比 eclipse、idea,還有用的比較多的 JD-GUI。均可以將 java 字節碼轉化爲 java 源碼。ProGuard的主要做用就是混淆。固然它還能對字節碼進行縮減體積、優化等,但那些對於咱們來講都算是次要的功能。算法
ProGuard is a free Java class file shrinker, optimizer, obfuscator, and preverifier. It detects and removes unused classes, fields, methods, and attributes. It optimizes bytecode and removes unused instructions. It renames the remaining classes, fields, and methods using short meaningless names. The resulting applications and libraries are smaller, faster, and a bit better hardened against reverse engineering.數組
Android 2.3 之後 SDK 中已經默認支持 ProGuard 混淆,具體配置文件在 sdk-root/tools/proguard/proguard-android.txt
。 緩存
SDK 內置 ProGuard 混淆文件:安全
# This is a configuration file for ProGuard. # http://proguard.sourceforge.net/index.html#manual/usage.html -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -verbose # Optimization is turned off by default. Dex does not like code run # through the ProGuard optimize and preverify steps (and performs some # of these optimizations on its own). -dontoptimize -dontpreverify # Note that if you want to enable optimization, you cannot just # include optimization flags in your own project configuration file; # instead you will need to point to the # "proguard-android-optimize.txt" file instead of this one from your # project.properties file. -keepattributes *Annotation* -keep public class com.google.vending.licensing.ILicensingService -keep public class com.android.vending.licensing.ILicensingService # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native -keepclasseswithmembernames class * { native <methods>; } # keep setters in Views so that animations can still work. # see http://proguard.sourceforge.net/manual/examples.html#beans -keepclassmembers public class * extends android.view.View { void set*(***); *** get*(); } # We want to keep methods in Activity that could be used in the XML attribute onClick -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keepclassmembers class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator CREATOR; } -keepclassmembers class **.R$* { public static <fields>; } # The support library contains references to newer platform versions. # Don't warn about those in case this app is linking against an older # platform version. We know about them, and they are safe. -dontwarn android.support.** # Understand the @Keep support annotation. -keep class android.support.annotation.Keep -keep @android.support.annotation.Keep class * {*;} -keepclasseswithmembers class * { @android.support.annotation.Keep <methods>; } -keepclasseswithmembers class * { @android.support.annotation.Keep <fields>; } -keepclasseswithmembers class * { @android.support.annotation.Keep <init>(...); }
在 Android Studio 中構建新項目時會在每一個 module 下生成 proguard-rules.pro
這裏能夠配置一些自定義的 ProGuard 規則。在 build.gradle 中配置是否啓動混淆配置, 通常狀況下編譯 release 版本會啓用混淆,debug 版本關閉:
buildTypes { release { signingConfig signingConfigs.tdSignConf minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug{ signingConfig signingConfigs.tdSignConf minifyEnabled false } }
在Android Studio 中啓用 ProGuard 混淆會在/app/build/outputs/mapping/release
下生成下面四個文件:
mapping.txt —> 表示混淆先後代碼的對照表
dump.txt —> 描述apk內全部class文件的內部結構
seeds.txt —> 列出了沒有被混淆的類和成員
usage.txt —> 列出了源代碼中被刪除在apk中不存在的代碼
mapping.txt 文件很是重要。若是你的代碼混淆後會產生bug的話,log提示中是混淆後的代碼,但願定位到源代碼的話就能夠根據mapping.txt反推。每次發佈都要保留它方便該版本出現問題時調出日誌進行排查,它能夠根據版本號或是發佈時間命名來保存或是放進代碼版本控制中。
若是是手動 ProGuard 混淆能夠在配置文件中添加 -printmapping
選項。
混淆後代碼變量、函數名、類名、都已經變成了隨機的字母,crash信息也就看不錯在什麼地方。因此就須要上面提到 mapping.txt 保存了混淆先後的代碼對照表。
例若有個錯誤日誌:
Caused by: java.lang.NullPointerException at cc.gnaixx.be.u(Unknown Source) at cc.gnaixx.at.v(Unknown Source) at cc.gnaixx.at.d(Unknown Source) at cc.gnaixx.av.onReceive(Unknown Source)
這個日誌中咱們根本沒法排查錯誤在哪一行,可是 ProGuard 提供了一個還原工具,工具在proguard/bin/retrace.sh
。
執行:
#crash.txt 爲錯誤棧文件 retrace.sh -verbose mapping.txt crash.txt
執行後的效果:
Caused by: java.lang.NullPointerException at cc.gnaixx.UtilTelephony.boolean is800MhzNetwork()(Unknown Source) at cc.gnaixx.ServiceDetectLte.void checkAndAlertUserIf800MhzConnected()(Unknown Source) at cc.gnaixx.ServiceDetectLte.void startLocalBroadcastReceiver()(Unknown Source) at cc.gnaixx.ServiceDetectLte$2.void onReceive(android.content.Context,android.content.Intent)(Unknown Source)
這裏須要注意一點若是你想查單個錯誤信息,必須在錯誤信息前加 at
, 否則沒法找出錯誤位置。
基本選項
-include {filename} 從給定的文件中讀取配置參數 -basedirectory {directoryname} 指定基礎目錄爲之後相對的檔案名稱 -injars {class_path} 指定要處理的應用程序jar,war,ear和目錄 -outjars {class_path} 指定處理完後要輸出的jar,war,ear和目錄的名稱 -libraryjars {classpath} 指定要處理的應用程序jar,war,ear和目錄所須要的程序庫文件 -dontskipnonpubliclibraryclasses 指定不去忽略非公共的庫類。 -dontskipnonpubliclibraryclassmembers 指定不去忽略包可見的庫類的成員。
保留選項
-keep {Modifier} {class_specification} 保護指定的類文件和類的成員 -keepclassmembers {modifier} {class_specification} 保護指定類的成員,若是此類受到保護他們會保護的更好 -keepclasseswithmembers {class_specification} 保護指定的類和類的成員,但條件是全部指定的類和類成員是要存在。 -keepnames {class_specification} 保護指定的類和類的成員的名稱(若是他們不會壓縮步驟中刪除) -keepclassmembernames {class_specification} 保護指定的類的成員的名稱(若是他們不會壓縮步驟中刪除) -keepclasseswithmembernames {class_specification} 保護指定的類和類的成員的名稱,若是全部指定的類成員出席(在壓縮步驟以後) -printseeds {filename} 列出類和類的成員-keep選項的清單,標準輸出到給定的文件
壓縮
-dontshrink 不壓縮輸入的類文件 -printusage {filename} -whyareyoukeeping {class_specification}
優化
-dontoptimize 不優化輸入的類文件 -assumenosideeffects {class_specification} 優化時假設指定的方法,沒有任何反作用 -allowaccessmodification 優化時容許訪問並修改有修飾符的類和類的成員
混淆
-dontobfuscate 不混淆輸入的類文件 -printmapping {filename} 混淆先後代碼的對照表 -applymapping {filename} 重用映射增長混淆 -obfuscationdictionary {filename} 使用給定文件中的關鍵字做爲要混淆方法的名稱 -overloadaggressively 混淆時應用侵入式重載 -useuniqueclassmembernames 肯定統一的混淆類的成員名稱來增長混淆 -flattenpackagehierarchy {package_name} 從新包裝全部重命名的包並放在給定的單一包中 -repackageclass {package_name} 從新包裝全部重命名的類文件中放在給定的單一包中 -dontusemixedcaseclassnames 混淆時不會產生形形色色的類名 -keepattributes {attribute_name,...} 保護給定的可選屬性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, InnerClasses. -renamesourcefileattribute {string} 設置源文件中給定的字符串常量
下面這些規則主要是本身在使用過程當中遇到過的問題
# 忽略指點錯誤,減小打印日誌,由於我是針對jar作混淆,打印一堆沒用日誌有點煩 -dontnote java.**, javax.**, org.** # 日誌打印類名從新定義爲"ProGuard"字符串 -renamesourcefileattribute ProGuard # 保留源文件名,保留行號,保留annotation -keepattributes SourceFile, LineNumberTable, *Annotation* # 保持枚舉 enum 類不被混淆 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # 保存內部類不被混淆 -keep class cc.gnaixx.TestClass$* {*;}
Android APK的發佈是須要簽名的。簽名機制在Android應用和框架中有着十分重要的做用。
例如,Android系統禁止更新安裝簽名不一致的APK;若是應用須要使用system權限,必須保證APK簽名與Framework簽名一致,等等。要破解一個APK,必然須要從新對APK進行簽名。而這個簽名,通常狀況沒法再與APK原先的簽名保持一致。(除非APK原做者的私鑰泄漏)簡單地說,簽名機制標明瞭APK的發行機構。
爲了說明APK簽名比對對軟件安全的有效性,咱們有必要了解一下Android APK的簽名機制。爲了更易於你們理解,咱們從Auto-Sign工具的一條批處理命令提及。
要簽名一個沒有簽名過的APK,可使用一個叫做auto-sign的工具。auto-sign工具實現簽名功能的命令主要是這一行命令:
java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk
這條命令的意義是:經過signapk.jar這個可執行jar包,以「testkey.x509.pem」這個公鑰文件和「testkey.pk8」這個私鑰文件對「update.apk」進行簽名,簽名後的文件保存爲「update_signed.apk」。
對於此處所使用的私鑰和公鑰的生成方式,這裏就不作進一步介紹了。這方面的資料你們能夠找到不少。咱們這裏要講的是signapk.jar到底作了什麼。
signapk.jar是Android源碼包中的一個簽名工具。因爲Android是個開源項目,因此,很高興地,咱們能夠直接找到signapk.jar的源碼!路徑爲/build/tools/signapk/SignApk.java。
對比一個沒有簽名的APK和一個簽名好的APK,咱們會發現,簽名好的APK包中多了一個叫作META-INF的文件夾。裏面有三個文件,分別名爲MANIFEST.MF、CERT.SF和CERT.RSA。signapk.jar就是生成了這幾個文件(其餘文件沒有任何改變。所以咱們能夠很容易去掉原有簽名信息)。
經過閱讀signapk源碼,咱們能夠理清簽名APK包的整個過程。
一、 生成MANIFEST.MF文件:
程序遍歷update.apk包中的全部文件(entry),對非文件夾非簽名文件的文件,逐個生成SHA1的數字簽名信息,再用Base64進行編碼。具體代碼見這個方法:
private static Manifest addDigestsToManifest(JarFile jar) //關鍵代碼以下: for (JarEntry entry: byName.values()) { String name = entry.getName(); if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) && !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) && (stripPattern == null ||!stripPattern.matcher(name).matches())) { InputStream data = jar.getInputStream(entry); while ((num = data.read(buffer)) > 0) { md.update(buffer, 0, num); } Attributes attr = null; if (input != null) attr = input.getAttributes(name); attr = attr != null ? new Attributes(attr) : new Attributes(); attr.putValue("SHA1-Digest", base64.encode(md.digest())); output.getEntries().put(name, attr); } }
以後將生成的簽名寫入MANIFEST.MF文件。關鍵代碼以下:
Manifest manifest = addDigestsToManifest(inputJar); je = new JarEntry(JarFile.MANIFEST_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); manifest.write(outputJar);
這裏簡單介紹下SHA1數字簽名。簡單地說,它就是一種安全哈希算法,相似於MD5算法。它把任意長度的輸入,經過散列算法變成固定長度的輸出(這裏咱們稱做「摘要信息」)。你不能僅經過這個摘要信息復原原來的信息。另外,它保證不一樣信息的摘要信息彼此不一樣。所以,若是你改變了apk包中的文件,那麼在apk安裝校驗時,改變後的文件摘要信息與MANIFEST.MF的檢驗信息不一樣,因而程序就不能成功安裝。
二、生成CERT.SF文件:
對前一步生成的Manifest,使用SHA1-RSA算法,用私鑰進行簽名。關鍵代碼以下:
Signature signature = Signature.getInstance("SHA1withRSA"); signature.initSign(privateKey); je = new JarEntry(CERT_SF_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); writeSignatureFile(manifest, new SignatureOutputStream(outputJar, signature));
RSA是一種非對稱加密算法。用私鑰經過RSA算法對摘要信息進行加密。在安裝時只能使用公鑰才能解密它。解密以後,將它與未加密的摘要信息進行對比,若是相符,則代表內容沒有被異常修改。
三、生成CERT.RSA文件:
生成MANIFEST.MF沒有使用密鑰信息,生成CERT.SF文件使用了私鑰文件。那麼咱們能夠很容易猜想到,CERT.RSA文件的生成確定和公鑰相關。
CERT.RSA文件中保存了公鑰、所採用的加密算法等信息。核心代碼以下:
je = new JarEntry(CERT_RSA_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); writeSignatureBlock(signature, publicKey, outputJar); //其中writeSignatureBlock的代碼以下: private static void writeSignatureBlock( Signature signature, X509Certificate publicKey, OutputStream out) throws IOException, GeneralSecurityException { SignerInfo signerInfo = new SignerInfo( new X500Name(publicKey.getIssuerX500Principal().getName()), publicKey.getSerialNumber(), AlgorithmId.get("SHA1"), AlgorithmId.get("RSA"), signature.sign()); PKCS7 pkcs7 = new PKCS7( new AlgorithmId[] { AlgorithmId.get("SHA1") }, new ContentInfo(ContentInfo.DATA_OID, null), new X509Certificate[] { publicKey }, new SignerInfo[] { signerInfo }); pkcs7.encodeSignedData(out); }
分析完APK包的簽名流程,咱們能夠清楚地意識到:
Android簽名機制實際上是對APK包完整性和發佈機構惟一性的一種校驗機制。
Android簽名機制不能阻止APK包被修改,但修改後的再簽名沒法與原先的簽名保持一致。(擁有私鑰的狀況除外)。
APK包加密的公鑰就打包在APK包內,且不一樣的私鑰對應不一樣的公鑰。換句話言之,不一樣的私鑰簽名的APK公鑰也必不相同。因此咱們能夠根據公鑰的對比,來判斷私鑰是否一致。
具體的實現方式在以前的一篇博客中有進行了介紹,實現方式其實很簡單:
逆向 ndk 跟逆向 java 代碼難度真不是在一個等級上的,ndk 開發除了安全性相對 java 開發高不少,並且在運行效率也高不少。
因此 NDK 開發相對於 JAVA 開發的優點:
1.代碼的保護。因爲apk的java層代碼很容易被反編譯,而C/C++庫反匯難度較大。
2.能夠方便地使用現存的開源庫。大部分現存的開源庫都是用C/C++代碼編寫的。
3.提升程序的執行效率。將要求高性能的應用邏輯使用C開發,從而提升應用程序的執行效率。
4.便於移植。用C/C++寫得庫能夠方便在其餘的嵌入式平臺上再次使用。
可是 NDK 的兼容性相對 JAVA 差不少,因此在進行 NDK 開發的時候兼容性是一個很大的問題。
以前寫了幾篇關於 NDK 開發的文章基本覆蓋了 NDK 開發的全部內容了。
這些教程都是基於 experimental 版本的 gradle 進行開發,可是這種版本的配置相對於普通版本的 gradle 有不少不一樣。而且建立一個新的 module 都須要更新 build.gradle 配置很是麻煩。
最近 Android Studio 更新到了2.2 版本,能夠支持兩種方式的 NDK開發:
1. ndk-build 開發
這種方式很是簡單隻須要在 build.gradle 添加 ndk 編譯配置:
externalNativeBuild { ndkBuild { path 'src/main/jni/Android.mk' } }
2. cmake 開發
先貼出官方文檔,之後再介紹。
官網文檔:
Add C and C++ Code to Your Project
這篇文章是在看雪上看到的對動態加載的原理介紹的比較清楚,大多數應用也是採用這種方式對 dex 文件進行加密處理。
熟悉Java技術的朋友,可能意識到,咱們須要使用類加載器靈活的加載執行的類。這在Java裏已經算是一項比較成熟的技術了,可是在Android中,咱們大多數人都還很是陌生。
Dalvik虛擬機如同其餘Java虛擬機同樣,在運行程序時首先須要將對應的類加載到內存中。而在Java標準的虛擬機中,類加載能夠從class文件中讀取,也能夠是其餘形式的二進制流。所以,咱們經常利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載執行的目的。
然而Dalvik虛擬機畢竟不算是標準的Java虛擬機,所以在類加載機制上,它們有相同的地方,也有不一樣之處。咱們必須區別對待。
例如,在使用標準Java虛擬機時,咱們常常自定義繼承自ClassLoader的類加載器。而後經過defineClass方法來從一個二進制流中加載Class。然而,這在Android裏是行不通的,你們就不必走彎路了。參看源碼咱們知道,Android中ClassLoader的defineClass方法具體是調用VMClassLoader的defineClass本地靜態方法。而這個本地方法除了拋出一個「UnsupportedOperationException」以外,什麼都沒作,甚至連返回值都爲空。
static void Dalvik_java_lang_VMClassLoader_defineClass(const u4* args, JValue* pResult) { Object* loader = (Object*) args[0]; StringObject* nameObj = (StringObject*) args[1]; const u1* data = (const u1*) args[2]; int offset = args[3]; int len = args[4]; Object* pd = (Object*) args[5]; char* name = NULL; name = dvmCreateCstrFromString(nameObj); LOGE("ERROR: defineClass(%p, %s, %p, %d, %d, %p)\n", loader, name, data, offset, len, pd); dvmThrowException("Ljava/lang/UnsupportedOperationException;", "can't load this type of class file"); free(name); RETURN_VOID(); }
那若是在Dalvik虛擬機裏,ClassLoader很差使,咱們如何實現動態加載類呢?Android爲咱們從ClassLoader派生出了兩個類:DexClassLoader和PathClassLoader。其中須要特別說明的是PathClassLoader中一段被註釋掉的代碼:
/**//* --this doesn't work in current version of Dalvik-- if (data != null) { System.out.println("--- Found class " + name + " in zip[" + i + "] '" + mZips[i].getName() + "'"); int dotIndex = name.lastIndexOf('.'); if (dotIndex != -1) { String packageName = name.substring(0, dotIndex); synchronized (this) { Package packageObj = getPackage(packageName); if (packageObj == null) { definePackage(packageName, null, null, null, null, null, null, null); } } } return defineClass(name, data, 0, data.length); } */
這從另外一方面佐證了defineClass函數在Dalvik虛擬機裏確實是被閹割了。而在這兩個繼承自ClassLoader的類加載器,本質上是重載了ClassLoader的findClass方法。在執行loadClass時,咱們能夠參看ClassLoader部分源碼:
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(className); if (clazz == null) { try { clazz = parent.loadClass(className, false); } catch (ClassNotFoundException e) { // Don't want to see this. } if (clazz == null) { clazz = findClass(className); } } return clazz; }
所以DexClassLoader和PathClassLoader都屬於符合雙親委派模型的類加載器(由於它們沒有重載loadClass方法)。也就是說,它們在加載一個類以前,回去檢查本身以及本身以上的類加載器是否已經加載了這個類。若是已經加載過了,就會直接將之返回,而不會重複加載。
DexClassLoader和PathClassLoader其實都是經過DexFile這個類來實現類加載的。這裏須要順便提一下的是,Dalvik虛擬機識別的是dex文件,而不是class文件。所以,咱們供類加載的文件也只能是dex文件,或者包含有dex文件的.apk或.jar文件。
也許有人想到,既然DexFile能夠直接加載類,那麼咱們爲何還要使用ClassLoader的子類呢?DexFile在加載類時,具體是調用成員方法loadClass或者loadClassBinaryName。其中loadClassBinaryName須要將包含包名的類名中的」.」轉換爲」/」。咱們看一下loadClass代碼就清楚了:
public Class loadClass(String name, ClassLoader loader) { String slashName = name.replace('.', '/'); return loadClassBinaryName(slashName, loader); }
在這段代碼前有一段註釋,截取關鍵一部分就是說:If you are not calling this from a class loader, this is most likely not going to do what you want. Use {@link Class#forName(String)} instead. 這就是咱們須要使用ClassLoader子類的緣由。至於它是如何驗證是不是在ClassLoader中調用此方法的,我沒有研究,你們若是有興趣能夠繼續深刻下去。
有一個細節,可能你們不容易注意到。PathClassLoader是經過構造函數new DexFile(path)來產生DexFile對象的;而DexClassLoader則是經過其靜態方法loadDex(path, outpath, 0)獲得DexFile對象。這二者的區別在於DexClassLoader須要提供一個可寫的outpath路徑,用來釋放.apk包或者.jar包中的dex文件。換個說法來講,就是PathClassLoader不能主動從zip包中釋放出dex,所以只支持直接操做dex格式文件,或者已經安裝的apk(由於已經安裝的apk在cache中存在緩存的dex文件)。而DexClassLoader能夠支持.apk、.jar和.dex文件,而且會在指定的outpath路徑釋放出dex文件。
另外,PathClassLoader在加載類時調用的是DexFile的loadClassBinaryName,而DexClassLoader調用的是loadClass。所以,在使用PathClassLoader時類全名須要用」/」替換」.」。
這一部分比較簡單,所以我就不贅言了。只是簡單的說下。
可能使用到的工具都比較常規:javac、dx、eclipse等。其中dx工具最好是指明--no-strict,由於class文件的路徑可能不匹配。
加載好類後,一般咱們能夠經過Java反射機制來使用這個類。可是這樣效率相對不高,並且老用反射代碼也比較複雜凌亂。更好的作法是定義一個interface,並將這個interface寫進容器端。待加載的類,繼承自這個interface,而且有一個參數爲空的構造函數,以使咱們可以經過Class的newInstance方法產生對象。而後將對象強制轉換爲interface對象,因而就能夠直接調用成員方法了。
最初設想將dex文件加密,而後經過JNI將解密代碼寫在Native層。解密以後直接傳上二進制流,再經過defineClass將類加載到內存中。
如今也能夠這樣作,可是因爲不能直接使用defineClass,而必須傳文件路徑給dalvik虛擬機內核,所以解密後的文件須要寫到磁盤上,增長了被破解的風險。
Dalvik虛擬機內核僅支持從dex文件加載類的方式是不靈活的,因爲沒有很是深刻的研究內核,我不能肯定是Dalvik虛擬機自己不支持仍是Android在移植時將其閹割了。不過相信Dalvik或者是Android開源項目都正在向可以支持raw數據定義類方向努力。
咱們能夠在文檔中看到Google說:Jar or APK file with "classes.dex". (May expand this to include "raw DEX" in the future.);在Android的Dalvik源碼中咱們也能看到RawDexFile的身影(不過沒有具體實現)。
在RawDexFile出來以前,咱們都只能使用這種存在必定風險的加密方式。須要注意釋放的dex文件路徑及權限管理,另外,在加載完畢類以後,除非出於其餘目的不然應該立刻刪除臨時的解密文件
目前的方式若是被識別後破解仍是相對比較簡單的,即便加密後的 dex 文件也須要在 load 前進行解密操做,而且須要把文件緩存在本地。因此只須要在合適的地方下個斷點就能夠把加密的 dex 拷貝下來。
可是咱們能夠經過其餘方式對 dex 進行加密,好比隱藏函數。這樣即便拿到了 dex 也沒法經過逆向工具查看源碼。
參考文章: