Android 混淆配置基础总结

Android 开启混淆

何为混淆

proguard 的英文翻译是护卫兵的意思,在编程中翻译为混淆。通过混淆会增加代码被反编译的难度。在java中,Proguard是一个Java类文件压缩器、优化器、混淆器、预校验器。压缩环节会检测以及移除没有用到的类、字段、方法以及属性。优化环节会分析以及优化方法的字节码。混淆环节会用无意义的短变量去重命名类、变量、方法。这些步骤让代码更精简,更高效,也更难被逆向(破解)。

混淆后会默认在工程的目录app/build/outputs/mapping/release(debug)下生成一个mapping.txt文件,这里边是混淆的规则,通过这个文件可以反推回源代码。

android-studio中开启混淆

正常情况下,只有在release的情况下开启混淆,但是也可以在debug模式下开启混淆,通过android log直观的、快速的定位问题所在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
buildTypes {
debug {
debuggable true
//开启代码混淆(debug也可以开启混淆,只用将false改为true即可)
minifyEnabled false
//混淆文件的路径
proguardFile getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
release {
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
debuggable false
//开启代码混淆
minifyEnabled true
//移除无用的resource文件
shrinkResources true
signingConfig signingConfigs.release
proguardFile getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

混淆的配置

哪些代码不需要混淆:

  • JNI方法不混淆,因为native方法是要完整的包名类名方法名来定义的,不能修改,否则找不到JNI方法
  • AndroidMainfest中的类不混淆,四大组件和Application的子类和Framework层下所有的类默认不会进行混淆
  • 使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象
  • 反射用到的类名或者方法名不混淆
  • 使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则
  • 有用到WEBView的JS调用也需要保证写的接口方法不混淆
  • Parcelable的子类和Creator静态成员变量不混淆,否则会产生android.os.BadParcelableException异常
    1
    2
    3
    4
    -keep class * implements Android.os.Parcelable {
    # 保持Parcelable不被混淆
    public static final Android.os.Parcelable$Creator *;
    }
  • 使用enum类型时需要注意避免以下两个方法混淆,因为enum类的特殊性,以下两个方法会被反射调用
    1
    2
    3
    4
    -keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
    }

混淆配置规则

其实我们经常用到的有以下几种:

  • 1.保持某个包及子包下的类名及内容不被混淆
  • 2.保持某个包下的类名及内容不被混淆
  • 3.保持某个类的类名及类中的某些变量或者方法不被混淆

万变不离其宗,当我们熟悉混淆的基本规则后就可以游刃有余的对需要混淆的内容进行混淆。

基础配置

压缩(Shrinking)

默认开启,用以减小应用体积,移除未被使用的类和成员,并且会在优化动作执行之后再次执行(因为优化后可能会再次暴露一些未被使用的类和成员)

  • 如果想要关闭压缩,在配置文件proguard-rules.pro中添加如下配置

    1
    -dontshrink #关闭压缩
  • 优化(Optimization)
    默认开启,在字节码级别执行优化,让应用运行的更快。

    1
    2
    -dontoptimize  #关闭优化
    -optimizationpasses n #表示proguard对代码进行迭代优化的次数,Android一般为5
  • 混淆(Obfuscation)
    默认开启,增大反编译难度,类和类成员会被随机命名,除非用keep保护。
    如果想要关闭混淆,在配置文件proguard-rules.pro中添加如下配置

    1
    -dontobfuscate  #关闭混淆

保持类名不被混淆

将混淆的范围从大到小排列,有以下几种:

  • 保持某个包下的所有类名不被混淆
    注意:子包下的类名已经类中的变量及方法名都被混淆了

    1
    -keep class com.thc.test.*
  • 保持某个包下及 子包下的所有类名不被混淆
    类中的变量及方法名都被混淆了

    1
    -keep class com.thc.test.**
  • 保持某个类名不被混淆
    类的内容会被混淆

    1
    -keep class com.test.ClassA

保持类名及内容都不被混淆

将混淆的范围从大到小排列,有以下几种:

  • 可以保持该包及子包下的类名,又可以保持类里面的内容不被混淆

    1
    -keep class com.test.**{*;}
  • 既可以保持该包下的类名,又可以保持类里面的内容不被混淆

    1
    -keep class com.test.*{*;}
  • 保持某个类的 类名及内部的所有内容不会混淆

    1
    -keep class com.test.ClassA{*;}
  • 保持某个类的 类名及内部指定的内容不被混淆

    1
    2
    3
    4
    5
    -keep class com.test.ClassA{
    <init>; #匹配所有构造器
    <fields>;#匹配所有域
    <methods>;#匹配所有方法
    }
不常用的混淆
  • 上边保持住了ClassA中的所有构造、变量、方法不被混淆

    如果还想缩小范围呢,例如指定一些private 方法不被混淆,这时可以采用下边这样的方式

    1
    2
    3
    4
    5
    6
    7
    -keep class com.test.ClassA{
    public <methods>;#保持该类下所有的共有方法不被混淆
    public *;#保持该类下所有的共有内容不被混淆
    private <methods>;#保持该类下所有的私有方法不被混淆
    private *;#保持该类下所有的私有内容不被混淆
    public <init>(java.lang.String);#保持该类的String类型的构造方法
    }
  • 限制某个类中的特定方法不被混淆
    例如:限定某个类型参数的方法不被混淆(仅仅限定于构造)

    1
    2
    3
    4
    //保持只有一个参数,而且参数类型为String类型的构造不被混淆
    -keep class com.test.ClassA{
    public <init>(String);
    }
  • 保持某个类中的内部类中的内容不被混淆

    1
    2
    #保持ClassA的内部类MyClass中的内容不被混淆
    -keep class com.test.ClassA$MyClass{*;}

进阶配置

  • 保持指定规则的类不被混淆

    例如extends某些类的类都不被混淆,android的四大组件就不需要混淆,这时就可以采用下边的写法

    1
    2
    3
    4
    5
    6
    -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.view.View
  • 类名混淆,但是类中的指定方法不被混淆,需要使用 keepclassmembernames ,不是keep,keep保持的是类名不被混淆

    1
    2
    3
    4
    #保持ClassA类下test(String)方法不被混淆
    -keepclassmembernames class com.test.ClassA{
    public void test(java.lang.String);
    }
  • 保持类名和类成员不被混淆(有待验证)

    1
    -keepclasseswithmembernames class com.test.ClassA

混淆后问题的定位

混淆之后第一次运行必然会出现各种问题,此时如何解决呢?可以通过查看log猜测个大概,例如某些第三方的混淆规可能没有配置,因为此时的log是混淆后打印的log,不容易解读。,也可以在android-studio中的gradle中配置将debug模式下设置为混淆(这时log仍然不容易阅读)。最好的办法是采用retrace工具来查看报错的log。混淆之后会生成一个映射表,文件名为mapping.txt,通过retrace工具和mapping.txt文件将log转换为正常的字符串。

sdk中自带retrace工具,自己电脑的retrace工具的路径/home/shaoyance/Android/Sdk/tools/proguard/bin/retrace.sh

mapping.txt的路径/home/shaoyance/android-studio-project/Demo/app/build/outputs/mapping/release/mapping.txt

假如locat中的log如下

1
2
3
4
5
6
7
E: inlineAddCurrentApInfo mInfos.size()=74
E: [Thermal_Hw] temperature_valid, battery : temperature is invalid, invalid temperatrue is 26
E: [Thermal_Hw] temperature_filter: get invalid temperature, use old temp
E: [charger_ic] temp_new :29 temp_old :28
E: Report temperature: [charger_ic] temp :29 report_threshold:1
E: RSSI=-47;LINKSPEED=150;NOISE=9999;FREQUENCY=2462;
E: updateConfiguration freq=2462 BSSID=78:62:56:a3:26:78 RSSI=-47 "UAP_MOBILE"WPA_PSK

去掉前边的线程,包名等信息,然后复制到一个txt文件中, 假设该文件在桌面上,文件名为log.txt,执行命令

1
/home/shaoyance/Android/Sdk/tools/proguard/bin/retrace.sh  /home/shaoyance/android-studio-project/Demo/app/build/outputs/mapping/release/mapping.txt /home/shaoyance/桌面/log.txt

执行完命令后会在命令行窗口中输出对应的log信息,此时就可以很清楚看到报错信息。

retrace工具的使用可以参考博客代码混淆后日志的解读工具-retrace

使用apktool反编译apk


混淆完成后如果想要查看是否混淆成功,可以解压apk文件,通过dex2jar工具将apk包中的dex文件转换为jar包,然后通过jd-gui打开jar包查看混淆成功后的java代码。自己使用apktool没反编译成功,但是通过dex2jar反编译成功,这里将两种方法都作以记录。

apktool的安装

apktool安装教程也可以参考官网

  1. 首先下载 Linux wrapper script,右键另存为apktool (注意:右键保存的时候不需要加任何后缀)
  2. 下载jar包(可以从官网下载,也可以从参考博客中的网盘中下载)
  3. 把下载的Jar文件重命名为apktool.jar
  4. 执行命令将这两个文件复制到/usr/local/bin/目录中
  5. 执行下方命令修改这两个文件为可执行权限

    1
    sudo chmod +x apktool apktool.jar
  6. 执行下方命令反编译

    1
    apktool d oupin-debug.apk

安装dex2jarjd-gui

dex2jar主要用来反编译dex文件,反编译后生成一个jar包,里边的文件都是.class格式
jd-gui主要用来查看jar文件

1. 安装dex2jar

点此下载dex2jar

将下载的文件解压,进入解压的目录执行命令

1
chmod +x ./d2j_invoke.sh

将需要反编译的dex文件放入的解压的目录中,执行下方命令

1
sh d2j-dex2jar.sh classes.dex

2. 安装jd-gui

下载deb安装包(或者.tar.gz

进入到下载目录中,执行下方命令安装deb

1
sudo dpkg -i jd-gui_1.4.0-0_all.deb

安装成功后,就可以将jar包拖到jd-gui的窗口中查看反编译后的代码了。

混淆配置参考博客:

Android混淆——了解这些就够了(鸿洋微信公众号推荐)
Android混淆配置总结-持续更新

反编译参考博客:

图文详解 | Android APK反编译实战
Android反编译(逆向)教程
Ubuntu反编译apk教程
安装dex2jarjd-gui参考博客
ubuntu下Android反编译详细教程-apktool,dex2jar,jd-gui的使用