diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/HandleBindAppHooker.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/HandleBindAppHooker.java index 32f23912..8663729a 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/HandleBindAppHooker.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/HandleBindAppHooker.java @@ -11,6 +11,7 @@ import com.elderdrivers.riru.common.KeepMembers; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.callbacks.XC_LoadPackage; +import static com.elderdrivers.riru.xposed.entry.hooker.XposedBlackListHooker.BLACK_LIST_PACKAGE_NAME; import static com.elderdrivers.riru.xposed.util.ClassLoaderUtils.replaceParentClassLoader; import static de.robv.android.xposed.XposedHelpers.getObjectField; import static de.robv.android.xposed.XposedHelpers.setObjectField; @@ -27,7 +28,7 @@ public class HandleBindAppHooker implements KeepMembers { public static String methodSig = "(Landroid/app/ActivityThread$AppBindData;)V"; public static void hook(Object thiz, Object bindData) { - if (XposedBridge.disableHooks) { + if (XposedBlackListHooker.shouldDisableHooks("")) { backup(thiz, bindData); return; } @@ -36,6 +37,11 @@ public class HandleBindAppHooker implements KeepMembers { ActivityThread activityThread = (ActivityThread) thiz; ApplicationInfo appInfo = (ApplicationInfo) getObjectField(bindData, "appInfo"); String reportedPackageName = appInfo.packageName.equals("android") ? "system" : appInfo.packageName; + + if (XposedBlackListHooker.shouldDisableHooks(reportedPackageName)) { + return; + } + ComponentName instrumentationName = (ComponentName) getObjectField(bindData, "instrumentationName"); if (instrumentationName != null) { logD("Instrumentation detected, disabling framework for"); @@ -64,6 +70,9 @@ public class HandleBindAppHooker implements KeepMembers { if (reportedPackageName.equals(INSTALLER_PACKAGE_NAME)) { XposedInstallerHooker.hookXposedInstaller(lpparam.classLoader); } + if (reportedPackageName.equals(BLACK_LIST_PACKAGE_NAME)) { + XposedBlackListHooker.hook(lpparam.classLoader); + } } catch (Throwable t) { logE("error when hooking bindApp", t); } finally { diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/LoadedApkConstructorHooker.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/LoadedApkConstructorHooker.java index 8d7109ec..9840a87a 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/LoadedApkConstructorHooker.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/LoadedApkConstructorHooker.java @@ -33,7 +33,7 @@ public class LoadedApkConstructorHooker implements KeepMembers { ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { - if (XposedBridge.disableHooks) { + if (XposedBlackListHooker.shouldDisableHooks("")) { backup(thiz, activityThread, aInfo, compatInfo, baseLoader, securityViolation, includeCode, registerPackage); return; @@ -48,6 +48,11 @@ public class LoadedApkConstructorHooker implements KeepMembers { String packageName = loadedApk.getPackageName(); Object mAppDir = getObjectField(thiz, "mAppDir"); logD("LoadedApk# ends: " + mAppDir); + + if (XposedBlackListHooker.shouldDisableHooks(packageName)) { + return; + } + if (packageName.equals("android")) { logD("LoadedApk# is android, skip: " + mAppDir); return; diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/XposedBlackListHooker.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/XposedBlackListHooker.java new file mode 100644 index 00000000..50c81f63 --- /dev/null +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/hooker/XposedBlackListHooker.java @@ -0,0 +1,87 @@ +package com.elderdrivers.riru.xposed.entry.hooker; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Build; + +import com.elderdrivers.riru.xposed.util.Utils; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XSharedPreferences; +import de.robv.android.xposed.XposedBridge; + +import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; +import static de.robv.android.xposed.XposedInit.INSTALLER_PACKAGE_NAME; + +public class XposedBlackListHooker { + + public static final String BLACK_LIST_PACKAGE_NAME = "com.flarejune.xposedblacklist"; + private static final boolean IS_USING_PROTECTED_STORAGE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; + private static final String BLACK_LIST_PREF_NAME = "list"; + private static final String PREF_KEY_BLACK_LIST = "blackList"; + public static final String PREF_FILE_PATH = (IS_USING_PROTECTED_STORAGE ? "/data/user_de/0/" : "/data/data") + + BLACK_LIST_PACKAGE_NAME + "/shared_prefs/" + BLACK_LIST_PREF_NAME + ".xml"; + private static final XSharedPreferences PREFERENCES = new XSharedPreferences(new File(PREF_FILE_PATH)); + // always white list. empty string is to make sure blackList does not contain empty packageName + private static final List WHITE_LIST = Arrays.asList(INSTALLER_PACKAGE_NAME, BLACK_LIST_PACKAGE_NAME, ""); + + static { + try { + PREFERENCES.makeWorldReadable(); + } catch (Throwable throwable) { + Utils.logE("error making pref worldReadable", throwable); + } + } + + public static boolean shouldDisableHooks(String packageName) { + return XposedBridge.disableHooks || getBlackList().contains(packageName); + } + + public static Set getBlackList() { + try { + PREFERENCES.reload(); + Set result = PREFERENCES.getStringSet(PREF_KEY_BLACK_LIST, new HashSet()); + if (result != null) result.removeAll(WHITE_LIST); + return result; + } catch (Throwable throwable) { + Utils.logE("error when reading black list", throwable); + return new HashSet<>(); + } + } + + public static void hook(ClassLoader classLoader) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return; + } + try { + findAndHookMethod(ContextWrapper.class, "getSharedPreferences", String.class, int.class, new XC_MethodHook() { + @TargetApi(Build.VERSION_CODES.N) + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + try { + String prefName = (String) param.args[0]; + if (!prefName.equals(BLACK_LIST_PREF_NAME)) { + return; + } + Activity activity = (Activity) param.thisObject; + Context context = activity.createDeviceProtectedStorageContext(); + context.moveSharedPreferencesFrom(activity, prefName); + param.setResult(context.getSharedPreferences(prefName, (int) param.args[1])); + } catch (Throwable throwable) { + Utils.logE("error hooking Xposed BlackList", throwable); + } + } + }); + } catch (Throwable throwable) { + Utils.logE("error hooking Xposed BlackList", throwable); + } + } +} diff --git a/Bridge/src/main/java/de/robv/android/xposed/XposedInit.java b/Bridge/src/main/java/de/robv/android/xposed/XposedInit.java index 6722961b..6fae2d82 100644 --- a/Bridge/src/main/java/de/robv/android/xposed/XposedInit.java +++ b/Bridge/src/main/java/de/robv/android/xposed/XposedInit.java @@ -3,6 +3,7 @@ package de.robv.android.xposed; import android.annotation.SuppressLint; import android.app.AndroidAppHelper; import android.os.Build; +import android.text.TextUtils; import android.util.Log; import com.android.internal.os.ZygoteInit; @@ -23,6 +24,7 @@ import dalvik.system.DexFile; import dalvik.system.PathClassLoader; import de.robv.android.xposed.services.BaseService; +import static com.elderdrivers.riru.xposed.entry.hooker.XposedBlackListHooker.BLACK_LIST_PACKAGE_NAME; import static de.robv.android.xposed.XposedHelpers.closeSilently; import static de.robv.android.xposed.XposedHelpers.findClass; import static de.robv.android.xposed.XposedHelpers.findFieldIfExists; @@ -122,6 +124,11 @@ public final class XposedInit { private static void loadModule(String apk, ClassLoader topClassLoader) { Log.i(TAG, "Loading modules from " + apk); + if (!TextUtils.isEmpty(apk) && apk.contains(BLACK_LIST_PACKAGE_NAME)) { + Log.i(TAG, "We are going to take over black list's job..."); + return; + } + if (!new File(apk).exists()) { Log.e(TAG, " File does not exist"); return;