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:
@@ -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")
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user