查看原文
其他

Android 代码混淆实战

一口仨馍 鸿洋 2019-04-05


本文由一口仨馍投稿。

一口仨馍的博客地址:

http://blog.csdn.net/qq_17250009/




1什么是代码混淆   

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


2

代码混淆的作用

   

1.减小APK的体积

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


3实现代码混淆


Eclipse 用户:


修改项目下project.properties文件,将下面代码前面的注释(#)去掉。 

proguard.config=${sdk.dir}/tools/proguard/ proguard-android.txt:proguard-project.txt


Android Studio用户:


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


3.1 用Demo说话




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



大概阅读一下。


注意下面这行说明:

# 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](http://download.csdn.net/detail/qq_17250009/9459037)

  4. 进入dex2jar解压目录,使用dex2jar命令反编译

  5. 使用jd-gui查看源代码


dex2jar反编译命令为:

d2j-dex2jar classes.dex

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




可以看到属性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这个文件,下文会涉及到。


4

实现自己的代码混淆

   

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

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

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




对比可以发现,setName()方法没有被混淆,这就是-keep关键字的作用,保持某部分代码不会混淆。


有读者可能会觉得本来就是混淆,这会怎么又不混淆了。


默认的混淆规则是:除了声明不被混淆的代码,其余的都会被混淆!比如我们在WebView中和JS交互的时候,需要提供一个接口给JS调用,如果混淆了方法名,JS就会找不到这个方法。


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

# 指定代码的压缩级别 -optimizationpasses 5         # 是否使用大小写混合                                               -dontusemixedcaseclassnames                                                     # 是否混淆第三方jar -dontskipnonpubliclibraryclasses                                                 # 混淆时是否做预校验 -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               # 保持 native 方法不被混淆 -keepclasseswithmembernames class * {                                              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); } # 保持枚举 enum 类不被混淆 -keepclassmembers enum * {                                                          public static **[] values();    public static ** valueOf(java.lang.String); } # 保持 Parcelable 不被混淆 -keep class * implements android.os.Parcelable {                                public static final android.os.Parcelable$Creator *; }

5查看混淆后的日志
   

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


  1. cmd进入sdk/tools/proguard/bin目录。

  2. 将混淆后的日志和上文提到的mapping文件放入此目录中。

  3. 执行命令:retrace.bat mapping.txt XXX.txt


执行命令前:



执行命令后:



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



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存