XposedBridge是Xposed框架替代ZygoteInit的文件,其中main方式是其入口,分析main方法可以更好的理解Xposed的运行模式,下面就来分析一下此函数。
initNative()函数对一些JNI函数的注册和回调方法的注册,JNI层对应的方法为XposedBridge_initNative,此方法后续会进行分析。
initXbridgeZygote();对Zygote进行初始化,深入探讨一下此函数,由于比较复杂,耐心跟踪一下
findAndHookMethod是关键,如何hook method,继续往下看
findAndHookMethod(ActivityThread.class, "handleBindApplication", "android.app.ActivityThread.AppBindData", new XC_MethodHook() );
这里是要find ActivityThread对象中的AppBindData方法,AppBindData在ActivityThread中是一个内部类。
XposedBridge.hookMethod(m, callback);
这句就是把find的method和callback进行关联,用callback替代AppBindData,看一下原理:
1. sHookedMethodCallbacks寻找要hook的method,如果不存在,则新建一个callback和hookmethod进行关联,sHookedMethodCallbacks.put(hookMethod, callbacks);callbacks.add(callback);
2. 如果是新hookmethod,进行hookMethodNative,进入JNI层调用XposedBridge_hookMethodNative
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是关键,如何hook method,继续往下看
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", new XC_MethodHook() );
这里是要find ActivityThread对象中的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);
}
}
}