XPosed解析--XposedBridge--main分析
XposedBridge是Xposed框架替代ZygoteInit的文件,其中main方式是其入口,分析main方法可以更好的理解Xposed的运行模式,下面就来分析一下此函数。
private static void main(String[] args) { // the class the VM has been created for or null for the Zygote process String startClassName = getStartClassName(); // initialize the Xposed framework and modules try { // initialize log file try { logFile = new File(BASE_DIR + "log/error.log"); if (startClassName == null && logFile.length() > MAX_LOGFILE_SIZE_SOFT) logFile.renameTo(new File(BASE_DIR + "log/error.log.old")); logWriter = new PrintWriter(new FileWriter(logFile, true)); logFile.setReadable(true, false); logFile.setWritable(true, false); } catch (IOException ignored) {} String date = DateFormat.getDateTimeInstance().format(new Date()); determineXposedVersion(); log("-----------------\n" + date + " UTC\n" + "Loading Xposed v" + XPOSED_BRIDGE_VERSION + " (for " + (startClassName == null ? "Zygote" : startClassName) + ")..."); if (startClassName == null) { // Zygote log("Running ROM '" + Build.DISPLAY + "' with fingerprint '" + Build.FINGERPRINT + "'"); } if (initNative()) { if (startClassName == null) { // Initializations for Zygote initXbridgeZygote(); } loadModules(startClassName); } else { log("Errors during native Xposed initialization"); } } catch (Throwable t) { log("Errors during Xposed initialization"); log(t); disableHooks = true; } // call the original startup code if (startClassName == null) ZygoteInit.main(args); else RuntimeInit.main(args); }
initNative()函数对一些JNI函数的注册和回调方法的注册,JNI层对应的方法为XposedBridge_initNative,此方法后续会进行分析。
initXbridgeZygote();对Zygote进行初始化,深入探讨一下此函数,由于比较复杂,耐心跟踪一下
private static void initXbridgeZygote() throws Throwable { final HashSet<String> loadedPackagesInProcess = new HashSet<String>(1); // normal process initialization (for new Activity, Service, BroadcastReceiver etc.) findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() { protected void beforeHookedMethod(MethodHookParam param) throws Throwable { ActivityThread activityThread = (ActivityThread) param.thisObject; ApplicationInfo appInfo = (ApplicationInfo) getObjectField(param.args[0], "appInfo"); ComponentName instrumentationName = (ComponentName) getObjectField(param.args[0], "instrumentationName"); if (instrumentationName != null) { XposedBridge.log("Instrumentation detected, disabling framework for " + appInfo.packageName); disableHooks = true; return; } CompatibilityInfo compatInfo = (CompatibilityInfo) getObjectField(param.args[0], "compatInfo"); if (appInfo.sourceDir == null) return; setObjectField(activityThread, "mBoundApplication", param.args[0]); loadedPackagesInProcess.add(appInfo.packageName); LoadedApk loadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo); XResources.setPackageNameForResDir(appInfo.packageName, loadedApk.getResDir()); LoadPackageParam lpparam = new LoadPackageParam(sLoadedPackageCallbacks); lpparam.packageName = appInfo.packageName; lpparam.processName = (String) getObjectField(param.args[0], "processName"); lpparam.classLoader = loadedApk.getClassLoader(); lpparam.appInfo = appInfo; lpparam.isFirstApplication = true; XC_LoadPackage.callAll(lpparam); if (appInfo.packageName.equals(INSTALLER_PACKAGE_NAME)) hookXposedInstaller(lpparam.classLoader); } }); // system thread initialization findAndHookMethod("com.android.server.ServerThread", null, Build.VERSION.SDK_INT < 19 ? "run" : "initAndLoop", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { loadedPackagesInProcess.add("android"); LoadPackageParam lpparam = new LoadPackageParam(sLoadedPackageCallbacks); lpparam.packageName = "android"; lpparam.processName = "android"; // it's actually system_server, but other functions return this as well lpparam.classLoader = BOOTCLASSLOADER; lpparam.appInfo = null; lpparam.isFirstApplication = true; XC_LoadPackage.callAll(lpparam); } }); // when a package is loaded for an existing process, trigger the callbacks as well hookAllConstructors(LoadedApk.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { LoadedApk loadedApk = (LoadedApk) param.thisObject; String packageName = loadedApk.getPackageName(); XResources.setPackageNameForResDir(packageName, loadedApk.getResDir()); if (packageName.equals("android") || !loadedPackagesInProcess.add(packageName)) return; if ((Boolean) getBooleanField(loadedApk, "mIncludeCode") == false) return; LoadPackageParam lpparam = new LoadPackageParam(sLoadedPackageCallbacks); lpparam.packageName = packageName; lpparam.processName = AndroidAppHelper.currentProcessName(); lpparam.classLoader = loadedApk.getClassLoader(); lpparam.appInfo = loadedApk.getApplicationInfo(); lpparam.isFirstApplication = false; XC_LoadPackage.callAll(lpparam); } }); findAndHookMethod("android.app.ApplicationPackageManager", null, "getResourcesForApplication", ApplicationInfo.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { ApplicationInfo app = (ApplicationInfo) param.args[0]; XResources.setPackageNameForResDir(app.packageName, app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir); } }); if (!new File(BASE_DIR + "conf/disable_resources").exists()) { hookResources(); } else { disableResources = true; } }
findAndHookMethod是关键,如何hookmethod,继续往下看
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); }
findAndHookMethod(ActivityThread.class,"handleBindApplication","android.app.ActivityThread.AppBindData",newXC_MethodHook());
这里是要findActivityThread对象中的AppBindData方法,AppBindData在ActivityThread中是一个内部类。
XposedBridge.hookMethod(m,callback);
这句就是把find的method和callback进行关联,用callback替代AppBindData,看一下原理:
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) { if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) { throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString()); } else if (hookMethod.getDeclaringClass().isInterface()) { throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString()); } else if (Modifier.isAbstract(hookMethod.getModifiers())) { throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString()); } boolean newMethod = false; CopyOnWriteSortedSet<XC_MethodHook> callbacks; synchronized (sHookedMethodCallbacks) { callbacks = sHookedMethodCallbacks.get(hookMethod); if (callbacks == null) { callbacks = new CopyOnWriteSortedSet<XC_MethodHook>(); sHookedMethodCallbacks.put(hookMethod, callbacks); newMethod = true; } } callbacks.add(callback); if (newMethod) { Class<?> declaringClass = hookMethod.getDeclaringClass(); int slot = (int) getIntField(hookMethod, "slot"); Class<?>[] parameterTypes; Class<?> returnType; if (hookMethod instanceof Method) { parameterTypes = ((Method) hookMethod).getParameterTypes(); returnType = ((Method) hookMethod).getReturnType(); } else { parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes(); returnType = null; } AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType); hookMethodNative(hookMethod, declaringClass, slot, additionalInfo); } return callback.new Unhook(hookMethod); }
1.sHookedMethodCallbacks寻找要hook的method,如果不存在,则新建一个callback和hookmethod进行关联,sHookedMethodCallbacks.put(hookMethod,callbacks);callbacks.add(callback);
2.如果是新hookmethod,进行hookMethodNative,进入JNI层调用XposedBridge_hookMethodNative
/** @function hookMethodNative 将输入的Class中的Method方法的nativeFunc替换为xposedCallHandler @para declaredClassIndirect 类对象 @para slot Method在类中的偏移位置 */ 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); } } }