Changes for accessibility and content provider

- Remove the Settings Provider hook and switch to the ContentProvider$Transport hook
- Fix the accessibility manager hook
This commit is contained in:
frknkrc44 2025-09-01 07:34:20 +03:00
commit 0dfd499476
No known key found for this signature in database
GPG key ID: 784AE1CC0DCFD34F
8 changed files with 116 additions and 162 deletions

View file

@ -14,7 +14,7 @@ class AccessibilityPreset : BasePreset(NAME) {
),
ReplacementItem(
name = Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
value = null,
value = "",
),
)
}

View file

@ -15,6 +15,6 @@ class DeveloperOptionsPreset : BasePreset("dev_options") {
ReplacementItem(
name = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
value = "0",
)
),
)
}

View file

@ -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")

View file

@ -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
}
}
}

View file

@ -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<String> {
val callingUid = Binder.getCallingUid()
if (callingUid == Constants.UID_SYSTEM) return arrayOf()
return Utils.binderLocalScope {
service.pms.getPackagesForUid(callingUid)
} ?: arrayOf()
}
}
}

View file

@ -50,8 +50,6 @@ class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage {
}
}
}
} else if (lpparam.packageName == "com.android.providers.settings") {
HMASettingsProviderService().handleLoadPackage(lpparam)
}
}
}

View file

@ -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<XC_MethodHook.Unhook>()
@ -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<AccessibilityServiceInfo>()
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<AccessibilityServiceInfo>()
break
}
}
} catch (e: Throwable) {
logE(TAG, "Fatal error occurred, ignore hooks", e)

View file

@ -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
}
}