基于cydia Hook在线热修复补丁方案
最近的在線熱補(bǔ)丁修復(fù)的討論相當(dāng)激烈,從Xopsed到Dexposed,再到AndFix,再到QQ空間團(tuán)隊的Class補(bǔ)丁。可謂是各有特色。本文討論的是基于Cydia Hook實(shí)現(xiàn)的在線Class熱補(bǔ)丁。相對于Xopsed、Dexposed、AndFix這三種都是替換Java方法,和QQ空間class替換而言,優(yōu)勢明顯。前者是替換方法,但是如果所替換的方法中遇到calss中的成員變量,就要通過反射得到,實(shí)現(xiàn)補(bǔ)丁方案。這相對于Class補(bǔ)丁來說解決性能問題,但是使用起來比較麻煩。而QQ空間團(tuán)隊使用的是替換整個Class,這就沒有成員變量的問題。但是QQ空間的方案卻犧牲了性能,主要是通過防止class打上CLASS_ISPREVERIFIED。本文所討論的方案是基于兩者實(shí)現(xiàn)的,沒有成員變量反射得到麻煩,也沒有防止class打上CLASS_ISPREVERIFIED犧牲性能的問題。那這是怎么實(shí)現(xiàn)的呢?請看下文:
我采用的是MultiDex方案實(shí)現(xiàn)的,這部分和QQ空間類似就是講補(bǔ)丁dex文件,放在其他dex(包括主dex),這是因?yàn)橐粋€ClassLoader可以包含多個dex文件,每個dex文件是一個Element,多個dex文件排列成一個有序的數(shù)組dexElements,當(dāng)找類的時候,會按順序遍歷dex文件,然后從當(dāng)前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個dex文件繼續(xù)查找。具體代碼:
public?Class?findClass(String name,?List<Throwable>suppressed){
????for?(Elementelement : dexElements) {??//每個Element就是一個dex文件
????????DexFile dex = element.dexFile;
??????? if?(dex !=?null) {
??????????? Class clazz=dex.loadClassBinaryName(name,?definingContext,?suppressed);
??????????? if?(clazz!=?null) {
????????????????return?clazz;
????????????}
??????? }
??? }
????if?(dexElementsSuppressedExceptions!=?null){
???????suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
????}
????return null;
}
?
我的方案中采用的是MultiDex,對其進(jìn)行一部分改造,具體代碼:
1、添加dex文件,并執(zhí)行install
/**
* 添加apk包外的dex文件
* 自動執(zhí)行install
* @param dexFile
*/
public static void addDexFileAutoInstall(Context context, List<File> dexFile,File optimizedDirectory) {
? ? if (dexFile != null && !dexFile.isEmpty() &&!dexFiles.contains(dexFile)) {
? ? ? ? ?dexFiles.addAll(dexFile);
? ? ? ? ?LogUtil.d(TAG, "add other dexfile");
? ? ? ? ?installDexFile(context,optimizedDirectory);
? ? ?}
}
2、installDexFile直接調(diào)用MultiDex 的installSecondaryDexes方法。
/**
?*?添加apk包外的dex文件,
?*?@param?context
?*/
publicstatic void?installDexFile(Context context,?File optimizedDirectory){
????if?(checkValidZipFiles(dexFiles)) {
????????try?{
????????????installSecondaryDexes(context.getClassLoader(),?optimizedDirectory,?dexFiles);
????????}?catch?(IllegalAccessExceptione){
???????????e.printStackTrace();
????????}?catch?(NoSuchFieldExceptione) {
???????????e.printStackTrace();
???? ???}?catch?(InvocationTargetExceptione){
???????????e.printStackTrace();
????????}?catch?(NoSuchMethodExceptione) {
???????????e.printStackTrace();
????????}?catch?(IOExceptione) {
???????????e.printStackTrace();
????????}
??? }
}
3、將patch.dex放在所有dex最前面。
private static voidexpandFieldArray(Object instance, String fieldName,?Object[]extraElements) throws NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
? ? ? ? ? ? ? ? ? ??Field jlrField = findField(instance, fieldName);
? ? ? ? ? ? ? ? ? ? Object[]original = (Object[]) jlrField.get(instance);
? ? ? ? ? ? ? ? ? ? Object[]combined = (Object[]) Array.newInstance(original.getClass().getComponentType(),original.length + extraElements.length);
? ? ? ? ? ? ? ? ? ? // 將后來的dex放在前面,主dex放在最后。
? ? ? ? ? ? ? ? ??System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
? ? ? ? ? ? ? ? ??System.arraycopy(original, 0, combined, extraElements.length,original.length);
? ? ? ? ? ? ? ? ? ?// 原始的dex合并,是將主dex放在前面,其他的dex依次放在后面。
? ? ? ? ? ? ? ? ? ?//System.arraycopy(original, 0, combined, 0, original.length);
? ? ? ? ? ? ? ? ? ?//System.arraycopy(extraElements, 0, combined, original.length,extraElements.length);
? ? ? ? ? ? ? ? ??jlrField.set(instance, combined);
}
到此將patch.dex放進(jìn)了Element,接下來的問題就是加載Class,當(dāng)加載patch.dex中類的時候,會遇到一個問題,這個問題就是QQ空間團(tuán)隊遇到,Classd的CLASS_ISPREVERIFIED。具體原因是dvmResolveClass這個方法對Class進(jìn)行了校驗(yàn)。判斷這個要Resolve的class是否和其引用來自一個dex。如果不是,就會遇到問題。
?
?
當(dāng)引用這和被引用者不在同一個dex中就會拋出異常,導(dǎo)致Resolve失敗。QQ空間團(tuán)隊的方案是阻止所有的Class類打上CLASS_ISPREVERIFIED來逃過校驗(yàn),這種方式其實(shí)是影響性能。
我們的方案是和QQ團(tuán)隊的類似,但是和QQ空間不同的是,我們將fromUnverifiedConstant設(shè)置為true,來逃過校驗(yàn),達(dá)到補(bǔ)丁的路徑。具體怎么實(shí)現(xiàn)呢?
要引用Cydia Hook技術(shù)來hook Native dalvik中dvmResolveClass這個方法。有關(guān)Cydia Hook技術(shù)請參考:
官網(wǎng)地址:http://www.cydiasubstrate.com/
官方教程:http://www.cydiasubstrate.com/id/38be592b-bda7-4dd2-b049-cec44ef7a73b
SDK下載地址:http://asdk.cydiasubstrate.com/zips/cydia_substrate-r2.zip
具體代碼如下。
//指明要hook的lib :
MSConfig(MSFilterLibrary,"/system/lib/libdvm.so")
?
//?在初始化的時候進(jìn)行hook
MSInitialize {
??? LOGD("Cydia Init");
????MSImageRef image;
????//載入lib
????image = MSGetImageByName("/system/lib/libdvm.so");
????if?(image != NULL) {
??????? LOGD("image is not null");
????????void?*dexload=MSFindSymbol(image,"dvmResolveClass");
????????if(dexload != NULL) {
??????????? LOGD("dexloadis not null");
????????????MSHookFunction(dexload,?(void*)proxyDvmResolveClass,?(void**)&dvmResolveClass_Proxy);
????????}?else{
??????????? LOGD("errorfind dvmResolveClass");
????????}
??? }
}
//?在初始化的時候進(jìn)行hook//保留原來的地址
ClassObject* (*dvmResolveClass_Proxy)(ClassObject* referrer, u4 classIdx, boolfromUnverifiedConstant);
// 新方法地址
static ClassObject* proxyDvmResolveClass(ClassObject* referrer, u4 classIdx,bool fromUnverifiedConstant) {
? ? ? ? return dvmResolveClass_Proxy(referrer, classIdx,true);
}
有人可能會擔(dān)心cydia?Hook性能,穩(wěn)定性問題,但是據(jù)我所知,目前有些公司已經(jīng)用它來實(shí)現(xiàn)apk加殼和脫殼防止反編譯技術(shù)方案。具體可以參考http://www.gitzx.com/android-cydiasubstrate/
到這里為止在線熱補(bǔ)丁修補(bǔ)方案就完了,由于本人技術(shù)原因,可能有些東西沒有講清楚,或者有什么紕漏,請告知。最后,這個方案我近期會整理一下開源出來。
github代碼:https://github.com/Jarlene/ClassPatch.git
總結(jié)
以上是生活随笔為你收集整理的基于cydia Hook在线热修复补丁方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux向日葵同步剪贴板,向日葵Win
- 下一篇: DIY自行轻松解决耳机缠绕问题