腾讯hotfix分析

在逆向腾讯某产品时,发现很多方法的前面都有这么一段代码:

 if(NotDoVerifyClasses.DO_VERIFY_CLASSES) {
     System.out.print(AntiLazyLoad.class);
 }

NotDoVerifyClasses和AntiLazyLoad在dex中居然找不到,这勾起我的兴趣。先来debug看看,到底是从哪里加载的这两个类:

ok,发现是从files下的verify.jar加载的,找到这个jar发编译一下:

package com.tencent;

public class AntiLazyLoad {
    public AntiLazyLoad() {
        super();
    }
}
package com.tencent;

public class NotDoVerifyClasses {
    public static boolean DO_VERIFY_CLASSES;

    static {
        NotDoVerifyClasses.DO_VERIFY_CLASSES = false;
    }

    public NotDoVerifyClasses() {
        super();
    }
}

代码很简单,感觉像是个桩,但这个有什么用?

从文件路径我们知道有hotfix,应该和热修复有关,深入研究一下。

我们先来看看另外一个问题:在前面的截图中,我们看到classloader是PathClassLoader,其pathList中居然被增加一个files目录下的jar,这个是怎么做到的?一般来说动态加载应该是DexClassLoader才对。

通过搜索verify.jar,发现了关键代码:

public static boolean inject(Context ctx, String dexPath, String arg5, String odexPath, String clazzName, boolean arg8) {
    boolean v0 = false;
    if(dexPath != null && (new File(dexPath).exists())) {
        if(DexUtil.isAliyunOs()) {
            try {
                DexUtil.injectInAliyunOs(ctx, dexPath, arg5, odexPath, clazzName, arg8);
                v0 = true;
            }
            catch(Throwable v0_1) {
                PatchLog.e("SystemClassLoaderInjector", "fail to inject", v0_1);
                throw v0_1;
            }
        }
        else if(!DexUtil.hasBaseDexClassLoader()) {
            try {
                DexUtil.injectBelowApiLevel14(ctx, dexPath, arg5, odexPath, clazzName, arg8);
                v0 = true;
            }
            catch(Throwable v0_1) {
                PatchLog.e("SystemClassLoaderInjector", "fail to inject", v0_1);
                throw v0_1;
            }
        }
        else {
            try {
                DexUtil.injectAboveEqualApiLevel14(ctx, dexPath, arg5, odexPath, clazzName, arg8);
                v0 = true;
            }
            catch(Throwable v0_1) {
                PatchLog.e("SystemClassLoaderInjector", "fail to inject", v0_1);
                throw v0_1;
            }
        }
    }

    return v0;
}

不同版本做了不同的Classloader注入方案,我是4.4的手机,对应看看injectAboveEqualApiLevel14

private static void injectAboveEqualApiLevel14(Context ctx, String dexPath, String libPath, String odexPath, String arg9, boolean arg10) {
    ClassLoader oldClassloader = ctx.getClassLoader();
    DexClassLoader dexClassLoader = new DexClassLoader(dexPath, odexPath, libPath, ctx.getClassLoader());
    Object v2 = DexUtil.combineArray(DexUtil.getDexElements(DexUtil.getPathList(oldClassloader)), 
            DexUtil.getDexElements(DexUtil.getPathList(dexClassLoader)), arg10);
    Object v0_1 = DexUtil.getPathList(oldClassloader);
    DexUtil.setField(v0_1, v0_1.getClass(), "dexElements", v2);
    if(!TextUtils.isEmpty(((CharSequence)arg9))) {
        dexClassLoader.loadClass(arg9);
        PatchLog.e("DexUtil", "load clas " + arg9 + " success ");
    }
}

原理弄明白了:先通过DexClassLoader加载files下的jar,然后反射获取其dexElements,然后合并到PathClassLoader的dexElements中,很巧妙的做法,其他注入方案不再详述。

上述这个过程在Application的attachBaseContext中就完成,即app一运行就会加载。也就是说,加入某个时候更新了files下的verify.jar,在下次启动app时,修改后的verify.jar代码就会被加载。 前面最开始看到有很多方法执行前就调用的桩,如果精心去设计NotDoVerifyClasses和AntiLazyLoad的代码,在方法体执行前就能被执行,确实可以做到热修复。

然而,其hotfix目录下不仅仅是上面的代码,上面也没有讲述其是如何从服务器端拉取热修复的代码的, hotfix下代码如下:

代码有点多,我耐着性子看完了,截图中的这一坨代码是为了做补丁管理的,其中就包括从服务端去拉取补丁,然后保存到files下的hotfix目录。假如服务端返回的补丁名字叫做verify,那么就会覆盖verify.jar,我想腾讯他们在推送热修复补丁时,肯定是推送这样一个补丁。

我记得阿里之前开源了一个修改自xposed的热修复框架:dexposed,其利用xposed的hook原理,可以做到hook任何一个java方法,效果也是不错的,但xposed因为很多适配问题,所以要产品化效果不太好。

腾讯的这种热修复方案是通过硬编码方式植入桩,假如植入方式能够工具化,也是一个不错的选择,至少没有适配问题。

关于上面的热修复原理,腾讯有官方介绍,发现自己之前有一些理解不到位的地方,原理参考: https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a&scene=1&srcid=1106Imu9ZgwybID13e7y2nEi#wechat_redirect

搜索了一下热修复,原来是2015年很火的一个技术(孤陋寡闻了),现在已经有很多成熟的方案,上述的hotfix只是其中之一,想了解更多可以自行去搜索。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JNI实现源码分析【二 数据结构】正文0x01: 虚拟机中的对象0x02: Dex相关的结构0x03: 实现JNI需要的数据结构0x04: JNI参数传递的数据结构

    我们知道,Java是面向对象的,Java是运行在虚拟机里面的,即先通过编译成字节码(dalvik对应dex),虚拟机解析字节码,构造出逻辑上相同的对象。 及虚拟...

    用户2930595
  • AndroidStudio阅读Android源码终极篇

    几乎在所有的教程里面,都提到说,要阅读Android源码,需要完整编译一次源码,比如我之前的文章使用AndroidStudio阅读Android源码。但是这个完...

    用户2930595
  • 反调试检测之一TracerPid

    当我们使用Ptrace方式跟踪一个进程时,目标进程会记录自己被谁跟踪,可以查看/proc/pid/status看到这个信息,下图展示的是使用ida进行调试的情况...

    用户2930595
  • TKE独立集群(1)

    如果serivce的数量不够用,该如何解决?创建集群后还可以更改配置吗?... ...

    何飞良
  • 使用JDK自带的VisualVM进行Java程序的性能分析

    VisualVM是JDK自带的一个用于Java程序性能分析的工具,JDK安装完毕后就有啦,在JDK安装目录的bin文件夹下能找到名称为jvisualvm.exe...

    Jerry Wang
  • MySQL学习笔记【基础篇】

    1、DB:数据库,保存一组有组织的数据的容器 2、DBMS:数据库管理系统,又称为数据库软件(产品),用于管理DB中的数据 3、SQL:结构化查询语言,用于...

    _DIY
  • 【视频】LeCun 报道:波士顿动力 Spot Mini,头部取货,可送快递

    【新智元导读】波士顿动力创始人Marc Raibert 昨天受邀在NIPS2016 大会上发表演讲,他的演讲题目为《动力足式机器人》。在演讲中,Raibert ...

    新智元
  • 还在担心写的一手烂SQL,送你4款工具

    对于正在运行的mysql,性能如何,参数设置的是否合理,账号设置的是否存在安全隐患,你是否了然于胸呢?

    JAVA葵花宝典
  • 【福布斯】Mobileye,车企巨头对抗谷歌的秘密武器

    来源:Forbes 译者:王亮 (文/Joann Muller)Amnon Shashua 每天早上要花20分钟开车去他耶路撒冷的办公室上班——通勤体验并不糟糕...

    新智元
  • 【先锋】完成B轮融资TalkingData:平台理念+跨界分析+技术开源

    年初,从北极光创投获得了1000万美元投资的TalkingData创始人兼CEO崔晓波走入CSDN的视野。《TalkingData CEO崔晓波深度专访:真正懂...

    CSDN技术头条

扫码关注云+社区

领取腾讯云代金券