在了解混淆之前,先来了解一下反编译。
反编译
Android程序打完包之后得到的是一个APK文件,这个文件是可以直接安装到任何Android手机上的,我们反编译其实也就是对这个APK文件进行反编译。Android的反编译主要又分为两个部分,一个是对代码的反编译,一个是对资源的反编译。
代码反编译
要想将APK文件中的代码反编译出来,我们需要用到以下两款工具:
dex2jar 这个工具用于将dex文件转换成jar文件 下载地址:http://sourceforge.net/projects/dex2jar/files/
jd-gui 这个工具用于将jar文件转换成java代码 下载地址:http://jd.benow.ca/
将这两个工具都下载好并解压,然后我们就开始对Demo程序进行反编译。解压dex2jar压缩包后,你会发现有很多个文件,如下图所示:
Paste_Image.png其中我们要用到的是d2j-dex2jar.bat这个文件,如果是linux或mac系统的话就要用d2j-dex2jar.sh这个文件。
接下来可以将要反编译的apk进行解压,先将后缀的“.apk”改为“.zip”,再进行解压,可以看到里面有一个文件classes.dex文件(这里还有一个classes2.dex是因为源apk方法数超过了65k,实行分包了),如下图:
Paste_Image.png这个classes.dex文件就是存放所有java代码的地方了,我们将它拷贝到dex2jar解压后的目录下,并在cmd中也进入到同样的目录,然后执行:
d2j-dex2jar classes.dex
结果如下:
执行成功后,在目录下会多一个文件classes-dex2jar.jar
Paste_Image.png接下来再借助jd-gui.exe进行查看这个jar文件,如下图
Paste_Image.png至此,代码反编译的工作就完成了。
资源反编译
原先解压出的apk里面,在res文件夹里面,其实已经有了大部分的资源了,但也仅仅是图片资源可以直接查看到,其余的xml打开都是被编译过的,无法直接读取到铭文,所以还需进行资源反编译。
要想将APK文件中的资源反编译出来,又要用到另外一个工具了:
apktool 这个工具用于最大幅度地还原APK文件中的9-patch图片、布局、字符串等等一系列的资源。 下载地址:http://ibotpeaches.github.io/Apktool/install/
这里只需要apktool.bat和apktool.jar这两个文件,将这两个文件放到同一个文件夹即可,如下图:
Paste_Image.png同样通过cmd执行语句
apktool d demo.apk
执行结果如下图所示,表示反编译资源已经成功
混淆
混淆的好处
1、减少apk文件大小,我尝试着把微博的apk资源进行混淆,apk文件由36.5M减少到35.6M;
2、增加反编译和二次打包的难度,混淆之后的apk不能用apktool之类的工具直接反编译。当然只是增加了难度,毕竟没有绝对安全;
3、减少运行时内存占用,在运行时各个res的key都是以String形式加载到内存中的,混淆之后key变短了很多内存占用肯定会变少。
混淆的原理
简单来说就是把方法,字段,包和类这些java 元素的名称改成无意义的名称,这样代码结构没有变化,还可以运行,但是想弄懂代码的架构却很难。 proguard 就是这样的混淆工具,它可以分析一组class 的结构,根据用户的配置,然后把这些class 文件的可以混淆java 元素名混淆掉。
常用语法
关键字
关键字 | 描述 |
---|---|
keep | 保留类和类中的成员,防止它们被混淆或移除。 |
keepnames | 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除。 |
keepclassmembers | 只保留类中的成员,防止它们被混淆或移除。 |
keepclassmembernames | 只保留类中的成员,防止它们被混淆,但当成员没有被引用时会被移除。 |
keepclasseswithmembers | 保留类和类中的成员,防止它们被混淆或移除,前提是指名的类中的成员必须存在,如果不存在则还是会混淆。 |
keepclasseswithmembernames | 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除,前提是指名的类中的成员必须存在,如果不存在则还是会混淆。 |
通配符
通配符 | 描述 |
---|---|
<field> | 匹配类中的所有字段 |
<method> | 匹配类中的所有方法 |
<init> | 匹配类中的所有构造函数 |
* | 匹配任意长度字符,但不含包名分隔符(.)。比如说我们的完整类名是com.example.test.MyActivity,使用com.*,或者com.exmaple.*都是无法匹配的,因为*无法匹配包名中的分隔符,正确的匹配方式是com.exmaple.*.*,或者com.exmaple.test.*,这些都是可以的。但如果你不写任何其它内容,只有一个*,那就表示匹配所有的东西。 |
** | 匹配任意长度字符,并且包含包名分隔符(.)。比如proguard-android.txt中使用的-dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包。 |
*** | 匹配任意参数类型。比如void set*(***)就能匹配任意传入的参数类型,*** get*()就能匹配任意返回值的类型。 |
… | 匹配任意长度的任意类型参数。比如void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)这些方法。 |
其它常用语法
-libraryjars class_path 应用的依赖包,如android-support-v4
-assumenosideeffects class_specification 假设调用不产生任何影响,在proguard代码优化时会将该调用remove掉。如system.out.println和Log.v等等
-dontwarn [class_filter] 不提示warnning
配置指令
代码压缩配置 shrinker配置
-dontshrink 声明不压缩文件.默认情况下,除了-keep相关指定的类,其它所有的**没有被引用到的类**都会被删除,每次优化(optimizate)操作之后也会执行一次压缩操作,因为每次优化操作可能删除一部分**不在需要的类**.
-printusage{filename} 将被删除的元素输出到文件,或者打印到保准输出流
whyareyoukeeping {class_specification} 打印为什么吗一个类或类的成员被保护,这对检查一个输出文件中的类的结果有帮助.
代码优化配置 optimizate配置
-dontoptimize 声明不优化的文件.默认情况下,优化选项是开启的,并且所有的优化都是在字节码层进行的.
-optimizations 更加细粒度的声明优化开启或者关闭.
**-optimizationpasses n **指定执行几次优化,默认情况只执行一次优化,执行多次优化可以提高优化的效果.但是如果执行一次优化之后没有效果,就会停止优化,剩下的设置次数不在执行.
其它配置
-dontusemixedcaseclassnames 表示混淆时不使用大小写混合类名。
-dontskipnonpubliclibraryclasses表示不跳过library中的非public的类。
-verbose表示打印混淆的详细信息。
-dontpreverify表示不进行预校验。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。
-keepattributes *Annotation*表示对注解中的参数进行保留。
举例说明
1.不混淆某个类的构造函数
例如不混淆Test类的构造函数
-keepcalssmembers clsss com.freezing.example.Text{
public <init>(int,int)
}
2.不混淆某个包下所有的类或者指定的类
例如不混淆package com.freezing.example下的所有的类/接口
-keep class com.freezing.example.**{*;}
例如不混淆com.freezing.example.Text类
-keep class com.freezing.example.Text{*;}
如果希望不混淆某个接口,则把上述命令的class替换为interface即可.
3.不混淆某个类的特定的函数
例如不混淆com.freezing.example.Test类中的setTestString函数
-keepclassmembers class com.freezing.example.Test{
public void setTestString(java.lang.String);
}
4.不混淆某个类的子类,某个接口的实现
例如不混淆com.freezing.example.Test的子类
-keep public class * extend com.freezing.example.Test
例如不混淆com.freezing.example.TestInterface的实现
-keep class * implements com.freezing.example.TestInterface{
public static final com.freezing.example.TestInterface$Creator *;
}
5.注意第三方依赖包
例如添加android-support-v4.jar依赖包
-libraryjars libs/android-support-v4.jar
-dontwarn android.support.v4.**{*;}
-keep class android.support.v4.**{*;}
-keep interface android.support.v4.**{*;}
注意添加dontwarn,因为默认情况下proguard会检查每一个引用是否正确,但是第三方库里往往有些不会用到的类,没有正确引用,所有如果不配置的话,系统会报错.
使用混淆文件
在项目的module.gradle里面有一段配置混淆的代码,如下:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
其中minifyEnabled 设置为true则打包后的apk就会是混淆过的,并且只有正式版的apk会混淆,debug版的apk是不会被混淆的。
最后介绍一个通用的混淆配置,有需要可以直接拿来用
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
#-------------------------------------------定制化区域----------------------------------------------
#---------------------------------1.实体类---------------------------------
#-------------------------------------------------------------------------
#---------------------------------2.第三方包-------------------------------
#-------------------------------------------------------------------------
#---------------------------------3.与js互相调用的类------------------------
#-------------------------------------------------------------------------
#---------------------------------4.反射相关的类和方法-----------------------
#----------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------------
#-------------------------------------------基本不用动区域--------------------------------------------
#---------------------------------基本指令区----------------------------------
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-verbose
-printmapping proguardMapping.txt
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
#----------------------------------------------------------------------------
#---------------------------------默认保留区---------------------------------
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-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();
}
-keep class **.R$* {
*;
}
-keepclassmembers class * {
void *(**On*Event);
}
#----------------------------------------------------------------------------
#---------------------------------webview------------------------------------
-keepclassmembers class fqcn.of.javascript.interface.for.Webview {
public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, jav.lang.String);
}
#----------------------------------------------------------------------------
#------------------------------------------------
网友评论