Android代码混淆 探索

声明

这篇文章更多的是做一个整理,内容来自于ProGuard官方文档以及各种博客等,相关文章的链接在参考目录里,感兴趣的可以去看看。

本人关于学习代码混淆的建议

了解基本的混淆概念和目的概念 -> 做一下简单的代码混淆实践 -> 详细了解混淆规则

目录:

  • 一:代码混淆是什么?
  • 二:为什么要进行代码混淆?
  • 三:代码混淆能保证代码的绝对安全吗?
  • 四:怎么进行代码混淆?
  • 五:什么是 ProGuard
  • 六:Proguard 的作用?
  • 七:Android Studio 中怎么使用 ProGuard 进行代码混淆
  • 八:为什么要了解混淆规则
  • 九:ProGuard 常用语法(包括 保留、压缩、优化、混淆)
  • 十:混淆注意事项
  • 十一:ProGuard 的输出文件说明
  • 十二:参考

分这么多的目录是为了 能够全面而且循序渐进得 将 ProGuard 讲清楚,同时适应于不同水平的开发者,因为很多刚入门的小白对一些比较基本的概念也是不清楚的,这些也是当初我刚接触 Proguard 时困惑我的点,所以写得比较多,你可以有选择性得看。

一、代码混淆是什么?

删除无用代码,将代码中的各种元素,如包名、类名、函数名、变量名等改成无意义的符号,使得反编译你apk的人无法根据名字猜测代码的用途,这是一种加密手段

二、为什么要进行代码混淆?

如果代码没经过混淆,发布出去后,别人只需要反编译即可查看你的源码,这是一种知识产权的保护手段

三、代码混淆能保证代码的绝对安全吗?

混淆的目的是为了加大反编译的成本,但是并不能彻底防止反编译

四、怎么进行代码混淆?

使用Android Studio创建项目时会在项目根目录下生成一个proguard-rules.pro文件,该文件便是指定项目混淆规则的文件,使用的时候只需要在里面加入相应的混淆规则即可。也就是说,你在这个文件里面指定 哪些代码需要混淆,哪些不需要

至于什么是混淆规则、混淆规则的语法、哪些需要或不需要混淆,在下面会详细讲到。

五、什么是ProGuard

ProGuard是一个开源的Java 代码混淆器。它能且只能混淆Android项目里面的java代码,对于Native代码,资源文件Drawable、Xml等无法进行混淆。

  • 官方对Proguard的解释是:

Proguard是一个集合了 文件压缩、优化、混淆和校验 等功能的工具。
它通过将类名、变量名、方法名重命名为无意义的名称实现混淆效果;
它检测并删除无用的类,变量,方法和属性;
它优化字节码并删除无用的指令;
最后它还校验处理后的代码。

进入app下的build.gradle,可以看到:

buildTypes {
    release {
        //buildConfigField "boolean", "LEO_DEBUG", "true" 在编译时定义新的属性及属性值到BuildConfig.java中
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

其中proguardFiles getDefaultProguardFile就是制定 混淆规则 的文件,分两部分:

(1)前一部分proguard-android.txt代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明(例如对枚举、注解、Activity等的过滤),帮我们省去了很多事,这个文件在SDK/tools/proguard/proguard-android.txt下;

(2)后一部分是我们项目里的自定义的混淆文件,目录在 app/proguard-rules.pro下,在这个文件里我们可以声明一些我们所需要的定制的混淆规则,主要在这里处理的有 对第三方库的混淆声明、对实体类的混淆声明等。

六、Proguard的作用?

压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。
优化(Optimize):对字节码进行优化,移除无用的指令。
混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。
预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。

七、Android Studio中怎么使用ProGuard进行代码混淆

很简单,只需要 修改build.grade 配置文件,将 buildTypes代码块中的 minifyEnabled false 改为 minifyEnabled true 即可。

注:接下来在以release模式下打包apk时便会自动运行ProGuard,此时即使你不添加自己的混淆规则,也会进行代码混淆。

可以看看google默认的混淆文件,在 \sdk\tools\proguard\proguard-android.txt

接下来介绍混淆规则

八、为什么要了解混淆规则

其实google已经给我们提供了很好的打包规则, 即如果我们将minifyEnabled set为true后,即使我们在proguard-rules.pro 里啥也不写, 我们打出来的release包也是混淆好的

但是!如果我们不添加混淆规则,一般程序在build的过程中会因为出现问题而中断。

如果加入一些自己的混淆规则 只需要在 proguard-rules.pro 中文件加入自己的混淆规则即可,

九、ProGuard常用语法(包括 保留、压缩、优化、混淆)

详见官方文档:https://www.guardsquare.com/en/proguard/manual/usage#classspecification

保留

libraryjars

使用该语句把你项目所有用到的jar包都声明进来

1.libraryjars class_path 应用的依赖包,如android-support-v4  
例如:  -libraryjars libs/universal-image-loader-1.9.0.jar

keep相关语法

表明要保留哪些 Java元素不进行混淆 (前3条指定 类或成员,后3条指定 类或成员 的名称)

1.keep [,modifier,...] class_specification 不混淆指定的类文件和类的成员 
例如:
(1).保留某个包下面的类以及子包 -keep public class com.droidyue.com.widget.**
(2).-keep class com.czy.**//不混淆所有com.czy包下的类,** 换成具体的类名则表示不混淆某个具体的类
(3).-keep class com.clock.**{*;}//不混淆所有com.clock包下的类和类中的所有成员变量,**可以换成具体类名,*可以换成具体的字段,可参照Serialzable的混淆 

2.keepclassmembers [,modifier,...] class_specification 不混淆指定类的成员,如果此类受到保护他们会被保护得更好
例如:
(1).保留所有类中使用otto的public方法:
-keepclassmembers class ** {
@com.squareup.otto.Subscribe public *;
@com.squareup.otto.Produce public *;
}
(2).保留Contants类的BOOK_NAME属性:
-keepclassmembers class com.example.admin.proguardsample.Constants {
 public static java.lang.String BOOK_NAME;
}

3.keepclasseswithmembers [,modifier,...] class_specification 不混淆指定的类和类的成员,但条件是所有指定的类和类成员是要存在。

4.keepnames class_specification 不混淆指定的类和类的成员的 名称(如果他们不会压缩步骤中删除)

5.keepclassmembernames class_specification 不混淆指定的类的 成员的名称 (如果他们不会压缩步骤中删除)

6.keepclasseswithmembernames class_specification 不混淆指定的类和类的成员 的名称,如果所有指定的类成员出席(在压缩步骤之后)

7.printseeds {filename} 列出类和类的成员-keep选项的清单,标准输出到给定的文件

dontwarn

dontwarn是一个和keep可以说是形影不离,尤其是处理引入的library时.
引入的library可能存在一些无法找到的引用和其他问题,在build时可能会发出警告,如果我们不进行处理,通常会导致build中止.因此为了保证build继续,我们需要使用dontwarn处理这些我们无法解决的library的警告.

8.dontwarn [class_filter] 不提示warnning  
例如:关闭Twitter sdk的警告  -dontwarn com.twitter.sdk.**

压缩

9.dontshrink 不压缩输入的类文件
10.printusage {filename}
11.whyareyoukeeping {class_specification}

优化

12.dontoptimize 不优化输入的类文件
13.assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用,即假设调用不产生任何影响,在proguard代码优化时会将该调用remove掉。如system.out.println和Log.v等等  
14.allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员,

混淆

15.dontobfuscate 不混淆输入的类文件
16.obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法的名称
17.overloadaggressively 混淆时应用侵入式重载
18.useuniqueclassmembernames 确定统一的混淆类的成员名称来增加混淆
19.flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中
20.repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中
21.dontusemixedcaseclassnames 混淆时不会产生形形色色的类名
22.keepattributes {attribute_name,…} 保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.
23.renamesourcefileattribute {string} 设置源文件中给定的字符串常量

通配符匹配规则

通配符 规则
匹配单个字符
* 匹配类名中的任何部分,但不包含额外的包名
** 匹配类名中的任何部分,并且可以包含额外的包名
% 匹配任何基础类型的类型名

详细通配符见官方文档
https://www.guardsquare.com/en/proguard/manual/usage#classspecification

十、混淆注意事项

1.哪些不能混淆

1.反射中使用的元素,如一些ORM框架的使用,需要保证类名、方法不变, 不然混淆后, 就反射不了

如果用到了反射需要加入 : 
-keepattributes Signature  
-keepattributes EnclosingMethod  

2.如果想让一些bean 对象不混淆, 例如com.czy.bean 包下面的全是 Json框架生成的bean对象, 那么只需加入:

-keep class czy.**{*;}//不混淆所有的com.czy.bean包下的类和这些类的所有成员变量 

3.枚举也不要混淆

4.四大组件不建议混淆,因为四大组件声明必须在manifest中注册,而混淆后类名会发生更改,此时混淆后的类名没有在manifest注册,这是不符合Android组件注册机制的。(AndroidMainfest中的类不混淆,四大组件和Application的子类和Framework层下所有的类默认不会进行混淆)

5.注解不能混淆
注解在Android平台中使用的越来越多,常用的有ButterKnife和Otto.很多场景下注解被用作在运行时反射确定一些元素的特征.

为了保证注解正常工作,我们不应该对注解进行混淆.Android工程默认的混淆配置已经包含了下面保留注解的配置

-keepattributes *Annotation*

6.JNI调用的java方法

7.Java的Native方法

8.JS调用Java的方法

9.WebView中JavaScript调用的方法方法不混淆

有用到WEBView的JS调用接口,需加入如下规则: 
-keepclassmembers class fqcn.of.javascript.interface.for.webview {  
   public *;  
}  
-keep class com.xxx.xxx.** { *; }//保持WEB接口不被混淆 此处xxx.xxx是自己接口的包名  

10.第三方库不建议混淆,使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则(关于第三方的库的, 一般都是看他们的官方文档)

这里推荐两个开源项目,里面收集了一些第三方库的混淆规则
android-proguard-snippets
android-proguard-cn

不难理解,混淆之后,类名会变成a,b,c这种,通过包名+类名自然就会找不到该类了,自然就会出现ClassNotFoundException异常。这里推荐一篇文章:http://www.itnose.net/detail/6043297.html

11.Parcelable的子类和Creator静态成员变量不混淆,否则会产生android.os.BadParcelableException异常

12.GSON的序列化与反序列化

使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象

13.继承了Serializable接口的类,在反序列化的时候, 需要正确的类名等, 在Android 中大多是实现 Parcelable来序列化的

继承了Serializable接口的类,需要加上:

//不混淆Serializable接口的子类中指定的某些成员变量和方法  
-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();  
}  

14.Layout文件引用到的自定义View

2.Log处理

我们都知道,使用Log的时候,需要用到TAG,然而TAG我们一般都会写成:

private static final String TAG = MainActivity.class.getSimpleName()

这时候MainActivity如果被混淆的话,log输出信息就会变成V/a:xxxxxxx,所以为了让log输出信息维持原状,可以将TAG处理成固定的字符串:

private static final String TAG = "MainActivity"

正好Android Studio里面的 Live Templates 能让你轻轻松松的声明TAG !
关于Log处理,推荐一篇文章:https://www.zybuluo.com/shark0017/note/163330

移除一些log代码:
移除Log类打印各个等级日志的代码,打正式包的时候可以做为禁log使用,这里可以作为禁止log打印的功能使用,另外的一种实现方案是通过BuildConfig.DEBUG的变量来控制

-assumenosideeffects class android.util.Log {  
public static *** v(...);  
public static *** i(...);  
public static *** d(...);  
public static *** w(...);  
public static *** e(...);  
}  

3.Crash信息处理

代码混淆的时候记得加上在混淆文件里面记得加上这句:
# keep住源文件以及行号
-keepattributes SourceFile,LineNumberTable

否则你将看到崩溃信息
这里推荐bugly的一篇文章: http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=26&extra=page%3D1

十一、ProGuard的输出文件说明

混淆后,会在/build/proguard/目录下输出下面的文件

dump.txt 描述apk文件中所有类文件间的内部结构。
mapping.txt 列出了原始的类,方法,和字段名与混淆后代码之间的映  射。
seeds.txt 列出了未被混淆的类和成员
usage.txt 列出了从apk中删除的代码 当我们需要处理crash log的时候,就可以通过mapping.txt的映射关系找到对应的类,方法,字段等。方法如下:

sdk\tools\proguard\bin 目录下有个retrace工具可以将混淆后的报错堆栈解码成正常的类名window下为retrace.bat,linux和mac为retrace.sh,

使用方法如下:

1.将crash log保存为yourfilename.txt
2.拿到版本发布时生成的mapping.txt
3.执行命令retrace.bat -verbose mapping.txt yourfilename.txt

所以我们每次打包版本都需要保存最新的mapping.txt文件。如果要使用到第三方的crash统计平台,比如bugly,还需要我们上传APP版本对应的mapping.txt.每次都要保存最新的mapping文件,那不就很麻烦?放心,gradle会帮到你,只需要在bulid.gradle加入下面的一句。每次我们编译的时候,都会自动帮你保存mapping文件到本地的。

android {
applicationVariants.all { variant ->
    variant.outputs.each { output ->
        if (variant.getBuildType().isMinifyEnabled()) {
            variant.assemble.doLast{
                    copy {
                        from variant.mappingFile
                        into "${projectDir}/mappings"
                        rename { String fileName ->
                            "mapping-${variant.name}.txt"
                        }
                    }
            }
        }
    }
    ......
}
}

实践记录

混淆实体类

实体类不能混淆,需要保留setget方法。对于boolean类型的get方法为isXXX,不能遗漏。在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。

-keep public class com.ljd.example.entity.** {
public void set*(***);
public *** get*();
public *** is*();
}

-keep class com.demo.login.bean.** { *; }
-keep class com.demo.main.bean.** { *; }

反编译三步走:

1.下载以下三个工具:

  • apktool
    • 作用:资源文件获取,可以提取出图片文件和布局文件进行使用查看
  • dex2jar
    • 作用:将apk反编译成Java源码(classes.dex转化成jar文件)
  • jd-gui
    • 作用:查看APK中classes.dex转化成出的jar文件,即源码文件

2.下载上述工具中的apktool,解压得到3个文件:aapt.exeapktool.batapktool.jar

将需要反编译的APK文件放到该目录下,打开命令行界面(运行-CMD) ,定位到apktool文件夹,输入以下命令:

apktool.bat d -f  test.apk  test    

apktool.bat   d  -f    [apk文件 ]   [输出文件夹])

此时test文件夹下即包含了所有资源文件

3.下载上述工具中的dex2jarjd-gui ,解压

将要反编译的APK后缀名改为.rar或则 .zip,并解压,得到其中的classes.dex文件(它就是java文件编译再通过dx工具打包而成的),将获取到的classes.dex放到之前解压出来的工具dex2jar-0.0.9.15 文件夹内,
在命令行下定位到dex2jar.bat所在目录,输入dex2jar.bat classes.dex

在改目录下会生成一个classes_dex2jar.jar的文件,然后打开工具jd-gui文件夹里的jd-gui.exe,之后用该工具打开之前生成的classes_dex2jar.jar文件,便可以看到源码了

4.下面图文解释,对应上面流程

进入工具`apktool`目录下,执行`apktool.bat d -f app-release.apk`,app-release为你的`apk`名称
此时会生成反编译`apk`后得到的资源文件`app-release`,你也可以在上面的命令中指定输出文件的名称,如`apktool.bat d -f app-release.apk test`,则会生成一个文件夹`test`来存放反编译得到的资源文件
按照上面步骤将解压后得到的`classes.dex`文件放到工具`dex2jar`文件夹后,`cmd`进入`dex2jar`并执行`dex2jar.bat classes.dex`
此时`dex2jar`工具下出现`classes_dex2jar.jar`
进入工具`jd-gui-0.3.5.windows`下运行`jd-gui.exe`并用`jd-gui.exe`打开上面生成的`classes_dex2jar.jar`文件即可看到反编译的项目

注:上述反编译资料来自http://blog.csdn.net/vipzjyno1/article/details/21039349/

参考

ProGuard手册
ProGuard手册 Version4.7

ProGuard代码混淆技术详解
Android分享:代码混淆那些事
http://blog.csdn.net/qq_35224673/article/details/52038093
http://blog.csdn.net/chen930724/article/details/49687067
http://blog.csdn.net/ljd2038/article/details/51308768 很详细


http://www.tuicool.com/articles/vyEnu2A 含例子
http://www.jianshu.com/p/be7ec1819d2f 含模板
http://www.jianshu.com/p/7391f0c554be 含内嵌类处理
常用第三方库的混淆
常用第三方库的混淆

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 什么是代码混淆 代码混淆就是将代码中的各种元素,如变量,方法,类和包的名字改写成无意义的名字,增加项目反编译后被读...
    蜗牛家族史阅读 4,972评论 1 4
  • 混淆(Proguard)用法 最近项目中遇到一些混淆相关的问题,由于之前对proguard了解不多,所以每次都是面...
    于晓飞93阅读 56,348评论 38 230
  • 01 昨天跟家里打电话,我妈又习惯性地跟我唠起了家常,什么谁家的孩子考上了一本,谁家的孩子来了录取通知书,谁家的孩...
    余少阅读 3,312评论 47 31
  • 姊齿秀次造假事件 一宗发生在日本的建筑舞弊案件。该案于2005年底被揭发,起因于一级建筑师姊齿秀次基于个人利益而长...
    ucudrrad阅读 191评论 0 0