Xposed注入实现分析及免重启定制
Xposed的實現解析
Xposed簡介
Xposed已經作為Android的Java層Hook大哥好多年了,不僅僅是安全研究者和逆向工程師手中的神器,還擁有著很多的插件開發者,在官方倉庫收錄的插件也已經超過了1000+個,而其服務的用戶以各搞機論壇用戶為主,也是不盡其數。
Xposed的代碼是完全開源的,并且代碼量也不算很大,對于安全研究者來說呢,我覺得研究一下Xposed的原理還是很有必要的。
Xposed的github地址:
https://github.com/rovo89/Xposed
https://github.com/rovo89/XposedBridge
本文使用Android4.4的源碼進行分析
如何實現全局注入
簡單的講,Xposed是通過替換修改過的app_process來注入Zygote以實現的全局注入。
Android的啟動過程
想要知道Xposed是如何實現的全局注入,首先要分析一下Android系統的啟動過程,就可以很方便的理解Xposed選擇的注入點。
我畫了張圖,Android系統的啟動大致如下:
從圖中可以很方便的理解從BootLoader到顯示系統界面的過程。
應用孵化器 - Zygote
Zygote介紹
Zygote,中文是"受精卵"...實際上他就是一個孵化器,所有應用程序的進程都是由它fork出來的。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | USER???? PID?? PPID? VSIZE? RSS???? WCHAN??? PC???????? NAME root??????155???1?????509740?41876?ffffffff b75761e0 S zygote drm???????156???1?????18508??4064??ffffffff b7599ff6 S?/system/bin/drmserver media?????157???1?????69296??15656?ffffffff b755bff6 S?/system/bin/mediaserver install???158???1?????6580???1228??c04d1048 b7507bb6 S?/system/bin/installd keystore??159???1?????10172??2088??c03f4a25 b7561ff6 S?/system/bin/keystore root??????160???1?????1096???4?????c044881c?080ddd03?S?/system/xbin/su system????187???1?????81040??3860??ffffffff b75b0ff6 S?/system/bin/surfaceflinger root??????343???2?????0??????0?????c01cfbd5?00000000?S flush-8:16 root??????414???62????6732???1412??c02f4452 b74c6bb6 S?/system/bin/sh root??????418???62????6688???1464??c01c0a90 b752e1e0 S logcat system????424???155???629276?46384?ffffffff b7575ff6 S system_server u0_a44????506???155???564252?75032?ffffffff b75779eb S com.android.systemui u0_a0?????551???155???525484?25696?ffffffff b75779eb S android.process.acore wifi??????565???1?????10776??2912??c01c0a90 b741d1e0 S?/system/bin/wpa_supplicant system????566???155???526468?21732?ffffffff b75779eb S com.android.settings u0_a22????606???155???521276?24932?ffffffff b75779eb S com.android.inputmethod.latin radio?????623???155???539356?28428?ffffffff b75779eb S com.android.phone u0_a23????637???155???557680?45252?ffffffff b75779eb S com.android.launcher u0_a29????673???155???520300?20324?ffffffff b75779eb S com.android.music u0_a16????693???155???521532?25876?ffffffff b75779eb S android.process.media u0_a49????719???155???517580?18600?ffffffff b75779eb S com.android.smspush u0_a15????806???155???521632?19920?ffffffff b75779eb S com.android.dialer bluetooth?821???155???522828?22284?ffffffff b75779eb S com.android.bluetooth dhcp??????845???1?????6656???1452??c01c0a90 b74e6ab6 S?/system/bin/dhcpcd u0_a6?????916???155???527828?22036?ffffffff b75779eb S com.android.calendar u0_a7?????943???155???519636?23176?ffffffff b75779eb S com.android.providers.calendar u0_a12????967???155???521156?21960?ffffffff b75779eb S com.android.deskclock u0_a17????1019??155???528300?24912?ffffffff b75779eb S com.android.email u0_a18????1078??155???524976?20436?ffffffff b75779eb S com.android.exchange u0_a28????1168??155???530852?23100?ffffffff b75779eb S com.android.mms u0_a32????1215??155???517576?19140?ffffffff b75779eb S com.android.onetimeinitializer u0_a47????1251??155???517732?19072?ffffffff b75779eb S com.android.voicedialer |
可以看到,這些包名格式的進程的PPID(父進程PID)都是155,即zygote進程。
Zygote啟動之后會建立一個Socket Server,然后fork自身成為一個新的進程——system_server,并讓它成為"前端代言人",當system_server接收到打開進程的請求時,它就會通過Socket跟Zygote通訊,Zygote就再fork自身稱為新的進程。
Zygote的啟動
在init.rc中可以看到,Zygote是通過app_process啟動的,
| 1 | service zygote?/system/bin/app_process?-Xzygote?/system/bin?--zygote?--start-system-server |
這時app_process的任務也很簡單,就是把自己的進程名變成Zygote,然后反射調用Zygote的主方法,即com.android.internal.os.ZygoteInit的main()。之后Zygote做的事情就是在介紹中說過的了。
Hook Zygote
那么Xposed就是通過修改app_process的方式來實現注入Zygote的。
項目地址:https://github.com/rovo89/Xposed 即是修改app_process的源碼,在app_main.cpp的main函數中可以看到
| 1 2 3 4 5 6 7 8 9 10 11 12 | isXposedLoaded?=?xposed::initialize(zygote, startSystemServer, className, argc, argv); if?(zygote) { ????runtime.start(isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE :?"com.android.internal.os.ZygoteInit", ????????????startSystemServer ??"start-system-server"?: ""); }?else?if?(className) { ????//?Remainder of args get passed to startup?class?main() ????runtime.mClassName?=?className; ????runtime.mArgC?=?argc?-?i; ????runtime.mArgV?=?argv?+?i; ????runtime.start(isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS :?"com.android.internal.os.RuntimeInit", ????????????application ??"application"?:?"tool"); } |
可以看到,Xposed首先嘗試加載自己的庫,如果成功的話,就將runtime.start的類名替換成自己的類:de.robv.android.xposed.XposedBridge,因為app_processs是采用反射main()的方法來調用加載方法,所以只要在這個類中定義一個main()方法,就可以為所欲為了,最后只需在末尾調用一下com.android.internal.os.ZygoteInit的main()讓系統繼續跑下去就可以實現對Zygote的Hook。
注入應用進程
既然已經拿到了Zygote的所有權了,但是現在Application還沒跑起來呢,如果這個時候就執行Hook模塊,肯定是無法Hook到相關代碼的。那么這時候就有必要提一下從Zygote接受到請求之后做了什么,我又簡單的畫了張圖:
值得注意的是,因為fork之后的子進程是會繼承父進程的狀態繼續往下跑,所以到handleChildProc的時候,此時已經是子進程在執行程序了,而ActivityThread也是在新進程中loop。
在Activity的loop中,可以接收的消息也包括了Appcalition的生命周期BIND_APPLICATION和EXIT_APPLICATION。處理BIND_APPLICATION的時候是直接調用了handleBindApplication方法,Xposed便是在Zygote中Hook了這個方法,在這里面執行用戶的Hook模塊,因為在這個時候,已經可以拿到應用的classLoader了,然后將此classLoader封裝成一個LoadPackageParam再傳給各個模塊的handleLoadPackage即可。
在XposedBridge的main中可看到其調用了initForZygote()
| 1 2 3 4 5 6 7 8 | protected static void main(String[] args) { ????... ????????if?(isZygote) { ????????????XposedInit.hookResources(); ????????????XposedInit.initForZygote(); ????????} ????... } |
initForZygote中又Hook了handleBindApplication
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | static void initForZygote() throws Throwable { ????... ????findAndHookMethod(ActivityThread.class,?"handleBindApplication",?"android.app.ActivityThread.AppBindData", new XC_MethodHook() { ????????@Override ????????protected void beforeHookedMethod(MethodHookParam param) throws Throwable { ????????????ActivityThread activityThread?=?(ActivityThread) param.thisObject; ????????????ApplicationInfo appInfo?=?(ApplicationInfo) getObjectField(param.args[0],?"appInfo"); ????????????String reportedPackageName?=?appInfo.packageName.equals("android") ??"system"?: appInfo.packageName; ????????????SELinuxHelper.initForProcess(reportedPackageName); ????????????ComponentName instrumentationName?=?(ComponentName) getObjectField(param.args[0],?"instrumentationName"); ????????????if?(instrumentationName !=?null) { ????????????????Log.w(TAG,?"Instrumentation detected, disabling framework for "?+?reportedPackageName); ????????????????XposedBridge.disableHooks?=?true; ????????????????return; ????????????} ????????????CompatibilityInfo compatInfo?=?(CompatibilityInfo) getObjectField(param.args[0],?"compatInfo"); ????????????if?(appInfo.sourceDir?==?null) ????????????????return; ????????????setObjectField(activityThread,?"mBoundApplication", param.args[0]); ????????????loadedPackagesInProcess.add(reportedPackageName); ????????????LoadedApk loadedApk?=?activityThread.getPackageInfoNoCheck(appInfo, compatInfo); ????????????XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir()); ????????????XC_LoadPackage.LoadPackageParam lpparam?=?new XC_LoadPackage.LoadPackageParam(XposedBridge.sLoadedPackageCallbacks); ????????????lpparam.packageName?=?reportedPackageName; ????????????lpparam.processName?=?(String) getObjectField(param.args[0],?"processName"); ????????????lpparam.classLoader?=?loadedApk.getClassLoader(); ????????????lpparam.appInfo?=?appInfo; ????????????lpparam.isFirstApplication?=?true; ????????????XC_LoadPackage.callAll(lpparam); ????????????if?(reportedPackageName.equals(INSTALLER_PACKAGE_NAME)) ????????????????hookXposedInstaller(lpparam.classLoader); ????????} ????}); ????... } |
Xposed在Zygote進程中執行完這些之后,每次fork出來的都是handleBindApplication已經被Hook過的進程,所以當每個應用進程被打開的時候,都會最開始就先執行Xposed的模塊插件,然后才開始執行本身的代碼。這樣就實現了注入到每個應用程序來進行Hook。
如何實現Hook
findAndHookMethod
findAndHookMethod想必是大家寫Xposed插件最常用及最熟悉的一個方法,我們就通過這個方法來分析Xposed是如何實現的Java Hook。
| 1 2 3 4 5 6 7 8 9 | public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName,?Object... parameterTypesAndCallback) { ????if?(parameterTypesAndCallback.length?==?0?|| !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook)) ????????throw new IllegalArgumentException("no callback defined"); ????XC_MethodHook callback?=?(XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1]; ????Method m?=?findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback)); ????return?XposedBridge.hookMethod(m, callback); } |
大家知道這個方法的參數是個可變長參數列表,然后它會取最后一個參數來作為callback,即hook時的注入的代碼。然后通過反射的方法來得到原本的Method。最后將這兩個傳給hookMethod。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) { ????... ????if?(newMethod) { ????????Class<?> declaringClass?=?hookMethod.getDeclaringClass(); ????????int?slot; ????????Class<?>[] parameterTypes; ????????Class<?> returnType; ????????if?(runtime?==?RUNTIME_ART) { ????????????slot?=?0; ????????????parameterTypes?=?null; ????????????returnType?=?null; ????????}?else?if?(hookMethod instanceof Method) { ????????????slot?=?getIntField(hookMethod,?"slot"); ????????????parameterTypes?=?((Method) hookMethod).getParameterTypes(); ????????????returnType?=?((Method) hookMethod).getReturnType(); ????????}?else?{ ????????????slot?=?getIntField(hookMethod,?"slot"); ????????????parameterTypes?=?((Constructor<?>) hookMethod).getParameterTypes(); ????????????returnType?=?null; ????????} ????????AdditionalHookInfo additionalInfo?=?new AdditionalHookInfo(callbacks, parameterTypes, returnType); ????????hookMethodNative(hookMethod, declaringClass, slot, additionalInfo); ????} ????... } |
可以看到hookMethod實際上時調用了一個native函數hookMethodNative,其他參數都很好理解,但是取的這個slot是個什么東西?翻了一下百度,在Dalvik的源碼中dalvik/vm/reflect.c:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | static?int?methodToSlot(const Method*?meth) { ????ClassObject*?clazz?=?meth->clazz; ????int?slot; ????if?(dvmIsDirectMethod(meth)) { ????????slot?=?meth?-?clazz->directMethods; ????????slot?=?-(slot+1); ????}?else?{ ????????slot?=?meth?-?clazz->virtualMethods; ????} ????return?slot; } |
原來這個slot就是此method在ClassObject的methods中的偏移,directMethods為負,virtualMethods為正。
偷梁換柱
hookMethodNative對應C函數XposedBridge_hookMethodNative
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | void XposedBridge_hookMethodNative(JNIEnv*?env, jclass clazz, jobject reflectedMethodIndirect, ????????????jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) { ????//?Usage errors? ????if?(declaredClassIndirect?==?NULL || reflectedMethodIndirect?==?NULL) { ????????dvmThrowIllegalArgumentException("method and declaredClass must not be null"); ????????return; ????} ????//?Find the internal representation of the method ????ClassObject*?declaredClass?=?(ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect); ????Method*?method?=?dvmSlotToMethod(declaredClass, slot); ????if?(method?==?NULL) { ????????dvmThrowNoSuchMethodError("Could not get internal representation for method"); ????????return; ????} ????if?(isMethodHooked(method)) { ????????//?already hooked ????????return; ????} ????//?Save a copy of the original method?and?other hook info ????XposedHookInfo*?hookInfo?=?(XposedHookInfo*) calloc(1, sizeof(XposedHookInfo)); ????memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct)); ????hookInfo->reflectedMethod?=?dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect)); ????hookInfo->additionalInfo?=?dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect)); ????//?Replace method with our own code ????SET_METHOD_FLAG(method, ACC_NATIVE); ????method->nativeFunc?=?&hookedMethodCallback; ????method->insns?=?(const u2*) hookInfo; ????method->registersSize?=?method->insSize; ????method->outsSize?=?0; ????if?(PTR_gDvmJit !=?NULL) { ????????//?reset JIT cache ????????char currentValue?=?*((char*)PTR_gDvmJit?+?MEMBER_OFFSET_VAR(DvmJitGlobals,codeCacheFull)); ????????if?(currentValue?==?0?|| currentValue?==?1) { ????????????MEMBER_VAL(PTR_gDvmJit, DvmJitGlobals, codeCacheFull)?=?true; ????????}?else?{ ????????????ALOGE("Unexpected current value for codeCacheFull: %d", currentValue); ????????} ????} } |
首先是通過slot獲取到Method對象在內存中的Method結構體指針,然后將它設置為一個native方法,再將他的nativeFunc即執行時對應的函數地址設置為hookedMethodCallback的指針,將保存著原method信息的hookInfo暫時存放在本是dalvik字節碼的insns,reflectedMethod是原method的java對象,additionalInfo是AdditionalHookInfo對象。
然后當調用到這個方法的時候,執行的就變成了hookedMethodCallback函數了。
| 1 2 3 4 5 6 | void hookedMethodCallback(const u4*?args, JValue*?pResult, const Method*?method, ::Thread*?self) { ????... ????dvmCallMethod(self, (Method*) methodXposedBridgeHandleHookedMethod, NULL, &result, ????????originalReflected, (int) original, additionalInfo, thisObject, argsArray); ????... } |
主要就是又調了一個java方法methodXposedBridgeHandleHookedMethod,解釋一下各個參數,originalReflected就是上面hookInfo的reflectedMethod,original是原method結構體指針,后面的不必解釋了吧。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | private static?Object?handleHookedMethod(Member method,?int?originalMethodId,?Object?additionalInfoObj, ????????????Object?thisObject,?Object[] args) throws Throwable { ????????... ????????int?beforeIdx?=?0; ????????do { ????????????try?{ ????????????????((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param); ????????????} catch (Throwable t) { ????????????????XposedBridge.log(t); ????????????????//?reset result (ignoring what the unexpectedly exiting callback did) ????????????????param.setResult(null); ????????????????param.returnEarly?=?false; ????????????????continue; ????????????} ????????????if?(param.returnEarly) { ????????????????//?skip remaining?"before"?callbacks?and?corresponding?"after"?callbacks ????????????????beforeIdx++; ????????????????break; ????????????} ????????}?while?(++beforeIdx < callbacksLength); ????????//?call original method?if?not?requested otherwise ????????if?(!param.returnEarly) { ????????????try?{ ????????????????param.setResult(invokeOriginalMethodNative(method, originalMethodId, ????????????????????????additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args)); ????????????} catch (InvocationTargetException e) { ????????????????param.setThrowable(e.getCause()); ????????????} ????????} ????????//?call?"after method"?callbacks ????????int?afterIdx?=?beforeIdx?-?1; ????????do { ????????????Object?lastResult?=??param.getResult(); ????????????Throwable lastThrowable?=?param.getThrowable(); ????????????try?{ ????????????????((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param); ????????????} catch (Throwable t) { ????????????????XposedBridge.log(t); ????????????????//?reset to last result (ignoring what the unexpectedly exiting callback did) ????????????????if?(lastThrowable?==?null) ????????????????????param.setResult(lastResult); ????????????????else ????????????????????param.setThrowable(lastThrowable); ????????????} ????????}?while?(--afterIdx >=?0); ????... } |
那么methodXposedBridgeHandleHookedMethod做的事情也很好理解,就是先把所有的beforeHookedMethod給調用一遍,然后再還原原來的method調用一遍,最后再把所有的afterHookMethod的給調用一遍,就完成了。
還原執行
執行原來的方法調用的是native方法invokeOriginalMethodNative。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void XposedBridge_invokeOriginalMethodNative(const u4*?args, JValue*?pResult, ????????????const Method*?method, ::Thread*?self) { ????Method*?meth?=?(Method*) args[1]; ????if?(meth?==?NULL) { ????????meth?=?dvmGetMethodFromReflectObj((Object*) args[0]); ????????if?(isMethodHooked(meth)) { ????????????meth?=?(Method*) meth->insns; ????????} ????} ????ArrayObject*?params?=?(ArrayObject*) args[2]; ????ClassObject*?returnType?=?(ClassObject*) args[3]; ????Object*?thisObject?=?(Object*) args[4];?//?null?for?static methods ????ArrayObject*?argList?=?(ArrayObject*) args[5]; ????//?invoke the method ????pResult->l?=?dvmInvokeMethod(thisObject, meth, argList, params, returnType, true); ????return; } |
emm..就是通過拿到備份的method結構體,然后直接調用dvmInvokeMethod即可。
改造更新免重啟的Xposed
大家在寫Xposed模塊的時候是不是有一個很不爽的地方,就是每次改兩行代碼之后又得重啟,非常的煩,那么清楚了注入的原理之后,發現這點好像不是必須的!只是rovo89可能是考慮到性能的問題,所以才采用這種方案。實際上,只要輕輕的改動兩行代碼,就可以實現免重啟更新!
Xposed原加載機制
Xposed原本是在app_process的時候一次性將模塊列表讀取,Load之后將其hook類放到一個Set里面,在de.robv.android.xposed.XposedBridge的main()中可以看到
| 1 2 3 4 5 6 7 8 9 10 | protected static void main(String[] args) { ????... ????????if?(isZygote) { ????????????XposedInit.hookResources(); ????????????XposedInit.initForZygote(); ????????} ????????XposedInit.loadModules(); ????... } |
待到應用啟動的時候再分別調用每個handleLoadPackage,之后就不再讀取插件。因為這樣,所以每次fork之后,插件已經被加載在內存中了,就算更新插件也不會被讀取加載,所以必須得重啟才能使得插件生效。
處理辦法
之前說到,Xposed通過HookhandleBindApplication來進入每個應用進程,那只要在這個時候再loadModules,那不就ojbk了嗎?
在loadModule中,進行加載不僅有handleLoadPackage,還有hook資源及hook命令行程序的包,
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private static void loadModule(String apk, ClassLoader topClassLoader) { ????????... ????????????????????????if?(moduleInstance instanceof IXposedHookZygoteInit) { ????????????????????????????IXposedHookZygoteInit.StartupParam param?=?new IXposedHookZygoteInit.StartupParam(); ????????????????????????????param.modulePath?=?apk; ????????????????????????????param.startsSystemServer?=?startsSystemServer; ????????????????????????????((IXposedHookZygoteInit) moduleInstance).initZygote(param); ????????????????????????} ????????????????????????if?(moduleInstance instanceof IXposedHookLoadPackage) ????????????????????????????XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); ????????????????????????if?(moduleInstance instanceof IXposedHookInitPackageResources) ????????????????????????????XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance)); ????????????????????}?else?{ ????????????????????????if?(moduleInstance instanceof IXposedHookCmdInit) { ????????????????????????????IXposedHookCmdInit.StartupParam param?=?new IXposedHookCmdInit.StartupParam(); ????????????????????????????param.modulePath?=?apk; ????????????????????????????param.startClassName?=?startClassName; ????????????????????????????((IXposedHookCmdInit) moduleInstance).initCmdApp(param); ????????... ????} |
但是我的需求就是只需要處理handleLoadPackage就可以了,所以給這個參數加個開關isLoadHookLoadPackage,我的代碼如下
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | if?(XposedBridge.isZygote) { ????????if?(moduleInstance instanceof IXposedHookZygoteInit) { ????????????IXposedHookZygoteInit.StartupParam param?=?new IXposedHookZygoteInit.StartupParam(); ????????????param.modulePath?=?apk; ????????????param.startsSystemServer?=?startsSystemServer; ????????????((IXposedHookZygoteInit) moduleInstance).initZygote(param); ????????} ????????if?(moduleInstance instanceof IXposedHookLoadPackage) ????????????if(isLoadHookLoadPackage) ????????????{ ????????????????XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); ????????????} ????????if?(moduleInstance instanceof IXposedHookInitPackageResources) ????????????XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance)); ????}?else?{ ????????if?(moduleInstance instanceof IXposedHookCmdInit) { ????????????IXposedHookCmdInit.StartupParam param?=?new IXposedHookCmdInit.StartupParam(); ????????????param.modulePath?=?apk; ????????????param.startClassName?=?startClassName; ????????????((IXposedHookCmdInit) moduleInstance).initCmdApp(param); ????????} ????} |
實際上就是簡單的加了一個if而已,然后在main的地方改成
| 1 | XposedInit.loadModules(false); |
最后就是在hook?handleBindApplication的地方加上XposedInit.loadModules(true);
| 1 2 3 4 5 | findAndHookMethod(ActivityThread.class,?"handleBindApplication",?"android.app.ActivityThread.AppBindData", new XC_MethodHook() { ????@Override ????protected void beforeHookedMethod(MethodHookParam param) throws Throwable { ????????XposedInit.loadModules(true); ... |
嗯,炒雞簡單,改源碼不超過2分鐘。
編譯
改源碼兩分鐘,編譯五小時..
rovo89還提供了另外一個項目:https://github.com/rovo89/XposedTools
因為Xposed編譯需要依賴AOSP來進行編譯,這個工具就是專門用來編譯Xposed源碼的,使用方法也很簡單,填好build.conf然后一把梭就可以了,具體的可以查看百度。
需要注意的是,上敘的源碼是新版的xposed,就是支持5.0+art的,而我使用的是4.4.4的源碼,模擬器也是4.4,所以clone源碼的時候要-b master來切換分支。然后修改的代碼也大同小異,就是loadmodule多了個startClassName參數,可以通過getStartClassName()方法獲取,就是這樣:
XposedBridge.loadModules(getStartClassName(),true);
因為這個老工程是個Eclipse工程,XposedTool是依賴于Gradle,所以無法使用XposedTool編譯。直接使用Android Studio - Build Apk最后把文件名改成XposedBridge.jar即可,
最后你可以選擇直接修改Xposed Installer將其中的XposedBridge.jar替換再安裝,或者在手機上安裝好Xposed,然后將修改好的XposedBridge.jar替換/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar,重啟,就可以愉快的調插件玩耍了。
順便fork了一份,并把代碼上傳了,雖說總共也沒改幾行代碼,2333。。
5.0- : https://github.com/hluwa/XposedBridge/tree/master
4.4+ : https://github.com/hluwa/XposedBridge/tree/art
原文地址:https://bbs.pediy.com/thread-223713.htm
總結
以上是生活随笔為你收集整理的Xposed注入实现分析及免重启定制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 全能Android HOOK框架 JNI
- 下一篇: 根据”so劫持”过360加固详细分析