前言
大家都知道,目前大多數的Android 是用Java語言寫的,即使現在Google非常力薦kotlin,但是還有目前很多的專案還是使用Java編寫,畢竟一個語言的替換是需要時間。因此,Java程式碼容易被反編譯也是總所周知的,因此自己的防止被反編譯還是需要重視的。
主要從四個方面來介紹本文:
1.Proguard混淆
2.對抗反編譯工具
3.對抗安卓模擬器
4.對抗apk重打包
Proguard混淆
Proguard 基礎
Proguard 是一個混淆程式碼的開源專案,Proguard主要的作用是混淆,當然他還有著對位元組碼進行縮減體積、優化等功能,我主要關注的是混淆。
概念
下面兩張圖片分別是混淆了和沒有混淆的圖片(網上找的圖):
這樣可以看出來,假如混淆和沒有假如混淆的區別之大。使用混淆之後的類名就完全變了,自然假如混淆後,針對反編譯還是有效果。
基本語法:
-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, and
InnerClasses.
-renamesourcefileattribute {string} 設定原始檔中給定的字串常量
複製程式碼
實際程式碼:
-ignorewarnings # 忽略警告,避免打包時某些警告出現
-optimizationpasses 5 # 指定程式碼的壓縮級別
-dontusemixedcaseclassnames # 是否使用大小寫混合
-dontskipnonpubliclibraryclasses # 是否混淆第三方jar
-dontpreverify # 混淆時是否做預校驗
-verbose # 混淆時是否記錄日誌
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* # 混淆時所採用的演算法
-libraryjars libs/treecore.jar
-dontwarn android.support.v4.** #預設proguard 會檢查每一個引用是否正確,但是第三方庫裡面往往有些不會用到的類,沒有正確引用。如果不配置的話,系統就會報錯。
-dontwarn android.os.**
-keep class android.support.v4.** { *; } # 保持哪些類不被混淆
-keep class com.baidu.** { *; }
-keep class vi.com.gdi.bgl.android.**{*;}
-keep class android.os.**{*;}
-keep interface android.support.v4.app.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.support.v4.widget
-keep public class * extends com.sqlcrypt.database
-keep public class * extends com.sqlcrypt.database.sqlite
-keep public class * extends com.treecore.**
-keep public class * extends de.greenrobot.dao.**
-keepclasseswithmembernames class * { # 保持 native 方法不被混淆
native <methods>;
}
-keepclasseswithmembers class * { # 保持自定義控制元件類不被混淆
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * { # 保持自定義控制元件類不被混淆
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity { //保持類成員
public void *(android.view.View);
}
-keepclassmembers enum * { # 保持列舉 enum 類不被混淆
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
public static final android.os.Parcelable$Creator *;
}
-keep class MyClass; # 保持自己定義的類不被混淆
複製程式碼
**注意: ** 在Android中有一部分是不能夠被混淆的,混淆了之後就會出現異常:
- 四大元件由於在Mainfest中註冊了,所以不能被混淆
- jin呼叫的Java的介面方法
- 系統介面方法
- R檔案的混淆可能會導致引用錯誤(如果有地方使用反射機制,R檔案被混淆之後就會找不到資源)
防止反編譯
防止反編譯方法
1 . Proguard混淆不僅僅可以混淆程式碼,還能反編譯工具失效或者奔潰
2 . 使用現在國內的APK加固方法,就我所知目前你想要在360市場和騰訊的市場上釋出應用,都要必須要使用他們對應的市場的加固方式:360加固和樂固加固
我們常用的反編譯工具有哪些呢?
- apkTool
- baksmali
- dex2.jar
- JEB
我們通過上面的兩種方法可以讓這些反編譯工具反編譯出來不易閱讀甚至讓反編譯工具失效或者。
由於前面已經提到過混淆的方法就不在贅述,使用國內的APK加固方法就更加的簡單了,到指定的官網下載工具加固即可,文件描述很詳細。
防止模擬器
防止模擬器逆向分析
原因:一般被處於逆向分析狀態是在Android模擬器中執行的,所以我們只需要在程式碼中判斷我們當前的APK是否執行在模擬器中即可。
檢測是否是模擬器 一般有一下幾種方式:
- 檢測模擬器上的幾個特殊檔案
- 檢測模擬器上的特殊號碼
- 檢測裝置IDS是不是“000000000000000”
- 檢測是否還有感測器、藍芽
- 檢測手機上才有的硬體資訊
- 檢測手機的運營商
下面我用程式碼來演示一下:
檢查IDS
/**
* 檢查IDS
*
* @param context
* @return
*/
public static boolean chechDeviceIDS(Context context) {
@SuppressLint("ServiceCast")
TelephonyManager telecomManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
@SuppressLint("MissingPermission")
String deviceId = telecomManager.getDeviceId();
if (deviceId.equalsIgnoreCase(DEVICE_ID)) {
Log.e(TAG, "chechDeviceIDS==" + DEVICE_ID);
return true;
}
return false;
}
複製程式碼
檢查模擬器特有檔案
/**
* 檢查模擬器特有的檔案
*
* @param context
* @return
*/
public static boolean chechDeviceFile(Context context) {
for (int i = 0; i < DEVICE_FILE.length; i++) {
String file_name = DEVICE_FILE[i];
File qemu_file = new File(file_name);
if (qemu_file.exists()) {
Log.e(TAG, "chechDeviceFile==" + true);
return true;
}
}
return false;
}
複製程式碼
檢查模擬器特有號碼
/**
* 檢查特有電話號碼
*
* @param context
* @return
*/
public static boolean chechDevicePhone(Context context) {
@SuppressLint("ServiceCast")
TelephonyManager telecomManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
@SuppressLint("MissingPermission")
String phoneNumber = telecomManager.getLine1Number();
for (String phone : DEVICE_PHONE) {
if (phone.equalsIgnoreCase(phoneNumber)) {
Log.e(TAG, "chechDevicePhone==" + phoneNumber);
return true;
}
}
return false;
}
複製程式碼
檢查模擬器是否含有這些裝置
/**
* 檢查特是否含有裝置
*
* @param context
* @return
*/
public static boolean chechDeviceBuild(Context context) {
String board = Build.BOARD;
String bootloader = Build.BOOTLOADER;
String brand = Build.BRAND;
String device = Build.DEVICE;
String hardware = Build.HARDWARE;
String model = Build.MODEL;
String product = Build.PRODUCT;
if (board.equalsIgnoreCase("unknown") || bootloader.equalsIgnoreCase("unknown")
|| brand.equalsIgnoreCase("generic") || model.equalsIgnoreCase("sdk")
|| product.equalsIgnoreCase("goldfish")) {
Log.e(TAG, "chechDeviceBuild==" + "find emulatorBuild");
return true;
}
return false;
}
複製程式碼
執行結果:
我們可以看到列印結果是都顯示是模擬器,提醒一點獲取這些資訊的時候別忘記新增許可權:
通過上面我們可以判斷是否是模擬器,那麼這時我們就可以通過判斷來殺死APP或者殺死APP所在的程式。
二次打包
防止二次打包
- 什麼是二次打包?
- 通過反編譯工具得到smali程式碼,然後再由smali程式碼,重打包形成APK,最後重新簽名才能執行。
如果程式處於破解狀態,那麼我們的APK肯定是要執行在真機或者模擬器上,必須要重新簽名,那麼此時的簽名和原來的簽名必然不一致,我們就可以在程式的入口判斷我們的簽名,來以此判斷二次打包。
程式碼如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e(TAG, "getSignature=="+getSignature("demo.lt.com.repacking"));
}
private int getSignature(String packageName) {
PackageManager packageManager = this.getPackageManager();
PackageInfo info = null;
int sig = 0;
try {
info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] signatures = info.signatures;
sig = signatures[0].hashCode();
} catch (Exception e) {
sig = 0;
e.printStackTrace();
}
return sig;
}
}
複製程式碼
獲取到簽名hashCode是唯一值:
因此我們只需要再增加程式碼即可判斷二次打包:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e(TAG, "getSignature==" + getSignature("demo.lt.com.repacking"));
if (getSignature("demo.lt.com.repacking") != -567503403) {
Log.e(TAG, "被重新打包了");
}
}
private int getSignature(String packageName) {
PackageManager packageManager = this.getPackageManager();
PackageInfo info = null;
int sig = 0;
try {
info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] signatures = info.signatures;
sig = signatures[0].hashCode();
} catch (Exception e) {
sig = 0;
e.printStackTrace();
}
return sig;
}
}
複製程式碼
這裡只是列印了個Log,實際程式碼中可以結束程式或者殺死APP。
一句話總結
以上就是今天的Android 防護的知識點,並不是要求你有多麼精通Android防護,而是我們作為一個Android開發者必須要知道這些知識點,我相信對你肯定是有用的。
原創不易,如果覺得寫得好,掃碼關注一下點個贊,是我最大的動力。
關注我,一定會有意想不到的東西等你:Android、Java、大資料等視訊資源、書籍等著你來拿。 每天專注分享Android、JAVA乾貨
備註:程式圈LT