版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、反编译
我们进行 apk 开发的时候,有时候一些效果写不出来,没有思路,这时候会去查看其他应用的源代码,参考怎么实现的,在这里需要对打包好的 apk 进行反编译查看。
安卓常用的反编译工具
1.apktool:
下载地址:https://ibotpeaches.github.io/Apktool/
作用:主要资源文件的获取。如 AndroidManifest、res 等。
解包: java -jar apktool的名字 d(反编译) 要解包的apk -o(输出) 文件名
java -jar .\apktool_2.3.0.jar d .\ap.apk -o out
重打包: java -jar apktool的名字 b(打包) 要打包的文件夹名字
java -jar .\apktool_2.3.0.jar b .\out\
我们直接用压缩工具打开 apk 获取的资源文件是被处理过的,不可读的,需要用 apktool 进行解包才可以。可以查看布局、样式、默认启动的 Activity、Application 等。
2.dex2jar:
下载地址:https://sourceforge.net/projects/dex2jar/files/
作用:将 apk 中的 dex 反编译成 jar。
反编译: dex2jar的bat 要反编译的dex
d2j-dex2jar.bat classes.dex
先获取 apk 中的 dex,然后使用 dex2jar 把 dex 转换成 jar 包,通过其他工具查看 jar 包下的源码。
3.enjarify:
下载地址:https://github.com/google/enjarify
作用:将 apk 或 apk 中的 dex 反编译成 jar。
反编译: enjarify 要反编译的apk 或 dex
enjarify classes.dex
enjarify 的使用需要依赖与 python3。
4.jd-gui:
下载地址:http://jd.benow.ca/
作用:查看反编译后 jar 包的代码。
查看 jar 的代码也可以在 Eclipse 中安装插件进行查看;Android Studio 自带反编译,可以直接查看 jar 包代码。(个人比较喜欢在这两个环境下进行查看)
5.jadx:
作用:直接查看资源和代码
jadx 的功能十分强大,可以直接使用这个进行 apk 的反编译。
二、Proguard 混淆
当我们编写代码时候,出于安全考虑,不希望自己编写的代码被其他人反编译,或者说比较简单的就获取到我们的实现逻辑。这时候需要对代码进行混淆。
1.Proguard
Proguard 可以对 java 代码进行压缩、优化、混淆、预检,会使编译后的文件更小,不容易进行逆向工程,反编译获取到代码。Proguard 先对代码进行压缩,然后优化,接着混淆,最后进行预检。
**压缩:**检测和删除没有使用到的类、方法和字段。
**优化:**对字节码进行优化,删除没有用的指令。如删除不必要的字段存取,不必要的方法调用,只写字段,未使用的方法参数等等。无论如何,字节码会变得更小。
**混淆:**使用 a、b、c 等没有意义的名称,对类、方法、字段进行重命名。
**预检:**对处理后的代码进行检查校验。
2.Proguard 配置文件
可以在 Proguard 官网 进行查看指导手册。
在 Android Studio 中使用混淆是比较简单的, Android Studio 自身集成了 ProGuard,默认是不开启的,只需要在 app 模块下的 build.gradle 文件中修改 minifyEnabled false 为 true 即可。
下方 proguardFiles 就是混淆配置文件,可以在这里定义项目打包的混淆选项。
getDefaultProguardFile(‘proguard-android.txt’) 是系统配置的,这个具体路径可以打印出来然后进行查看。
如果说没有 proguard-android.txt 这个文件存在,或者有开启了混淆报错(还是没有这个文件),可以点击 Gradle 中的任务进行生成。
proguard-rules.pro 是我们自己进行添加混淆规则配置的文件。
proguard-rules.pro 默认情况下是空的,会使用系统的 getDefaultProguardFile(‘proguard-android.txt’) 这个配置文件进行混淆。
3.Proguard 配置
我们主要使用的是 keep 指令,keep 相关的可以分为两组,第二组是第一组加 “names”。
-keep
指定类和类成员(变量和方法)不被混淆。
-keepclassmembers
指定类成员不被混淆(就是-keep的缩小版,不管类名了)。
-keepclasseswithmembers
指定类和类成员不被混淆,前提是指定的类成员存在(如果不存在,仍然会混淆)。
没有加 names 的是 From being removed or renamed,即保持不被移除和重命名;加的是 From being renamed,即保持不被重命名。两者的区别就是是否保持不被移除,前者如果指定了不被混淆和移除,那么即使这个类或类成员有无用的也会被保留下来;后者如果是无用的类或类成员,即使指定了不被混淆那么也会被移除。
系统默认的配置文件:
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
# will be ignored by new version of the Android plugin for Gradle.
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize steps (and performs some
# of these optimizations on its own).
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
#关闭优化
-dontoptimize
#不知道什么意思?
#dont=不要
#use 使用
#mixed 混合
#case 情况 案件
#classnames 类名
#所以合起来就是 不要使用混合情况的类名 这里的混合指混合大小写
#不使用大小写混合,混淆后类名称为小写
-dontusemixedcaseclassnames
#不要跳过非公共库的类
#jar包也需要混淆
-dontskipnonpubliclibraryclasses
#输出详情
-verbose
#保留注属性:注解、内部类、(Signature、EnclosingMethod)泛性与反射
# Preserve some attributes that may be required for reflection.
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod
#不混淆类
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
-keep public class com.google.android.vending.licensing.ILicensingService
#不打印配置类中可能的错误或遗漏的注释
-dontnote com.android.vending.licensing.ILicensingService
-dontnote com.google.vending.licensing.ILicensingService
-dontnote com.google.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
#不混淆任何包含native方法的类的类名以及native方法名
-keepclasseswithmembernames class * {
native <methods>;
}
# Keep setters in Views so that animations can still work.
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
# We want to keep methods in Activity that could be used in the XML attribute onClick.
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
# Preserve annotated Javascript interface methods.
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
# The support libraries contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontnote android.support.**
#不对指定的类、包中的不完整的引用发出警告
-dontwarn android.support.**
# 保留 android.support.annotation.Keep 这个类
# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep
# 保留使用 Keep 注解的类,就不会被移除
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
三、测试与定位
混淆过的 apk 必须完整的重新测试一遍,有可能由于类名或者方法名的混淆,导致一些方法找不到(Activity 的 onCreate 等),从而异常。在使用反射的时候,由于被反射的类没有被使用到,Proguard 检测时候,会把它删除掉,导致找不到。
1.模拟异常
首先,在这边先模拟一个异常,然后进行异常的定位。
创建 Bug 类,里面有一个 bug 方法,除 0 异常,然后在 MainActivity 中引用。
Bug:
public class Bug {
public static int bug(){
int sum = 0;
for(int i=0; i<10; i++){
sum += i;
}
int i = 0;
sum = sum / i;
return sum;
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int sum = Bug.bug();
TextView textView = findViewById(R.id.id);
textView.setText(sum + "");
}
}
开启混淆:
运行报错:
可以发现,这边的异常堆栈打印出来的是混淆过后的类名和方法名,不利于我们直接进行定位问题。
再认真查看一下,会发现混淆代码后面的错误行号显示的是 (Unknown Source),而不是行号。
2.混淆映射文件
开启混淆的时候会在 outputs 文件下生成一个 mapping.txt 文件,这个文件保存着混淆前后各个类、方法、属性的对应关系。
我们可以直接通过这个文件进行查看,参照打出来的异常堆栈,找到原先错误的类,方法。
这段比较简单,所以一下子就能定位到错误函数,如果函数层级较深,光靠自己比对来查找难度就比较大了。所以 sdk 中提供了一个工具在 tools/proguard/bin 中,可以直接帮我们还原原先的错误堆栈信息。
我们把logcat里面保存的错误堆栈复制到一个文件中:
然后执行 retrace 脚本(在 Windows 上为 retrace.bat;在 Mac/Linux 上为 retrace.sh)
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
现在错误的日志堆栈 mapping 还原了。所以我们每次在发布版本之后都需要保留这个 mapping 文件。
3.开启行号
接下来我们解决另一个问题,行号显示不出来。
如果希望出现具体行数,我们需要在配置文件中加入抛出异常时保留代码行号,在异常分析中可以方便定位。
-keepattributes SourceFile,LineNumberTable
这个可以直接补充在我们的配置文件中。
再次运行,发现已经打出行号信息。
但是在后面把真正的类名也显示出来了,为了屏蔽这个显示,使用字符串"AAA"来替代真正的类,避免泄漏更多的信息。
-renamesourcefileattribute AAA
这时候打印出来的异常堆栈没有包含 Bug 类的信息了。
而且也是可以使用 mapping.txt 文件进行查找。
四、总结
虽然我们的代码经过了混淆,但是实际上,对于有耐心有条件的人来说,还是能够从混淆过后的代码中看到蛛丝马迹。混淆只是使反编译解读代码的难度增大。