(十二)Proguard 的使用与配置 —— 代码混淆

版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、反编译

我们进行 apk 开发的时候,有时候一些效果写不出来,没有思路,这时候会去查看其他应用的源代码,参考怎么实现的,在这里需要对打包好的 apk 进行反编译查看。

安卓常用的反编译工具

1.apktool:

下载地址:https://ibotpeaches.github.io/Apktool/
作用:主要资源文件的获取。如 AndroidManifest、res 等。
解包: java  -jar  apktool的名字  d(反编译)  要解包的apk  -o(输出)  文件名
	  java  -jar .\apktool_2.3.0.jar d .\ap.apk -o out
重打包: java  -jar  apktool的名字  b(打包)  要打包的文件夹名字
	    java -jar .\apktool_2.3.0.jar b .\out\

我们直接用压缩工具打开 apk 获取的资源文件是被处理过的,不可读的,需要用 apktool 进行解包才可以。可以查看布局、样式、默认启动的 Activity、Application 等。

2.dex2jar:

下载地址:https://sourceforge.net/projects/dex2jar/files/
作用:将 apk 中的 dex 反编译成 jar。
反编译: dex2jar的bat 要反编译的dex
		d2j-dex2jar.bat classes.dex 

先获取 apk 中的 dex,然后使用 dex2jar 把 dex 转换成 jar 包,通过其他工具查看 jar 包下的源码。

3.enjarify:

下载地址:https://github.com/google/enjarify
作用:将 apk 或 apk 中的 dex 反编译成 jar。
反编译: enjarify 要反编译的apk 或 dex
		enjarify classes.dex 

enjarify 的使用需要依赖与 python3。

4.jd-gui:

下载地址:http://jd.benow.ca/
作用:查看反编译后 jar 包的代码。

查看 jar 的代码也可以在 Eclipse 中安装插件进行查看;Android Studio 自带反编译,可以直接查看 jar 包代码。(个人比较喜欢在这两个环境下进行查看)

5.jadx:

作用:直接查看资源和代码

jadx 的功能十分强大,可以直接使用这个进行 apk 的反编译。

二、Proguard 混淆

当我们编写代码时候,出于安全考虑,不希望自己编写的代码被其他人反编译,或者说比较简单的就获取到我们的实现逻辑。这时候需要对代码进行混淆。

1.Proguard

Proguard 可以对 java 代码进行压缩、优化、混淆、预检,会使编译后的文件更小,不容易进行逆向工程,反编译获取到代码。Proguard 先对代码进行压缩,然后优化,接着混淆,最后进行预检。

**压缩:**检测和删除没有使用到的类、方法和字段。

**优化:**对字节码进行优化,删除没有用的指令。如删除不必要的字段存取,不必要的方法调用,只写字段,未使用的方法参数等等。无论如何,字节码会变得更小。

**混淆:**使用 a、b、c 等没有意义的名称,对类、方法、字段进行重命名。

**预检:**对处理后的代码进行检查校验。

2.Proguard 配置文件

可以在 Proguard 官网 进行查看指导手册。

在 Android Studio 中使用混淆是比较简单的, Android Studio 自身集成了 ProGuard,默认是不开启的,只需要在 app 模块下的 build.gradle 文件中修改 minifyEnabled false 为 true 即可。

这里写图片描述

下方 proguardFiles 就是混淆配置文件,可以在这里定义项目打包的混淆选项。

getDefaultProguardFile(‘proguard-android.txt’) 是系统配置的,这个具体路径可以打印出来然后进行查看。
这里写图片描述

如果说没有 proguard-android.txt 这个文件存在,或者有开启了混淆报错(还是没有这个文件),可以点击 Gradle 中的任务进行生成。

这里写图片描述

这里写图片描述

proguard-rules.pro 是我们自己进行添加混淆规则配置的文件。

这里写图片描述

proguard-rules.pro 默认情况下是空的,会使用系统的 getDefaultProguardFile(‘proguard-android.txt’) 这个配置文件进行混淆。

3.Proguard 配置

我们主要使用的是 keep 指令,keep 相关的可以分为两组,第二组是第一组加 “names”。

这里写图片描述

-keep 
	指定类和类成员(变量和方法)不被混淆。
-keepclassmembers 
	指定类成员不被混淆(就是-keep的缩小版,不管类名了)。
-keepclasseswithmembers
	指定类和类成员不被混淆,前提是指定的类成员存在(如果不存在,仍然会混淆)。

没有加 names 的是 From being removed or renamed,即保持不被移除和重命名;加的是 From being renamed,即保持不被重命名。两者的区别就是是否保持不被移除,前者如果指定了不被混淆和移除,那么即使这个类或类成员有无用的也会被保留下来;后者如果是无用的类或类成员,即使指定了不被混淆那么也会被移除

系统默认的配置文件:

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
# will be ignored by new version of the Android plugin for Gradle.

# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize steps (and performs some
# of these optimizations on its own).
# 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.

#关闭优化
-dontoptimize
#不知道什么意思?
#dont=不要
#use 使用
#mixed 混合
#case 情况 案件
#classnames 类名
#所以合起来就是 不要使用混合情况的类名 这里的混合指混合大小写
#不使用大小写混合,混淆后类名称为小写
-dontusemixedcaseclassnames

#不要跳过非公共库的类
#jar包也需要混淆
-dontskipnonpubliclibraryclasses

#输出详情
-verbose

#保留注属性:注解、内部类、(Signature、EnclosingMethod)泛性与反射
# Preserve some attributes that may be required for reflection.
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod

#不混淆类
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
-keep public class com.google.android.vending.licensing.ILicensingService

#不打印配置类中可能的错误或遗漏的注释
-dontnote com.android.vending.licensing.ILicensingService
-dontnote com.google.vending.licensing.ILicensingService
-dontnote com.google.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
#不混淆任何包含native方法的类的类名以及native方法名
-keepclasseswithmembernames class * {
    native <methods>;
}

# Keep setters in Views so that animations can still work.
-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 ** CREATOR;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

# Preserve annotated Javascript interface methods.
-keepclassmembers class * {
    @android.webkit.JavascriptInterface <methods>;
}

# The support libraries 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.
-dontnote android.support.**
#不对指定的类、包中的不完整的引用发出警告
-dontwarn android.support.**

# 保留 android.support.annotation.Keep 这个类
# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep

# 保留使用 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>(...);
}

三、测试与定位

混淆过的 apk 必须完整的重新测试一遍,有可能由于类名或者方法名的混淆,导致一些方法找不到(Activity 的 onCreate 等),从而异常。在使用反射的时候,由于被反射的类没有被使用到,Proguard 检测时候,会把它删除掉,导致找不到。

1.模拟异常

首先,在这边先模拟一个异常,然后进行异常的定位。

创建 Bug 类,里面有一个 bug 方法,除 0 异常,然后在 MainActivity 中引用。

Bug:

public class Bug {

    public static int bug(){
        int sum = 0;
        for(int i=0; i<10; i++){
            sum += i;
        }
        int i = 0;
        sum = sum / i;
        return sum;
    }
}

MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int sum = Bug.bug();

        TextView textView = findViewById(R.id.id);
        textView.setText(sum + "");

    }
}

开启混淆:

这里写图片描述

运行报错:
这里写图片描述

可以发现,这边的异常堆栈打印出来的是混淆过后的类名和方法名,不利于我们直接进行定位问题。

再认真查看一下,会发现混淆代码后面的错误行号显示的是 (Unknown Source),而不是行号。

这里写图片描述

2.混淆映射文件

开启混淆的时候会在 outputs 文件下生成一个 mapping.txt 文件,这个文件保存着混淆前后各个类、方法、属性的对应关系。

这里写图片描述

这里写图片描述

我们可以直接通过这个文件进行查看,参照打出来的异常堆栈,找到原先错误的类,方法。

这段比较简单,所以一下子就能定位到错误函数,如果函数层级较深,光靠自己比对来查找难度就比较大了。所以 sdk 中提供了一个工具在 tools/proguard/bin 中,可以直接帮我们还原原先的错误堆栈信息。

这里写图片描述

我们把logcat里面保存的错误堆栈复制到一个文件中:
这里写图片描述

然后执行 retrace 脚本(在 Windows 上为 retrace.bat;在 Mac/Linux 上为 retrace.sh

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

这里写图片描述

现在错误的日志堆栈 mapping 还原了。所以我们每次在发布版本之后都需要保留这个 mapping 文件。

3.开启行号

接下来我们解决另一个问题,行号显示不出来。

如果希望出现具体行数,我们需要在配置文件中加入抛出异常时保留代码行号,在异常分析中可以方便定位。

-keepattributes SourceFile,LineNumberTable

这个可以直接补充在我们的配置文件中。

这里写图片描述

再次运行,发现已经打出行号信息。
这里写图片描述

但是在后面把真正的类名也显示出来了,为了屏蔽这个显示,使用字符串"AAA"来替代真正的类,避免泄漏更多的信息。

-renamesourcefileattribute AAA

这里写图片描述

这时候打印出来的异常堆栈没有包含 Bug 类的信息了。
这里写图片描述

而且也是可以使用 mapping.txt 文件进行查找。

四、总结

虽然我们的代码经过了混淆,但是实际上,对于有耐心有条件的人来说,还是能够从混淆过后的代码中看到蛛丝马迹。混淆只是使反编译解读代码的难度增大。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值