腾讯hotfix分析

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

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

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

腾讯hotfix分析_第1张图片

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下代码如下:

腾讯hotfix分析_第2张图片

代码有点多,我耐着性子看完了,截图中的这一坨代码是为了做补丁管理的,其中就包括从服务端去拉取补丁,然后保存到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只是其中之一,想了解更多可以自行去搜索。

你可能感兴趣的:(腾讯hotfix分析)