Android 代码混淆实战

什么是代码混淆?

Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。

代码混淆的作用

  1. 减小APK的体积
  2. 增加反编译后的阅读困难度(注:代码混淆并不能防止反编译)。

实现代码混淆

Eclipse用户

修改项目下project.properties文件,将下面代码前面的注释(#)去掉。
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

Android studio用户

修改Module下build.gradle,将minifyEnabled设置为true

用Demo说话

public class MainActivity extends Activity {

    private String mName;

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

    private void setName() {
        mName = "董永康";
    }

    private String getName(){
        return mName;
    }

}

这里以ES为例。打开proguard.project.txt。原文如下。

# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# 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 *;
#}

大概阅读下。注意下面这行说明:

# By default, the flags in this file are appended to flags specified in ${sdk.dir}/tools/proguard/proguard-android.txt

这里说明我们的proguard.project.txt文件会被追加到proguard-android.txt文件下。也就是说真正的混淆文件其实是proguard-android.txt,我们再配置的文件只是添加在proguard-android.txt文件后面。proguard-android.txt文件只是基础配置没多少内容,有兴趣的可以去看下。

现在我们先不对混淆做任何配置,只使用默认的混淆看看有什么结果。反编译apk查看代码步骤如下:

  1. 生成TestProguard.apk文件
  2. 修改后缀apk为zip,解压
  3. 复制解压后的classes.dex文件到dex2jar目录下(点我下载dex2jar和jd-gui)
  4. 进入dex2jar解压目录,使用dex2jar命令反编译
  5. 使用jd-gui查看源代码
dex2jar反编译命令为:d2j-dex2jar classes.dex

使用jd-gui打开后的代码如下:

public class MainActivity extends Activity{
  private String a;

  private void a()
  {
    this.a = "董永康";
  }

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903064);
    a();
  }
}

可以看到属性mName变成了a,getName()方法由于没有调用,自动被取出掉了。这就是代码混淆的好处。你想想,如果你反编译一套代码后看见变量名甚至类名都变成了a、b、c,你还有兴趣接着看下去吗?

代码混淆生成apk之后,项目下面会多出来一个proguard文件夹,下面分别解释proguard文件夹中四个文件的作用。

  • dump.txt : 描述了apk中所有类 文件中内部的结构体。( Describes the internal structure of all the class files in the .apk file )
  • mapping.txt : 列出了原始的类、方法和名称与混淆代码间的映射。( Lists the mapping between the original and obfuscated class, method, and field names. )
  • seeds.txt : 列出了没有混淆的类和方法。( Lists the classes and members that are not obfuscated )
  • usage.txt : 列出congapk中删除的代码。( Lists the code that was stripped from the .apk )
    着重注意下mapping.txt这个文件,下文会涉及到。

实现自己的代码混淆

上面只是基础的混淆,远远不能满足实际需求。下面添加一个我们自己的混淆规则。在proguard-project.txt中添加如下代码:

-keep public class com.example.testproguard.MainActivity{
    void setName();
}

乍一看不知道什么意思,对比下混淆后的代码

public class MainActivity extends Activity{

  private String a;

  private void setName()
  {
    this.a = "董永康";
  }

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903064);
    setName();
  }
}

对比可以发现,setName()方法没有被混淆。这就是-keep关键字的作用。保持某部分代码不会混淆。有读者可能会觉得本来就是混淆,这会怎么又不混淆了。默认的混淆规则是:除了声明不被混淆的代码,其余的都会被混淆!比如我们在WebView中和JS交互的时候,需要提供一个接口给JS调用,如果混淆了方法名,JS就会找不到这个方法。

上面只是个示例,更多参见下文:

-optimizationpasses 5                                                           # 指定代码的压缩级别
-dontusemixedcaseclassnames                                                     # 是否使用大小写混合
-dontskipnonpubliclibraryclasses                                                # 是否混淆第三方jar
-dontpreverify                                                                  # 混淆时是否做预校验
-verbose                                                                        # 混淆时是否记录日志
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*        # 混淆时所采用的算法

-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 com.android.vending.licensing.ILicensingService              # 保持哪些类不被混淆

-keepclasseswithmembernames class * {                                           # 保持 native 方法不被混淆
    native <methods>;
}

-keepclasseswithmembers class * {                                               # 保持自定义控件类不被混淆
    public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);     # 保持自定义控件类不被混淆
}

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

-keep class MyClass;                                                            # 保持自己定义的类不被混淆

查看混淆后的日志

代码几乎都被我们混淆了,我们怎么再去查看错误日志?对着a.b.c(),鬼知道这是什么方法!骚年,别急。Google当然会为我们考虑到这种情况。下面讲解怎么查看混淆后的代码。

  1. cmd进入sdk/tools/proguard/bin目录。
  2. 将混淆后的日志和上文提到的mapping文件放入此目录中。
  3. 执行命令:retrace.bat mapping.txt XXX.txt

执行命令前

before

执行命令后

after

可以看到我们发生错误的方法为initViews(),而不是a();感谢耐心阅读到最后!

google:http://developer.android.com/tools/help/proguard.html

你可能感兴趣的:(android,ProGuard,源代码,反编译,代码混淆)