1. 程式人生 > >Android熱補丁技術—dexposed原理簡析(阿里Hao)

Android熱補丁技術—dexposed原理簡析(阿里Hao)

本文由嵌入式企鵝圈原創團隊成員、阿里資深工程師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、龍芯、炬力、拓爾思等頂級企業資深工程師分享----嵌入式、Linux、物聯網、GPU、Android、自動駕駛等技術,歡迎掃碼關注微信公眾號:嵌入式企鵝圈,實時推送原創文章!