小言_互联网的博客

LoadedApk Crash的探索之路

349人阅读  评论(0)

LoadedApk Crash的探索之路

Bug成因分析,问题定位

Crash 现场

起因是:在工作中遇到两个奔溃堆栈日志,如下图:

初步分析

这个Crash发生在第三方SDK中,是由SDK内部触发,不是通过直接调用产生的,并且难以复现。本来这个问题应该由SDK提供方来解决,但是用户不管这些,需要我们APP自己来解决。

我在华为手机上也遇到这个问题了,没有页面发生奔溃,但是也没有从根本上避免这个问题。

代码定位

这两个Crash分别发生在ConnectionTracker类的unbindService方法和zzaz类的onReceive方法。

通过查看LoadedApk的源码,定位到了抛出异常的地方。通过分析代码发现,是没有注册Receiver或Service,就进行相应的解注册而抛出的异常。


Crash模拟

自己写了一个demo,做了一个试验,比较顺利地复现这一情况。

手动修复尝试

反射替换实例

思路:使用反射,替换成一个带有try-catch方法ConnectionTracker的子类。

报错:

Class创建实例

先通过反射创建一个ConnectionTracker的一个实例,也报错。

public void reflection1() throws Exception {
    ConnectionTracker instance = ConnectionTracker.getInstance();
    Field zzfa = instance.getClass().getDeclaredField("zzfa");
    zzfa.setAccessible(true);
    ConnectionTracker tracker = ConnectionTracker.class.newInstance();
    zzfa.set(instance, tracker);
}
java.lang.IllegalAccessException: void com.google.android.gms.common.stats.ConnectionTracker.<init>() is not accessible from java.lang.Class<zhangls.me.myapplication12.MainActivity>

Constructor创建实例

换一种方法用反射创建一个ConnectionTracker的一个实例,成功了,没有报错。

public void reflection2() throws Exception {
    ConnectionTracker instance = ConnectionTracker.getInstance();
    Field zzfa = instance.getClass().getDeclaredField("zzfa");
    zzfa.setAccessible(true);

    Constructor<ConnectionTracker> constructor
            = ConnectionTracker.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    ConnectionTracker tracker = constructor.newInstance();

    zzfa.set(instance, tracker);
}

使用CGlib

但是还是没有办法去创建ConnectionTracker它的子类,现在尝试使用cglib动态代理的方式来做。

//implementation 'cglib:cglib:3.3.0'
public void reflection3() throws Exception {
    ConnectionTracker instance = ConnectionTracker.getInstance();
    Field zzfa = instance.getClass().getDeclaredField("zzfa");
    zzfa.setAccessible(true);

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(ConnectionTracker.class);
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                throws Throwable {
            Object result = null;
            try {
                result = proxy.invokeSuper(obj, args);
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return result;
        }
    });
    zzfa.set(instance, enhancer.create());
}

也报错,原来是Android不适用标准的cglib.

2019-10-07 09:39:45.663 20306-20306/zhangls.me.myapplication12 E/AndroidRuntime: FATAL EXCEPTION: main
Process: zhangls.me.myapplication12, PID: 20306
java.lang.ExceptionInInitializerError
    at zhangls.me.myapplication12.MainActivity.reflection3(MainActivity.java:55)
    at zhangls.me.myapplication12.MainActivity.onCreate(MainActivity.java:30)
    at android.app.Activity.performCreate(Activity.java:7136)
    at android.app.Activity.performCreate(Activity.java:7127)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:6669)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

使用CGlib-for-Android

找了一个CGLib-for-Android,地址:https://github.com/leo-ouyang/CGLib-for-Android

public void reflection4() throws Exception {
    ConnectionTracker instance = ConnectionTracker.getInstance();
    Field zzfa = instance.getClass().getDeclaredField("zzfa");
    zzfa.setAccessible(true);

    leo.android.cglib.proxy.Enhancer enhancer = new leo.android.cglib.proxy.Enhancer(this);
    enhancer.setSuperclass(ConnectionTracker.class);
    enhancer.setInterceptor(new leo.android.cglib.proxy.MethodInterceptor() {
        @Override
        public Object intercept(Object o, Object[] args,
                                leo.android.cglib.proxy.MethodProxy methodProxy) throws Exception {
            return methodProxy.invokeSuper(o, args);
        }
    });
    zzfa.set(instance, enhancer.create());
}

还是报错,Java.lang.IllegalStateException: static methods cannot access ‘this’

W: java.lang.IllegalStateException: static methods cannot access 'this'
W:     at leo.android.cglib.dx.Code.getThis(Code.java:256)
W:     at leo.android.cglib.proxy.Enhancer.generateFieldsAndMethods(Enhancer.java:154)
W:     at leo.android.cglib.proxy.Enhancer.create(Enhancer.java:49)
W:     at zhangls.me.myapplication12.MainActivity.reflection4(MainActivity.java:87)
W:     at zhangls.me.myapplication12.MainActivity.onCreate(MainActivity.java:30)
W:     at android.app.Activity.performCreate(Activity.java:7136)
W:     at android.app.Activity.performCreate(Activity.java:7127)
W:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
W:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
W:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
W:     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
W:     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
W:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
W:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
W:     at android.os.Handler.dispatchMessage(Handler.java:106)
W:     at android.os.Looper.loop(Looper.java:193)
W:     at android.app.ActivityThread.main(ActivityThread.java:6669)
W:     at java.lang.reflect.Method.invoke(Native Method)
W:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
W:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

遗留的那些线索

  • Extends private class
  • 动态修改字节码,重新加载
  • app 编译覆盖library的class
  • 构建Apk时进行dex分包
  • JavaAgent premain

DexClassLoader动态加载

基本原理

安卓App热补丁动态修复技术介绍
(https://zhuanlan.zhihu.com/p/20308548)

该方案的缺点

  • 兼容性不好
  • 维护成本较高
  • 容易出问题

手动修改SDK的aar方案

操作步骤

  1. 准备old.aar 放在app/libs目录下
  2. unzip old.aar -d tempFolder
  3. unzip classes.jar -d tempFolderClasses
  4. 修改对应的class
  5. jar cvf newClasses.jar -C tempFolderClasses/ .
  6. jar cvf new.aar -C tempFolder/ .

https://github.com/dinuscxj/ClassPlugin
(A flexible class replacement plugin for gradle)

遇到的问题


该方案的缺点

  • 操作步骤复杂,繁琐
  • 依赖传递失效,引起不必要的麻烦
  • Git无法记录,不利于以后的跟踪和协作
  • 无法复用,或者成本很高

构建Apk编码时修改class

基本原理

插件架构

该方案的缺点

  • 增加打包时间
  • 学习成本较高
  • 注意混淆

行业内其他方案开源

1.https://github.com/didi/booster

简介:🚀Optimizer for mobile applications

Booster 示例:使用 ASM 对字节码进行操作
https://github.com/boostersamples/transformer-with-asm

2.https://github.com/tiann/epic

简介:Epic 是一个在虚拟机层面、以 Java Method 为粒度的 运行时 AOP Hook 框架。简单来说,Epic 就是 ART 上的 Dexposed(支持 Android 4.0 ~ 10.0)。它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析、安全审计等。

监控 Java 线程的创建和销毁

class ThreadMethodHook extends XC_MethodHook{
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        Thread t = (Thread) param.thisObject;
        Log.i(TAG, "thread:" + t + ", started..");
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Thread t = (Thread) param.thisObject;
        Log.i(TAG, "thread:" + t + ", exit..");
    }
}

DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Thread thread = (Thread) param.thisObject;
        Class<?> clazz = thread.getClass();
        if (clazz != Thread.class) {
            Log.d(TAG, "found class extend Thread:" + clazz);
            DexposedBridge.findAndHookMethod(clazz, "run", new ThreadMethodHook());
        }
        Log.d(TAG, "Thread: " + thread.getName() + " class:" + thread.getClass() +  " is created.");
    }
});
DexposedBridge.findAndHookMethod(Thread.class, "run", new ThreadMethodHook());

3.https://github.com/eleme/lancet

简介:
Lancet 是一个轻量级Android AOP框架。

编译速度快, 并且支持增量编译.
简洁的 API, 几行 Java 代码完成注入需求.
没有任何多余代码插入 apk.
支持用于 SDK, 可以在SDK编写注入代码来修改依赖SDK的App.

@Proxy("i")
@TargetClass("android.util.Log")
public static int anyName(String tag, String msg){
    msg = msg + "lancet";
    return (int) Origin.call();
}

参考资料

1. Android修改第三方.aar后重新打包(https://www.jianshu.com/p/f0a267551493)

2. Java字节码增强探秘(https://juejin.im/post/5d773ae1518825058772843c)

3. ASM实战统计方法耗时(http://www.wangyuwei.me/2017/03/05/ASM实战统计方法耗时/)

4. Android 跟我一起用 ASM 实现编译期字节码插桩(http://blog.n0texpecterr0r.cn/?p=752)

5. Gradle插件与ASM入门(https://www.jianshu.com/p/83a360c9a3d7)

6. CSDN ASM 6篇文章专栏(https://blog.csdn.net/lijingyao8206/article/category/3276863)

7. How to edit jar files with ASM(https://riptutorial.com/java/example/12965/how-to-edit-jar-files-with-asm)


转载:https://blog.csdn.net/ihrthk/article/details/102535584
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场