Android代码混淆后的定位

什么是代码混淆

在java写的.java文件后,使用javac编译成.class文件,在编译的过程,不像C/C++或C#那样编译时进行加密或混淆,它是直接对其进行符号化、标记化的编译处理,于是,也产生了一个逆向工程的问题:可以根据.class文件反向解析成原来的java文件!
Android应用程序很大一部分都是使用Java语言编写的,在发布自己的程序的时候,有一些十分重要的功能或者组件,我们并不希望被别人反编译后直接使用,所以才有了代码混淆的做法,当然一些极其重要的东西一般会用C/C++编写然后通过动态库的方式进行调用。
如图所示,在未开启代码混淆的时候,反编译后的代码和左边的源代码基本上一一对应,能够十分轻松的找到代码的逻辑:
未开启代码混淆
而在进行了代码混淆之后,所反编译出来的源码如图所示:
混淆反编译后的
可以看出,在开启反编译的情况下,程序的代码暂且算是得到了一定程度上的保护。
目前代码混淆的方式有很多种,其中Android Studio就自带一种,他是通过将包名、类名、方法名用相似的名字进行“重命名”,这样一来对于反编译后的代码阅读起来也是极其困难:
混淆后的类
当然目前还有更加安全的做法就是将字节码文件转换成为动态库,这样一来通过极大地增加了反编译的难度(比如腾讯应用加固)。

如何开启Android Studio的代码混淆

对于android应用程序的代码混淆目前也有着多种方式,最简单的方法就是Android Studio自带的代码混淆功能,在应用模块的build.gradleminifyEnabled改为true即可打开代码混淆功能:
build.gradle
另外,我们还可以自定义配置混淆规则,Android Studio系统默认的混淆规则文件为同级目录下的proguard-rules.pro这个文件,我们只需修改proguard-rules.pro即可,这里介绍一下一些比较常用的混淆规则,具体的可以参考:缩减、混淆处理和优化应用

#指定压缩级别(0~7之间,默认为5,一般不需要更改)
-optimizationpasses 7

#混淆时不使用大小写混合,混淆后的类名为小写(大小写混淆容易导致class文件相互覆盖)
-dontusemixedcaseclassnames

#保护代码中的Annotation不被混淆(这在Json实体映射是非常重要,例如FastJson)
-keepattributes *Annotation*

#避免混淆泛型,这在JSON实体映射时非常重要,比如fastJson
-keepattributes Signature

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

#设置抛出异常时保留代码行号
-keepattributes SourceFile.LineNumberTable

#忽略警告 (慎用)
-ignorewarnings

#未混淆的类和成员
-printseeds seeds.txt
#列出从apk中删除的代码
-printusage unused.txt

#保留所有的本地native方法不被混淆
-keepclasseswithmembernames class *{
    native <methods>;
 }
 
#保留继承自android.view.View类的子类
-keep public class * extends android.view.View

#保留在Activity中的方法参数是view的方法,从而我们在layout里面编写onClick就不会被影响
-keepclassmembers class * extends android.app.Activity{
	public void *(android.view.View);
}

#枚举类不能被混淆
-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 *;
}

#对于回调函数onXXEvent的,不能被混淆
-keepclassmembers class * {
    void *(**Event);
}

如何在程序奔溃后定位到奔溃的代码行

mapping.txt

代码混淆的好处有,但是也有他的缺点:对于开发人员来说,在不添加日志的情况下,程序奔溃以后很难定位到具体的代码位置。
Exception
就像图里所展示的一样,在Crash日志里面根本定位不到异常位置。要是大概了解Crash的位置并且该类的代码量不多,可以通过添加日志慢慢分析定位,那要是别的情况呢?
幸运的是,Android Studio在对代码进行混淆的时候,同时也会生成一份名为 mapping.txt的索引文件,通过这份文件还是可以比较轻松的定位出问题来源的,该文件位置为 build/outputs/mapping/debug/mapping.txt ,当然这个文件的位置会根据你的配置也许会有所不同的。
mapping.txt

分析流程

在日志中的输出为:

java.lang.NullPointerException
    at java.io.File.<init>(File.java:283)
    at com.imorning.demo.MainActivity.F(:26)
    at com.imorning.demo.MainActivity.E(Unknown Source:0)
    at k1.a.onClick(Unknown Source:2)
    at android.view.View.performClick(View.java:7448)
    at com.google.android.material.button.MaterialButton.performClick(:1119)
    at android.view.View.performClickInternal(View.java:7425)
    at android.view.View.access$3600(View.java:810)
    at android.view.View$PerformClick.run(View.java:28305)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:223)
    at android.app.ActivityThread.main(ActivityThread.java:7656)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

在这其中,比较重要的几句就是

at java.io.File.<init>(File.java:283)
at com.imorning.demo.MainActivity.F(:26)
at com.imorning.demo.MainActivity.E(Unknown Source:0)

首先通过 java.io.File.<init> 这一句可以确定该异常发生的位置是在构造 File 对象的时候,类似于 new File(filePath) 这种情况,然后根据

at com.imorning.demo.MainActivity.F(:26)
at com.imorning.demo.MainActivity.E(Unknown Source:0)

这两句可以判断是在 MainActivity 这个类里面,具体的方法是FE,但是很显然我们的源代码里面是不存在这两个方法的,所以这两个方法是由于代码混淆后产生的,那如何确定他们在源代码中的名字是啥呢?
打开 mapping.txt 文件,搜索 com.imorning.demo.MainActivity 这个类,然后可以找到如下内容:

......
com.imorning.demo.MainActivity -> com.imorning.demo.MainActivity:
    java.lang.String path -> o
    13:15:void <init>() -> <init>
    void $r8$lambda$jbVq07liQWCDW9wfWoMM5fMjaN0(com.imorning.demo.MainActivity,android.view.View) -> E
    26:28:void lambda$onCreate$0(android.view.View) -> F
    19:31:void onCreate(android.os.Bundle) -> onCreate
......
k1.MainActivity$$ExternalSyntheticLambda0 -> k1.a:
    com.imorning.demo.MainActivity com.imorning.demo.MainActivity$$InternalSyntheticLambda$0$b73ef46b845bbab58eb3dd9efa4049f61728e93353f15e1a854468e6cd9441d6$0.f$0 -> a
    void com.imorning.demo.MainActivity$$InternalSyntheticLambda$0$b73ef46b845bbab58eb3dd9efa4049f61728e93353f15e1a854468e6cd9441d6$0.<init>(com.imorning.demo.MainActivity) -> <init>
    void com.imorning.demo.MainActivity$$InternalSyntheticLambda$0$b73ef46b845bbab58eb3dd9efa4049f61728e93353f15e1a854468e6cd9441d6$0.onClick(android.view.View) -> onClick
......

可以在第一部分很明显找到EF两个函数的定义,分别是:

$r8$lambda$jbVq07liQWCDW9wfWoMM5fMjaN0(com.imorning.demo.MainActivity,android.view.View) -> E
26:28:void lambda$onCreate$0(android.view.View) -> F

这里由于使用了lambda表达式,因此对于函数E的确认有点问题,不过问题不大,可以通过其两个参数确定出来大致范围,并且由于使用了lambda表达式也是比较好确认的。
对于函数F则可以直接看出来它的函数名是onCreate(),因此定位比较简单,同时也就确认了函数E是在onCreate()中的某个方法,并且使用了File的构造函数,这样一来便可以确定出来位置了:
Exception

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偷窃月亮的贼

感谢投喂~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值