文档章节

Android进阶之ProGuard代码混淆

g
 grasp_D
发布于 2017/07/03 15:03
字数 2695
阅读 13
收藏 0

简介

Proguard工具通过移除无用的代码以及使用语义隐晦的名称来重命名类、字段和方法,从而达到压缩、优化和混淆代码的目的。最终您将获得一个较小的 .apk 文件,此文件更难于进行反向工程。由于 ProGuard 会使应用更难于进行反向工程,因此当应用使用对安全性要求极高的功能时(例如,当您向应用授予许可时),您必须使用此工具。

ProGuard 已集成到 Android 构建系统,所以您无需手动调用此工具。只有当您在发布模式下构建应用时,ProGuard 才会运行,因此当您在调试模式下构建应用时,就无需处理混淆后的代码。是否运行 ProGuard 完全由您决定,但我们强烈建议您运行该工具。

本文介绍如何启用和配置 ProGuard,以及如何使用 retrace 工具解码混淆后的堆栈跟踪信息。

除了基本的Proguard混淆外,还有一些其他的混淆方式和工具。例如:混淆资源

美团资源混淆

微信资源混淆

开启Proguard

  1. Ant、Eclipse构建

    <project_root>/project.properties 文件中设置 proguard.config 属性。该路径可以是绝对路径,也可以是项目根目录的相对路径。
    
    proguard.config=proguard.cfg
    
  2. AndroidStudio-Gradle构建

    android {
        buildTypes {
            release {
                minifyEnabled true
                proguardFile getDefaultProguardFile('proguard-android.txt')
            }
        }
    
        productFlavors {
            flavor1 {
            }
            flavor2 {
                proguardFile 'some-other-rules.txt'
            }
        }
    }
    

提示:

getDefaultProguardFile()可以返回这两个文件的绝对路径。
proguardFile 可以配置多个混淆文件

配置Proguard

在某些情况下,proguard.cfg或proguard-android.txt 文件中的默认配置足以满足您的需求。不过,在很多情况下,ProGuard 很难做出正确分析,因此可能会移除它认为无用而实际上您的应用却需要的代码。部分示例如下:

  • 一个只在 AndroidManifest.xml 文件中引用的类
  • 一个通过 JNI 调用的方法
  • 动态引用的字段和方法

默认的 proguard.cfg或proguard-android.txt 文件旨在涵盖一般的使用情形,但您可能会遇到异常情况,例如 ClassNotFoundException(此异常情况会在 ProGuard 删除您的应用调用的整个类时发生)。

您可以通过在 proguard.cfg或proguard-android.txt 文件中添加一个 -keep 行,来修复因 ProGuard 在删除代码而造成的错误。例如:

-keep public class <MyClass>

在使用 -keep 选项时,您既有许多选择也有不少需要注意的方面,因此我们强烈建议您阅读 ProGuard 手册,详细了解如何自定义您的配置文件。该手册中的“Keep 选项概述”和“示例”部分尤其有用;问题排查部分则概述了在 ProGuard 删除代码后您可能会遇到的其他常见问题。

下面给出一个常用的默认配置命令:

-include {fileame}  从给定的文件中读取配置参数
-libraryjars libs/xxxx.jar 指定库jar包
-keep public class * extends android.app.Activity 保留类不被删除
-keep class className$InnerName{ 保留内部类的属性和方法
    public <fields>;
    public <methods>;
}
-keepclassmembers class * implements java.io.Serializable { 保留类的成员
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
-dontshrink 不压缩输入的类文件
-dontoptimize 不优化输入的类文件
-keepattributes *Annotation* 保留Annotation
-dontwarn xxx.xxx.** 不检查引用

Proguard产生的文件

当混淆后的代码输出堆栈跟踪信息时,方法名称会被混淆,即便仍能进行调试,难度也会很大。幸运的是,ProGuard 在每次运行时都会输出以下文件:

  1. dump.txt

    描述 .apk 文件中所有类文件的内部结构

  2. mapping.txt

    列出原始与混淆后的类、方法和字段名称之间的对应关系。

    Windows 上的 retrace.bat 脚本以及 Linux 或 Mac OS X 上的 retrace.sh 脚本可以将混淆后的堆栈跟踪信息转换成可读文件,此文件位于 /tools/proguard/ 目录中。执行 retrace 工具的语法如下:

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

    例如:

    retrace.bat -verbose mapping.txt obfuscated_trace.txt
    

    建议发布时应保留mapping.txt文件。

  3. seeds.txt

    列出未混淆的类和成员

  4. usage.txt

    列出从 .apk 删除的代码

 

@Keep注解来防止混淆

 

写到这,你是不是发现了一个问题:非常的麻烦、一点都不灵活,而且通过-keep的方式防止混淆那种有共同特征的类、属性或方式非常有用,但是没有共同特征的呢?

这里介绍一种比较新颖、轻快的方法,通过@Keep注解来灵活的防止混淆,用起来非常的灵活、快捷、方便,怎样用呢?像普通的注解一样,如下:

//防止混淆类
@Keep
public class Person {}

//防止混淆变量
@Keep
public String name;

//防止混淆方法
@Keep
public int getAge(){}

但是当你加上上面的注解后,发现@Keep并没有起作用,该混淆的还是混淆了,这是为什么呢?

原因目前Gradle还不支持@Keep混淆,Google只是定义好了一个这种注解,并没有实现它,也就是说@Keep目前只是一个空壳。这里我们来手动开启它,让它支持防止混淆,在你的proguard.cfg或proguard-android.txt配置文件里面加入以下代码:

#手动启用support keep注解
-dontskipnonpubliclibraryclassmembers
-printconfiguration
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmembers class * {
    @android.support.annotation.Keep *;
}

Proguard相关语法

后面的文件名,类名,或者包名等可以使用占位符代替
“?”表示一个字符 可以匹配多个字符,但是如果是一个类,不会匹配其前面的包名
“*”可以匹配多个字符,会匹配前面的包名。

  • 输入输出选项
    • -include filename
      从给定的文件中读取配置参数
    • -injars class_path
      输入(即使用的) jar文件路径
    • -outjars class_path
      输出 jar 路径
    • -libraryjars class_path
      指定的jar将不被混淆
    • -skipnonpubliclibraryclasses
      跳过(不混淆) jars中的 非public classes
    • -dontskipnonpubliclibraryclasses
      不跳过(混淆) jars中的 非public classes 默认选项
    • -dontskipnonpubliclibraryclassmembers
      不跳过 jars中的非public classes的members
    • -keepdirectories [directory_filter]
      指定目录 keep 在 out jars中
      • 保持不变的选项(混淆不进行处理的内容)
    • -keep {Modifier} {class_specification}
      保护指定的类文件和类的成员
    • -keepclassmembers {modifier} {class_specification}
      保护指定类的成员,如果此类受到保护他们会保护的更好
    • -keepclasseswithmembers {class_specification}
      保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。
    • -keepnames {class_specification}
      保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)
    • -keepclassmembernames {class_specification}
      保护指定的类的成员的名称(如果他们不会压缩步骤中删除)
    • -keepclasseswithmembernames {class_specification}
      保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
    • -printseeds {filename}
      列出类和类的成员-keep选项的清单,标准输出到给定的文件
      • 压缩选项
    • -dontshrink
      不启用 shrink。shrink操作默认启用,主要的作用是将一些无效代码给移除,即没有被显示调用的代码。
    • -printusage [filename]
      打印被移除的代码,在标准输出
    • -whyareyoukeeping class_specification
      打印 在shrink过程中 为什么有些代码被 keep
      • 优化选项
    • -dontoptimize
      该选项表示 不启用。optimization,默认启用
      当不使用该选项时,下面的才有效
    • -optimizations optimization_filter
      根据optimization_filter指定要优化的文件
    • -optimizationpasses n
      优化数量 n
    • -assumenosideeffects class_specification
      优化时允许访问并修改类和类的成员的 访问修饰符,可能作用域会变大。
    • -mergeinterfacesaggressively
      合并接口,即使它们的实现类未实现合并后接口的所有方法。
      • 混淆选项
    • -dontobfuscate
      不混淆
    • -printmapping [filename]
      打印 映射旧名到新名
    • -applymapping filename
      打印相关
    • -obfuscationdictionary filename
      指定外部模糊字典
    • -classobfuscationdictionary filename
      指定class模糊字典
    • -packageobfuscationdictionary filename
      指定package模糊字典
    • -overloadaggressively
      过度加载,多个属性和方法使用相同的名字,只是参数和返回类型不同 可能各种异常
    • -useuniqueclassmembernames
      类和类成员都使用唯一的名字
    • -dontusemixedcaseclassnames
      不使用大小写混合类名
    • -keeppackagenames [package_filter]
      保持packagename 不混淆
    • -flattenpackagehierarchy [package_name]
      指定重新打包,所有包重命名,这个选项会进一步模糊包名 好东西
      将包里的类混淆成n个再重新打包到一个个的package中,注:混淆是有用,但是我用的时候安装会崩溃,不知道为什么?
    • -repackageclasses [package_name]
      将包里的类混淆成n个再重新打包到一个统一的package中 会覆盖flattenpackagehierarchy选项
    • -keepattributes [attribute_filter]
      混淆时可能被移除下面这些东西,如果想保留,需要用该选项。“Annotation、Exceptions, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable”
      • 预校验选项
    • -dontpreverify
      不预校验,默认选项
      • 通用选项
    • -verbose
      打印日志
    • -dontnote [class_filter]
      不打印某些错误
    • -dontwarn [class_filter]
      不打印警告信息
    • -ignorewarnings
      忽略警告,继续执行
    • -printconfiguration [filename]
      打印配置文件
    • -dump [filename]
      指定打印类结构

demo示例

##--- For:android默认 ---
-optimizationpasses 5  # 指定代码的压缩级别
-allowaccessmodification #优化时允许访问并修改有修饰符的类和类的成员
-dontusemixedcaseclassnames  # 是否使用大小写混合
-dontskipnonpubliclibraryclasses  # 是否混淆第三方jar
-dontpreverify  # 混淆时是否做预校验
-verbose    # 混淆时是否记录日志
-ignorewarnings  # 忽略警告,避免打包时某些警告出现
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*  # 混淆时所采用的算法

-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * { # 保持 native 方法不被混淆
    native <methods>;
}

-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

-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 *;
}

-keepclassmembers class **.R$* { #不混淆R文件
    public static <fields>;
}

-dontwarn android.support.**
##--- End android默认 ---

##--- For:不能被混淆的 ---
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference

##--- For:保持自定义控件类不被混淆 ---
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
##--- For:android-support-v4 ---
-dontwarn android.support.v4.**
-keep class android.support.v4.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class * extends android.support.v4.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v4.widget
-keep class * extends android.support.v4.app.** {*;}
-keep class * extends android.support.v4.view.** {*;}

##--- For:Serializable ---
-keep class * implements java.io.Serializable {*;}
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {*;}

##--- For:Gson ---
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.idea.fifaalarmclock.entity.***
-keep class com.google.gson.stream.** { *; }

##--- For:Remove 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(...);
}

##--- For:attributes(未启用) ---
#-keepattributes SourceFile,LineNumberTable # 保持反编译工具能看到代码的行数,以及release包安装后出现异常信息可以知道在哪行代码出现异常,建议不启用
-keepattributes *Annotation* #使用注解
-keepattributes Signature #过滤泛型  出现类型转换错误时,启用这个
#-keepattributes *Exceptions*,EnclosingMethod  #没试过,未知效果

本文转载自:http://hanhailong.com/2015/12/28/Android%E8%BF%9B%E9%98%B6%E4%B9%8BProGuard%E4%BB%A3%E7%A0%81%E6%B7%

共有 人打赏支持
上一篇: 函数式编程
下一篇: sqlite
g
粉丝 1
博文 126
码字总数 20266
作品 0
青岛
程序员
私信 提问
如何混淆Android项目代码(ProGuard)防止反编译

ProGuard简介 ProGuard是一个SourceForge上非常知名的开源项目。官网网址是:http://proguard.sourceforge.net/。 Java的字节码一般是非常容易反编译的。为了很好的保护Java源代码,我们往往...

鉴客
2011/12/30
9.4K
3
IntelliJ IDEA android项目的混淆不生效

我在工程的project.properties文件中分别增加了 proguard.config=proguard-project.txt(规则拷贝到相同路径下的proguard-project.txt文件中) 和proguard.config=E:/transfer/Android/android......

wenyong160
2013/08/29
2.7K
2
Android 代码混淆及反编译方法

一、AndroidSDK自带proguard混淆 在Android SDK中自带有proguard代码混淆器,但在默认的情况下该混淆器是没有打开的。该混淆器在SDK中的路径是android-sdk-windows\tools\proguard 启动andro...

鉴客
2011/11/13
9.5K
3
使用proguard混淆android代码

当前是有些工具比如apktool,dextojar等是可以对我们android安装包进行反编译,获得源码的。为了减少被别人破解,导致源码泄露,程序被别人盗取代码,等等。我们需要对代码进行混淆,android...

码农明明
2014/05/31
0
4
Android Apk包的签名出库、自动编译与混淆

1.签名与打正式包 右键工程,android tools export signed application 可以帮你生成签名(如果没有的话)以及打签名正式包 这时目录内会自动生成几个需要的文件,并且打一个不混淆的正式包 ...

hawkyoung
2013/11/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Akka实战:HTTP大文件断点上传、下载,秒传

访问:https://github.com/yangbajing/scala-applications/tree/master/file-upload 获取本文所述完整源码,包括Akka HTTP后端和HTML5实现的前端。 在很多应用里面都会有类似大文件上传的需求...

羊八井
14分钟前
0
0
node:event-loop & 宏任务 & 微任务

event-loop(事件轮询) 代码进入执行栈后会判断当前代码是同步任务还是异步任务,如果是同步任务则会将任务调到主线程同步执行,如果是异步任务则会将任务调到异步队列中。 主线程同步任务执...

小草先森
18分钟前
0
0
php-fpm配置文件详解

php-fpm配置文件详解 php-fpm配置文件路径: /usr/local/php-fpm/etc/php-fpm.conf ,还包含了一个配置文件路径下的所有以.conf结尾的配置文件(子配置文件)/usr/local/php-fpm/etc/php-fp...

李超小牛子
40分钟前
4
0
排序-堆排序

在说明堆排序的过程前得先了解什么是堆: 先看下图(来源于java数据结构和算法(第二版)): 堆是个完全二叉树,并且父节点总是大于(小于)它的孩子,因此根节点永远是最大或者最小的元素。...

FAT_mt
今天
3
0
matlab-自控原理 秩判据 能观性 已知线性定常系统的A和C矩阵

  matlab : R2018a 64bit     OS : Windows 10 x64 typesetting : Markdown    blog : my.oschina.net/zhichengjiu    gitee : gitee.com/zhichengjiu   code clearclc% x'=A*x......

志成就
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部