安卓apk反編譯,安卓混淆文件使用

 2023-12-25 阅读 35 评论 0

摘要:簡單介紹 Android SDK 自帶了混淆工具 Proguard。它位于 SDK 根目錄 \tools\proguard 下面。如果開啟了混淆,Proguard 默認情況下會對所有代碼,包括第三方包都進行混淆(可能需要編寫混淆規則來保持不能被混淆的部分)。 安卓apk反編譯、作為 Andro

簡單介紹

Android SDK 自帶了混淆工具 Proguard。它位于 SDK 根目錄 \tools\proguard 下面。如果開啟了混淆,Proguard 默認情況下會對所有代碼,包括第三方包都進行混淆(可能需要編寫混淆規則來保持不能被混淆的部分)。

安卓apk反編譯、作為 Android 開發者,如果你不想開源你的應用,那么在應用發布前,就需要對代碼進行混淆處理,從而讓我們代碼即使被反編譯,也難以閱讀。

混淆的作用

壓縮(Shrinking)

默認開啟,用以減小應用體積,移除未被使用的類和成員,并且會在優化動作執行之后再次執行(因為優化后可能會再次暴露一些未被使用的類和成員)。

-dontshrink 關閉壓縮

優化(Optimization)

默認開啟,在字節碼級別執行優化,讓應用運行的更快。

-dontoptimize 關閉優化

-optimizationpasses n 表示 proguard 對代碼進行迭代優化的次數,Android 一般為5。

混淆(Obfuscation)

默認開啟,增大反編譯難度,類和類成員會被隨機命名(像 a、b()、c之類的),除非用 keep 保護。

-dontobfuscate:關閉混淆。

開啟混淆

在 app 的 gradle 文件中修改,在 android - buildTypes - release(debug)下:

android {...buildTypes {release {...//可優化字節碼zipAlignEnabled trueminifyEnabled trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}...
} 
說明

‘proguard-android.txt’ 是AndroidStudio默認自動導入的規則,這個文件位于Android SDK根目錄\tools\proguard\proguard-android.txt。這里面是一些比較常規的不能被混淆的代碼規則。

'proguard-rules.pro’是針對我們自己的項目需要特別定義混淆規則,它位于項目根目錄下面,里面的內容需要我們自己編寫。

zipAlignEnabled true 這個在打包時需要設置為true,能優化我們的java字節碼,提高運行效率。zipAlign 可以讓安裝包中的資源按4字節對齊,這樣可以減少應用在運行時的內存消耗。像Google Play 還強制要求開發者上傳的應用必須是經過 zipAlign 的,

簡單混淆規則

從上面說明知道,我們要編寫的混淆規則就是寫在 proguard-rules.pro 文件中,在 AS 左邊列表中很好找到,每個 module 都有自己的 proguard-rules.pro 文件,我們對應著改就可以。

下面說說簡單的混淆規則,好多文章都講的不是清楚:

保持類
-keep class cn.hadcn.test.*
-keep class cn.hadcn.test.**

一顆星表示只是保持該包下的類名,而子包下的類名還是會被混淆;兩顆星表示把本包和所含子包下的類名都保持;

保持類及其中方法及變量

用以上方法保持類后,你會發現類名雖然未混淆,但里面的具體方法和變量命名還是變了,這時如果既想保持類名,又想保持里面的內容不被混淆,我們就需要以下方法了:

-keep class com.silence.test.* {*;}
-keep class com.silence.test.** {*;}
保持特定類

在此基礎上,我們也可以使用 Java 的基本規則來保護特定類不被混淆,比如我們可以用 extend,implement 等這些 Java 規則。如下例子就避免所有繼承 Activity 的類被混淆

-keep public class * extends android.app.Activity
保持類及其中內部類

如果我們要保留一個類中的內部類不被混淆則需要用$符號,如下例子表示保持 TestFragment 內部類 MyClass 中的所有 public 內容不被混淆。

-keep class com.silence.TestFragment$MyClass{public*;}
保持類及其中特定內容

再者,如果一個類中你不希望保持全部內容不被混淆,而只是希望保護類下的特定內容(包含類名),就可以使用

<init>;//匹配所有構造器
<fields>;//匹配所有域
<methods>;//匹配所有方法方法

你還可以在或前面加上 private 、public、native 等來進一步指定不被混淆的內容,如

-keep class cn.hadcn.test.One{public<methods>;}

表示 One 類下的所有public方法都不會被混淆,當然你還可以加入參數,比如以下表示用JSONObject 作為入參的構造函數不會被混淆

-keep class cn.hadcn.test.One{public<init>(org.json.JSONObject);}

復雜混淆規則

作用范圍

應該注意到上面我們都保持類(使用 keep),實際還可以僅保持類成員,但是簡單使用的話,上面應該足夠了,其他的下面列舉一下:

作用范圍保持所指定類、成員所指定類、成員在壓縮階段沒有被刪除,才能被保持
類和類成員-keep-keepnames
僅類成員-keepclassmembers-keepclassmembernames
類和類成員(前提是成員都存在)-keepclasseswithmembers-keepclasseswithmembernames
默認包含的規則文件說明
#混淆時不生成大小寫混合的類名
-dontusemixedcaseclassnames
#不忽略非公共的類庫
-dontskipnonpubliclibraryclasses
#混淆過程中打印詳細信息
-verbose#關閉優化
-dontoptimize
#不預校驗
-dontpreverify# Annotation注釋不能混淆
-keepattributes *Annotation*
#對于NDK開發 本地的native方法不能被混淆
-keepclasseswithmembernames class * {native <methods>;
}
#保持View的子類里面的setget方法不被混淆(*代替任意字符)
-keepclassmembers public class * extends android.view.View {void set*(***);*** get*();
}#保持Activity子類里面的參數類型為View的方法不被混淆,如被XML里面應用的onClick方法
# 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);
}#保持枚舉類型values()、以及valueOf(java.lang.String)成員不被混淆
-keepclassmembers enum * {public static **[] values();public static ** valueOf(java.lang.String);
}#保持實現Parcelable接口的類里面的Creator成員不被混淆
-keepclassmembers class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator CREATOR;
}#保持R類靜態成員不被混淆
-keepclassmembers class **.R$* {public static <fields>;
}#不警告support包中不使用的引用
-dontwarn android.support.**
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
#保持使用了Keep注解的方法以及類不被混淆
-keepclasseswithmembers class * {@android.support.annotation.Keep <methods>;
}
#保持使用了Keep注解的成員域以及類不被混淆
-keepclasseswithmembers class * {@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {@android.support.annotation.Keep <init>(...);
}

上面默認的規則中指示了些需要保持不能別混淆的代碼,包括:

  1. 繼承至 Android 組件(Activity, Service…)的類。

  2. 自定義控件,繼承至 View 的類(被xml文件引用到的,名字已經固定了的)
    enum 枚舉

  3. 實現了 android.os.Parcelable 接口的

  4. Android R文件

  5. 數據庫驅動…

  6. Android support 包等

  7. Android 的注釋不能混淆

    -keepattributes *Annotation*
    
  8. 對于 NDK 開發 本地的 native 方法不能被混淆

    -keepclasseswithmembernames class * { native <methods>; }
    
其他不能混淆內容
  • 自定義控件不進行混淆

  • 枚舉類不被混淆

  • 反射類不進行混淆

  • 實體類不被混淆

  • JS調用的Java方法

  • 四大組件不進行混淆

  • JNI 中調用類不進行混淆

  • Layout布局使用的View構造函數、android:onClick

  • Parcelable的子類和 Creator靜態成員變量不混淆

  • 第三方開源庫或者引用其他第三方的SDK包不進行混淆

混淆模板

對于特定的項目還有很多不能被混淆的,需要我們自己寫規則來指示,將在下面來說明:

#壓縮級別0-7,Android一般為5(對代碼迭代優化的次數)
-optimizationpasses 5 #不使用大小寫混合類名
-dontusemixedcaseclassnames #混淆時記錄日志
-verbose#不警告org.greenrobot.greendao.database包及其子包里面未應用的應用
-dontwarn org.greenrobot.greendao.database.**
-dontwarn rx.**
-dontwarn org.codehaus.jackson.**
......
#保持jackson包以及其子包的類和類成員不被混淆
-keep class org.codehaus.jackson.** {*;}
#--------重要說明-------
#-keep class 類名 {*;}
#-keepclassmembers class 類名{*;}
#一個*表示保持了該包下的類名不被混淆;
# -keep class org.codehaus.jackson.*
#二個**表示保持該包以及它包含的所有子包下的類名不被混淆
# -keep class org.codehaus.jackson.** 
#------------------------
#保持類名、類里面的方法和變量不被混淆
-keep class org.codehaus.jackson.** {*;}
#不混淆類ClassTwoOne的類名以及類里面的public成員和方法
#public 可以換成其他java屬性如privatepublic static 、final等
#還可以使<init>表示構造方法、<methods>表示方法、<fields>表示成員,
#這些前面也可以加public等java屬性限定
-keep class com.dev.demo.two.ClassTwoOne {public *;
}
#不混淆類名,以及里面的構造函數
-keep class com.dev.demo.ClassOne {public <init>();
}
#不混淆類名,以及參數為int 的構造函數
-keep class com.dev.demo.two.ClassTwoTwo {public <init>(int);
}
#不混淆類的public修飾的方法,和private修飾的變量
-keepclassmembers class com.dev.demo.two.ClassTwoThree {public <methods>;private <fields>;
}
#不混淆內部類,需要用$修飾
#不混淆內部類ClassTwoTwoInner以及里面的全部成員
-keep class com.dev.demo.two.ClassTwoTwo$ClassTwoTwoInner{*;}
......

更多混淆模板說明,可以參考下面這篇博客:

https://www.jianshu.com/p/90feb5c50cce

實用例子

關閉 Log日志

一般我們開發的時候會自己寫一個 Log 封裝類,去解決正式版日志的開關,但是其實使用混淆文件也能起到正式版不輸出日志的用處,還能直接作用到代碼上,不用怕忘記刪 Log 了。

-assumenosideeffects class android.util.Log {public static boolean isLoggable(java.lang.String, int);public static int v(...);public static int i(...);public static int w(...);public static int d(...);public static int e(...);
}

反推崩潰日志

混淆后根據 Crash 崩潰日志反推代碼

成功打包后會在目錄 app\build\outputs\mapping\release 下生成幾個文件:

在這里插入圖片描述

dump.txt 混淆后類的內部結構說明;

mapping.txt 混淆前與混淆后名稱對應關系;

seeds.txt 經過了一系列keep語句的保持,沒有被混淆的類,成員的名稱列表文件。

usage.txt 經過壓縮后被刪除的沒有使用的代碼,方法…等的名稱的列表文件


retrace工具:

混淆反推工具為 etrace.sh( Mac 平臺)或者 retrace.bat(Windows平臺),該工具在sdk根目錄\tools\proguard\bin\retrace.sh ( Windows 平臺類似);

命令格式:

./retrace.sh [mapping.txt目錄] [崩潰日歷目錄]
retrace.bat [mapping.txt目錄] [崩潰日歷目錄])

使用說明

  • 第一步:保存 Crash 日志如下:截取日志保存為 txt 文件如:bug.txt。

    03-21 03:09:32.389: E/AndroidRuntime(3582): FATAL EXCEPTION: main
    03-21 03:09:32.389: E/AndroidRuntime(3582): Process: com.dev.demo, PID: 3582
    03-21 03:09:32.389: E/AndroidRuntime(3582): java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
    03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.two.b.b(Unknown Source)
    03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.a.a.printTest(Unknown Source)
    03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.MainActivity.i(Unknown Source)
    03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.MainActivity.a(Unknown Source)
    03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.MainActivity$1.onClick(Unknown Source)
    
  • 第二步:刪掉Crash日志中 **03-21 03:09:32.389: E/AndroidRuntime(3582)😗*部分,否則無法反推還原。刪除后如下:

    FATAL EXCEPTION: main
    Process: com.dev.demo, PID: 3582
    java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
    at com.dev.demo.two.b.b(Unknown Source)
    at com.dev.demo.a.a.printTest(Unknown Source)
    at com.dev.demo.MainActivity.i(Unknown Source)
    at com.dev.demo.MainActivity.a(Unknown Source)
    at com.dev.demo.MainActivity$1.onClick(Unknown Source)
    
  • 第三步:打開命令窗口輸入命令:如 😗*./retrace.sh mapping.txt bug.txt ** 如下:

    在這里插入圖片描述

    按確認后得到結果如下:

    FATAL EXCEPTION: main
    Process: com.dev.demo, PID: 3582
    java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
    at com.dev.demo.two.ClassTwoThree.void test()(Unknown Source)
    at com.dev.demo.one.ClassOneOne.void printTest()(Unknown Source)
    at com.dev.demo.MainActivity.void printWifi()(Unknown Source)
    at com.dev.demo.MainActivity.void access$000(com.dev.demo.MainActivity)(Unknown Source)
    at com.dev.demo.MainActivity$1.void onClick(android.view.View)(Unknown Source) ```
    
可視化工具

除了有 retrace.sh 工具外還有可視化工具,和 retrace.sh 同目錄下 proguardgui.sh,這是一塊 Progurad 的可視化工具(Windows平臺類似):

在這里插入圖片描述

有時為了Crash日志更容易定位可以在規則里面添加:

-keepattributes SourceFile, LineNumberTable

這樣Crash日志里面就能保留類名稱和行號了。

結語

以上就是我對混淆文件的一些總結,收集了一些資料整合了一下,具體參考文章如下:

  • Android混淆從入門到精通
  • Android常用混淆配置
  • Android 代碼混淆零基礎入門

希望本文對讀者有所幫助!

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/196841.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息