• Android热补丁技术—dexposed原理简析(阿里Hao)


    本文由嵌入式企鹅圈原创团队成员、阿里资深project师Hao分享。

    上篇文章《Android无线开发的几种经常使用技术我们介绍了几种android移动应用开发中的经常使用技术,当中的热补丁正在被越来越多的开发团队所使用,它涉及到dalvik虚拟机和android的一些核心技术,如今就来介绍下它的一些原理。

    本篇先介绍dexposed方案:https://github.com/alibaba/dexposed。它是手机淘宝团队使用的热补丁方案,后来开源到github上,取的名字dexposed表明了自己是基于大名鼎鼎的xposed hook方案,有饮水思源、回馈开源项目的意思。

    与xposed不同的是,dexposed是自己hook自己的应用。因此不须要root权限。

    它的关键点是:在native层中先找到要修复的Java函数相应的Method对象,改动它变为native方法,把它的nativeFunc指向hookedMethodCallback。这样对这个java函数的调用就转为调用hookedMethodCallback这个native函数了,然后再用这个native函数回调java层自己实现的统一接口来处理。这个统一接口是XC_MethodReplacement类。它主要有beforeHookedMethod、afterHookedMethod和replaceHookedMethod等几个方法,前两个在运行原java函数前后做一些事。replaceHookedMethod则是替换原java方法。以下来具体分析下这个hook的过程。


    一、DexposedBridge.findAndHookMethod

    findAndHookMethod是hook原java方法的入口,它传入的參数是类Class和方法名,最后一个可变參数parameterTypesAndCallback,是用户实现的用于替换原方法的XC_MethodReplacement的实例。

     

    先调用XposedHelpers.findMethodExact找到要hook的java方法。再用hookMethod进行真正的hook。

    1.findMethodExact依据类名和方法名,用反射找到Method。并把它的属性改为可訪问。

    2.hookMethod先把hook成功后的callback、要hook的方法的參数和返回值类型保存到AdditionalHookInfo中,把它作为參数传给hookMethodNative。hookMethodNative是一个native方法。它的第3个參数slot表示该Method在class的方法表中所处的位置,在native的实现中会用到这个slot。

     

    hookMethodNative的实现环境分dalvik和art,由于dexposed对art的支持不完好,同一时候art本身的原理和机制也是一个难点,所以本篇仅仅介绍dalvik下的实现。art的有关内容以后有机会再作介绍。

     

     

    二、hookMethodNative

    每个java的类在虚拟机的实现中都相应着一个C++的ClassObject。dvmDecodeIndirectRef是libdvm中的方法。它能够从java对象的间接引用获得ClassObject对象。再依据slot,用dvmSlotToMethod找到Method对象。这里的ClassObject和Method都是虚拟机内部用来表示class和Method的数据结构。

    然后把原来的Method结构先备份到XposedHookInfo中,

     

    XposedHookInfo的结构例如以下:

    可见,它用originalMethod保存原来java方法的Method。用reflectedMethod保存原java方法在native的引用。注意这跟originalMethod中保存的Method对象不同。originalMethod中保存的Method能够理解为运行字节码的地址,而reflectedMethod中保存的是用来描写叙述原java方法的一个ClassObject对象。它们两个在第五部分又一次调用原java方法时会用到。

    additionalInfo用来保存附加信息AdditionalHookInfo。接着使用SET_METHOD_FLAG宏把该方法设为native。让nativeFunc指向hookedMethodCallback。这样对该java方法的调用就会转为对hookedMethodCallback这个native方法的调用了。Insns指向这种方法的字节码,在这里把它改为指向hookInfo。实际上也就是originalMethod的字节码的地址。

     

     

    三、hookedMethodCallback

    hookedMethodCallback会回调java层的方法handleHookedMethod。终于会调用到前面说过的,在findAndHookMethod中传入的XC_MethodReplacement里的before、after方法。

     

    这里首先把在hookInfo中保存的信息作为传给java层的handleHookedMethod的參数,然后用dvmCallMethod这个dalvik的函数调用xposedHandleHookedMethod这个java的方法。xposedHandleHookedMethod在初始化时已经被设置好了。

    GetStaticMethodId是dvm中用来获取静态方法地址的函数,可见在初始化时,已经把java的静态方法handleHookedMethod的地址赋给了xposedHandleHookedMethod了。这里须要注意两点。一是这个时候已经不能像没hook之前那样。通过jni从native调java的函数,或者从java调native的函数。由于原来java方法的上下文已经被改变了(已经被保存在hookInfo中),所以后面仅仅能通过libdvm中的方法,手动改动函数的指向。

    二是dvmCallMethod的第5和第6个參数originalReflected和original就是第二部分中保存的方法的引用和方法的字节码地址(original被直接转成了int型)。后面第五部分中这两项还会被又一次传回native层用来找到原java函数的入口。


    四、handleHookedMethod

    前面说到从native中调回java的方法handleHookedMethod。handleHookedMethod会依据须要,选择是否还调用原来的java方法,或者仅仅调用XC_MethodReplacement里自己实现的before、after方法。

    当中beforeHookedMethod方法默认会调用replaceHookedMethod,我们仅仅要实现它就可以替代对原方法的调用。

     

    假设param.returnEarly为false才会调invokeOriginalMethodNative运行原来的方法。

    默认的beforeHookedMethod中会调setParam,把param.returnEarly的值设为为true。这样就不会再调用原来的java方法了。


    最后还要把返回值返回。

     

     

    五、invokeOriginalMethodNative

    假设在java层须要又一次调用原java函数。那么在第二部分中把原java函数的信息备份到hookInfo中就能起到作用了。Java层的invokeOriginalMethod方法会调一个native的方法invokeOriginalMethodNative来实现这个过程。

    这个native函数相同在初始化时就被设置好了:

    要调用的invokeOriginalMethodNative在虚拟机中Method是dexposedInvokeOriginalMethod,这里传入了第二部分中备份的原java方法的对象引用reflectedMethod和字节码地址int型的original。

    dvmSetNativeFunc的第2个參数是DalvikBridgeFunc类型的指针,这个函数会把dexposedInvokeOriginalMethod的nativeFunc指向xxx_invokeOriginalMethodNative。

    再次注意此时不能像寻常的jni调用那样。java层的invokeOriginalMethodNative经过jni注冊后能直接调到com_taobao_android_dexposed_DexposedBridge_invokeOriginalMethodNative了。

    dvmInvokeMethod跟dvmCallMethod一样。都是dalvik中用来直接调Method的函数,这样就完毕了对原java方法的调用。

     

    最后一句话概括这样的hook方法,就是通过把原java方法的类型改为native来把对java函数的调用转到native层。在native层用dvm的各种函数来操作Method的指针和对象来控制函数流程。



      百分百原创,每周两篇,阿里、魅族、nvidia、龙芯、炬力、拓尔思等顶级企业资深project师分享----嵌入式、Linux、物联网、GPU、Android、自己主动驾驶等技术,欢迎扫码关注微信公众号:嵌入式企鹅圈,实时推送原创文章!


  • 相关阅读:
    Android仿人人客户端(v5.7.1)——个人主页(三)
    hdu2554-N对数的排列问题
    POJ1363:Rails
    golang printf
    HDU1200:To and Fro
    [C# 基础知识系列]专题六:泛型基础篇——为什么引入泛型
    poj 2480 (欧拉函数应用)
    Re:从0开始的微服务架构--(二)快速快速体验微服务架构?--转
    爬虫推荐的工具
    python2 与 python3 语法区别--转
  • 原文地址:https://www.cnblogs.com/claireyuancy/p/7213714.html
Copyright © 2020-2023  润新知