From 0dfd499476cd8d507edf7a63fa27b2c5d9e7223b Mon Sep 17 00:00:00 2001 From: frknkrc44 Date: Mon, 1 Sep 2025 07:34:20 +0300 Subject: [PATCH] Changes for accessibility and content provider - Remove the Settings Provider hook and switch to the ContentProvider$Transport hook - Fix the accessibility manager hook --- .../settings_presets/AccessibilityPreset.kt | 2 +- .../DeveloperOptionsPreset.kt | 2 +- .../hidemyapplist/xposed/HMAService.kt | 2 + .../xposed/HMASettingsProviderService.kt | 148 ------------------ .../hidemyapplist/xposed/Utils4Xposed.kt | 11 ++ .../hidemyapplist/xposed/XposedEntry.kt | 2 - .../xposed/hook/AccessibilityHook.kt | 37 +++-- .../xposed/hook/ContentProviderHook.kt | 74 +++++++++ 8 files changed, 116 insertions(+), 162 deletions(-) delete mode 100644 xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMASettingsProviderService.kt create mode 100644 xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/AccessibilityPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/AccessibilityPreset.kt index 678a2d2..10e3cbb 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/AccessibilityPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/AccessibilityPreset.kt @@ -14,7 +14,7 @@ class AccessibilityPreset : BasePreset(NAME) { ), ReplacementItem( name = Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - value = null, + value = "", ), ) } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/DeveloperOptionsPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/DeveloperOptionsPreset.kt index c96ae3c..d2de721 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/DeveloperOptionsPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/DeveloperOptionsPreset.kt @@ -15,6 +15,6 @@ class DeveloperOptionsPreset : BasePreset("dev_options") { ReplacementItem( name = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, value = "0", - ) + ), ) } diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt index 0837041..2485d14 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt @@ -11,6 +11,7 @@ import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.common.Utils import icu.nullptr.hidemyapplist.xposed.hook.AccessibilityHook import icu.nullptr.hidemyapplist.xposed.hook.ActivityHook +import icu.nullptr.hidemyapplist.xposed.hook.ContentProviderHook import icu.nullptr.hidemyapplist.xposed.hook.IFrameworkHook import icu.nullptr.hidemyapplist.xposed.hook.PlatformCompatHook import icu.nullptr.hidemyapplist.xposed.hook.PmsHookTarget28 @@ -154,6 +155,7 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { frameworkHooks.add(ActivityHook(this)) frameworkHooks.add(PmsPackageEventsHook(this)) frameworkHooks.add(AccessibilityHook(this)) + frameworkHooks.add(ContentProviderHook(this)) frameworkHooks.forEach(IFrameworkHook::load) logI(TAG, "Hooks installed") diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMASettingsProviderService.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMASettingsProviderService.kt deleted file mode 100644 index ac50e15..0000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMASettingsProviderService.kt +++ /dev/null @@ -1,148 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed - -import android.content.ContentProvider -import android.os.Bundle -import android.provider.Settings -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.IXposedHookLoadPackage -import de.robv.android.xposed.callbacks.XC_LoadPackage -import icu.nullptr.hidemyapplist.common.JsonConfig -import icu.nullptr.hidemyapplist.common.SettingsPresets -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem -import org.frknkrc44.hma_oss.common.BuildConfig -import java.io.File - -class HMASettingsProviderService : IXposedHookLoadPackage { - companion object { - private const val TAG = "SettingsProviderHook" - } - - init { - searchDataDir() - } - - private lateinit var dataDir: String - private lateinit var configFile: File - - private val configLock = Any() - private var configLastModified: Long = 0 - - var config = JsonConfig().apply { detailLog = true } - private set - - private fun searchDataDir() { - File("/data/system").list()?.forEach { - if (it.startsWith("hide_my_applist")) { - if (!this::dataDir.isInitialized) { - val newDir = File("/data/misc/$it") - File("/data/system/$it").renameTo(newDir) - dataDir = newDir.path - } else { - File("/data/system/$it").deleteRecursively() - } - } - } - File("/data/misc").list()?.forEach { - if (it.startsWith("hide_my_applist")) { - if (!this::dataDir.isInitialized) { - dataDir = "/data/misc/$it" - } else if (dataDir != "/data/misc/$it") { - File("/data/misc/$it").deleteRecursively() - } - } - } - if (!this::dataDir.isInitialized) { - dataDir = "/data/misc/hide_my_applist_" + Utils.generateRandomString(16) - } - - File("$dataDir/log").mkdirs() - configFile = File("$dataDir/config.json") - - logI(TAG, "Data dir: $dataDir") - } - - private fun loadConfig() { - if (!configFile.exists()) { - logI(TAG, "Config file not found") - return - } - - if (configLastModified == configFile.lastModified()) { - return - } - - configLastModified = configFile.lastModified() - - val loading = runCatching { - val json = configFile.readText() - JsonConfig.parse(json) - }.getOrElse { - logE(TAG, "Failed to parse config.json", it) - return - } - if (loading.configVersion != BuildConfig.CONFIG_VERSION) { - logW(TAG, "Config version mismatch, need to reload") - return - } - config = loading - logI(TAG, "Config loaded") - } - - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - logI(TAG, "Load hook") - - findMethod( - "com.android.providers.settings.SettingsProvider", - lpparam.classLoader, - ) { - name == "call" - }.hookBefore { param -> - loadConfig() - - val contentProvider = param.thisObject as ContentProvider - val method = param.args[0] as String? - val name = param.args[1] as String? - - val caller = contentProvider.callingPackage - if (!config.scope.containsKey(caller)) { - // logD(TAG, "Not enabled app: $caller") - return@hookBefore - } - - logD(TAG, "@call caller: $caller, method: $method, name: $name") - - when (method) { - "GET_global", "GET_secure", "GET_system" -> { - val replacement = getSpoofedSetting(caller, name) - if (replacement != null) { - logD(TAG, "@getSettings returned replacement for $caller: ${replacement.value}") - param.result = Bundle().apply { - putString(Settings.NameValueTable.VALUE, replacement.value) - } - } - } - } - } - } - - fun getSpoofedSetting(caller: String?, name: String?): ReplacementItem? { - synchronized(configLock) { - if (caller == null || name == null) return null - - val presets = config.scope[caller]?.applySettingsPresets - if (presets?.isEmpty() ?: true) { - return null - } - - for (presetName in presets) { - val preset = SettingsPresets.instance.getPresetByName(presetName) - val value = preset?.getSpoofedValue(name) - if (value != null) return value - } - - return null - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt index a3f2e44..bc5b388 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt @@ -1,7 +1,10 @@ package icu.nullptr.hidemyapplist.xposed +import android.os.Binder import android.os.Build import com.github.kyuubiran.ezxhelper.utils.findField +import icu.nullptr.hidemyapplist.common.Constants +import icu.nullptr.hidemyapplist.common.Utils class Utils4Xposed { companion object { @@ -12,5 +15,13 @@ class Utils4Xposed { }.get(packageSettings) as? String }.getOrNull() } + + fun getCallingApps(service: HMAService): Array { + val callingUid = Binder.getCallingUid() + if (callingUid == Constants.UID_SYSTEM) return arrayOf() + return Utils.binderLocalScope { + service.pms.getPackagesForUid(callingUid) + } ?: arrayOf() + } } } diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt index 802b2b2..79962ed 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt @@ -50,8 +50,6 @@ class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage { } } } - } else if (lpparam.packageName == "com.android.providers.settings") { - HMASettingsProviderService().handleLoadPackage(lpparam) } } } diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt index 67b5737..e59877f 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt @@ -1,18 +1,20 @@ package icu.nullptr.hidemyapplist.xposed.hook import android.accessibilityservice.AccessibilityServiceInfo -import android.app.ActivityThread import com.github.kyuubiran.ezxhelper.utils.findMethod import com.github.kyuubiran.ezxhelper.utils.hookBefore import de.robv.android.xposed.XC_MethodHook import icu.nullptr.hidemyapplist.common.settings_presets.AccessibilityPreset import icu.nullptr.hidemyapplist.xposed.HMAService +import icu.nullptr.hidemyapplist.xposed.Utils4Xposed import icu.nullptr.hidemyapplist.xposed.logE import icu.nullptr.hidemyapplist.xposed.logI +// Big credits: https://github.com/Nitsuya/DoNotTryAccessibility/blob/main/app/src/main/java/io/github/nitsuya/donottryaccessibility/hook/AndroidFrameworkHooker.kt class AccessibilityHook(private val service: HMAService) : IFrameworkHook { companion object { private const val TAG = "AccessibilityHook" + private const val ACCESSIBILITY_SERVICE = "com.android.server.accessibility.AccessibilityManagerService" } private val hookList = mutableSetOf() @@ -20,24 +22,39 @@ class AccessibilityHook(private val service: HMAService) : IFrameworkHook { override fun load() { logI(TAG, "Load hook") - hookList += findMethod( - "com.android.server.accessibility.AccessibilityManagerService" - ) { + hookList += findMethod(ACCESSIBILITY_SERVICE) { name == "getInstalledAccessibilityServiceList" }.hookBefore { param -> hookedMethod(param) } - hookList += findMethod( - "com.android.server.accessibility.AccessibilityManagerService" - ) { + hookList += findMethod(ACCESSIBILITY_SERVICE) { name == "getEnabledAccessibilityServiceList" }.hookBefore { param -> hookedMethod(param) } + + hookList += findMethod(ACCESSIBILITY_SERVICE) { + name == "addClient" + }.hookBefore { param -> + val callingApps = Utils4Xposed.getCallingApps(service) + if (callingApps.isEmpty()) return@hookBefore + + for (caller in callingApps) { + if (service.getEnabledSettingsPresets(caller).contains(AccessibilityPreset.NAME)) { + param.result = 0L + break + } + } + } } private fun hookedMethod(param: XC_MethodHook.MethodHookParam) { try { - val caller = ActivityThread.currentActivityThread().application.packageName - if (service.getEnabledSettingsPresets(caller).contains(AccessibilityPreset.NAME)) { - param.result = java.util.ArrayList() + val callingApps = Utils4Xposed.getCallingApps(service) + if (callingApps.isEmpty()) return + + for (caller in callingApps) { + if (service.getEnabledSettingsPresets(caller).contains(AccessibilityPreset.NAME)) { + param.result = java.util.ArrayList() + break + } } } catch (e: Throwable) { logE(TAG, "Fatal error occurred, ignore hooks", e) diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt new file mode 100644 index 0000000..5cb0307 --- /dev/null +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt @@ -0,0 +1,74 @@ +package icu.nullptr.hidemyapplist.xposed.hook + +import android.os.Bundle +import android.provider.Settings +import com.github.kyuubiran.ezxhelper.utils.findMethod +import com.github.kyuubiran.ezxhelper.utils.hookBefore +import de.robv.android.xposed.XC_MethodHook +import icu.nullptr.hidemyapplist.common.SettingsPresets +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem +import icu.nullptr.hidemyapplist.xposed.HMAService +import icu.nullptr.hidemyapplist.xposed.Utils4Xposed +import icu.nullptr.hidemyapplist.xposed.logD + +class ContentProviderHook(private val service: HMAService): IFrameworkHook { + companion object { + private const val TAG = "ContentProviderHook" + } + + private var hook: XC_MethodHook.Unhook? = null + + // Credit: https://github.com/Nitsuya/DoNotTryAccessibility/blob/main/app/src/main/java/io/github/nitsuya/donottryaccessibility/hook/AndroidFrameworkHooker.kt + override fun load() { + hook = findMethod( + "android.content.ContentProvider\$Transport" + ) { + name == "call" + }.hookBefore { param -> + val callingApps = Utils4Xposed.getCallingApps(service) + if (callingApps.isEmpty()) return@hookBefore + + val method = param.args[2] as String? + val name = param.args[3] as String? + + for (caller in callingApps) { + logD(TAG, "@call caller: $caller, method: $method, name: $name") + + when (method) { + "GET_global", "GET_secure", "GET_system" -> { + val replacement = getSpoofedSetting(caller, name) + if (replacement != null) { + logD(TAG, "@getSettings returned replacement for $caller: ${replacement.value}") + param.result = Bundle().apply { + putString(Settings.NameValueTable.VALUE, replacement.value) + putInt("_generation_index", -1) + } + } + } + } + } + } + } + + fun getSpoofedSetting(caller: String?, name: String?): ReplacementItem? { + if (caller == null || name == null) return null + + val presets = service.getEnabledSettingsPresets(caller) + if (presets.isEmpty()) { + return null + } + + for (presetName in presets) { + val preset = SettingsPresets.instance.getPresetByName(presetName) + val value = preset?.getSpoofedValue(name) + if (value != null) return value + } + + return null + } + + override fun unload() { + hook?.unhook() + hook = null + } +} \ No newline at end of file