Working MVP: hide accessibility, ADB, and dev-debug state from LSPosed-scoped apps

LSPosed module that hooks AccessibilityManager and Settings.Secure/Global
(getString/getStringForUser/getInt/getIntForUser) inside whatever app
process LSPosed injects it into, so that process sees no accessibility
services and no adb/wifi-debugging flags enabled. Target apps are chosen
via the module's scope list in LSPosed Manager, not in-app config.

Confirmed working against a real target app on-device; it previously
refused to launch with an accessibility service active and now starts
normally with the module enabled and scoped to it. Required disabling
KernelSU's "umount modules" option for that app, since it otherwise blocks
Zygisk (and therefore LSPosed) from injecting into the process at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Claude Sonnet 5
2026-08-06 15:59:31 +06:00
commit cc5ceef51a
13 changed files with 550 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "space.bdeshi.veil"
compileSdk = 35
defaultConfig {
applicationId = "space.bdeshi.veil"
minSdk = 27
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
compileOnly("de.robv.android.xposed:api:82")
}
+19
View File
@@ -0,0 +1,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="@string/app_name"
android:allowBackup="false"
android:hasCode="true">
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposedminversion"
android:value="82" />
<meta-data
android:name="xposeddescription"
android:value="@string/module_description" />
</application>
</manifest>
+1
View File
@@ -0,0 +1 @@
space.bdeshi.veil.HideA11yHook
@@ -0,0 +1,116 @@
package space.bdeshi.veil
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam
private const val SELF_PACKAGE = "space.bdeshi.veil"
// Settings keys whose *string* reads should look absent to the hooked app.
private val NULL_STRING_KEYS = setOf(
"enabled_accessibility_services",
)
// Settings keys whose reads (string "0" / int 0) should look disabled to the hooked app.
private val ZERO_VALUE_KEYS = setOf(
"accessibility_enabled",
"touch_exploration_enabled",
"adb_enabled",
"adb_wifi_enabled",
)
class HideA11yHook : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: LoadPackageParam) {
if (lpparam.packageName == SELF_PACKAGE) return
hookAccessibilityManager(lpparam)
hookSettingsClass("android.provider.Settings\$Secure", lpparam)
hookSettingsClass("android.provider.Settings\$Global", lpparam)
}
private fun hookAccessibilityManager(lpparam: LoadPackageParam) {
val clazz = XposedHelpers.findClassIfExists(
"android.view.accessibility.AccessibilityManager",
lpparam.classLoader,
) ?: return
fun hook(name: String, vararg args: Any, result: Any?) {
try {
XposedHelpers.findAndHookMethod(
clazz, name, *args,
object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: XC_MethodHook.MethodHookParam): Any? {
return result
}
},
)
} catch (t: Throwable) {
// Method signature not present on this API level — safe to skip.
}
}
hook("getEnabledAccessibilityServiceList", Int::class.javaPrimitiveType!!, result = emptyList<Any>())
hook("getInstalledAccessibilityServiceList", result = emptyList<Any>())
hook("getAccessibilityServiceList", result = emptyList<Any>())
hook("isEnabled", result = false)
hook("isTouchExplorationEnabled", result = false)
}
private fun hookSettingsClass(className: String, lpparam: LoadPackageParam) {
val clazz = XposedHelpers.findClassIfExists(className, lpparam.classLoader) ?: return
fun callOriginal(param: XC_MethodHook.MethodHookParam): Any? =
XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args)
val stringReplacement = object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: XC_MethodHook.MethodHookParam): Any? {
val name = param.args.getOrNull(1) as? String ?: return callOriginal(param)
return when (name) {
in NULL_STRING_KEYS -> null
in ZERO_VALUE_KEYS -> "0"
else -> callOriginal(param)
}
}
}
val intReplacement = object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: XC_MethodHook.MethodHookParam): Any? {
val name = param.args.getOrNull(1) as? String ?: return callOriginal(param)
return if (name in ZERO_VALUE_KEYS) 0 else callOriginal(param)
}
}
val resolverType = android.content.ContentResolver::class.java
val stringType = String::class.java
val intType = Int::class.javaPrimitiveType!!
val signatures = listOf(
arrayOf(resolverType, stringType),
arrayOf(resolverType, stringType, intType),
)
for (methodName in listOf("getString", "getStringForUser")) {
for (signature in signatures) {
try {
XposedHelpers.findAndHookMethod(clazz, methodName, *signature, stringReplacement)
} catch (t: Throwable) {
// Not present with this signature on this API level.
}
}
}
for (methodName in listOf("getInt", "getIntForUser")) {
for (signature in signatures) {
try {
XposedHelpers.findAndHookMethod(clazz, methodName, *signature, intReplacement)
} catch (t: Throwable) {
// Not present with this signature on this API level.
}
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
<resources>
<string name="app_name">Hide A11y</string>
<string name="module_description">Hides accessibility service state from apps selected in LSPosed scope</string>
</resources>