Rename hook class to VeilHook; add root/manager-app hiding and build-tag spoofing
Renamed since the module hides more than accessibility state now — hook class and app display name become VeilHook/Veil (the module already lived under the space.bdeshi.veil namespace). Two new features, same per-app toggle model as everything else: - Root/manager apps: hides packages in a configurable shared list (default KernelSU + Magisk) from PackageManager's getPackageInfo/getApplicationInfo/ getInstalledPackages/getInstalledApplications. - Build tags/type: overwrites Build.TAGS/Build.TYPE to release-keys/user for the hooked process, hiding the test-keys/userdebug markers apps sometimes check for. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
space.bdeshi.veil.HideA11yHook
|
||||
space.bdeshi.veil.VeilHook
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
package space.bdeshi.veil
|
||||
|
||||
object Features {
|
||||
const val PREFS_NAME = "hidea11y_prefs"
|
||||
const val PREFS_NAME = "veil_prefs"
|
||||
const val KEY_PREFIX = "features_"
|
||||
const val HIDDEN_PACKAGES_KEY = "hidden_packages"
|
||||
|
||||
const val ACCESSIBILITY = "accessibility"
|
||||
const val ADB = "adb"
|
||||
const val DEV_OPTIONS = "dev_options"
|
||||
const val MOCK_LOCATION = "mock_location"
|
||||
const val ROOT_APPS = "root_apps"
|
||||
const val BUILD_TAGS = "build_tags"
|
||||
|
||||
val ALL: Set<String> = setOf(ACCESSIBILITY, ADB, DEV_OPTIONS, MOCK_LOCATION)
|
||||
val ALL: Set<String> = setOf(
|
||||
ACCESSIBILITY,
|
||||
ADB,
|
||||
DEV_OPTIONS,
|
||||
MOCK_LOCATION,
|
||||
ROOT_APPS,
|
||||
BUILD_TAGS,
|
||||
)
|
||||
|
||||
val LABELS: LinkedHashMap<String, String> = linkedMapOf(
|
||||
ACCESSIBILITY to "Accessibility services",
|
||||
ADB to "USB / wireless debugging",
|
||||
DEV_OPTIONS to "Developer options enabled",
|
||||
MOCK_LOCATION to "Mock location (legacy flag only)",
|
||||
ROOT_APPS to "Root/manager apps (PackageManager)",
|
||||
BUILD_TAGS to "Build tags/type (test-keys, userdebug)",
|
||||
)
|
||||
|
||||
val DEFAULT_HIDDEN_PACKAGES: Set<String> = setOf(
|
||||
"me.weishu.kernelsu",
|
||||
"com.topjohnwu.magisk",
|
||||
)
|
||||
|
||||
fun keyFor(packageName: String): String = KEY_PREFIX + packageName
|
||||
|
||||
@@ -19,8 +19,10 @@ import java.io.File
|
||||
class SettingsActivity : Activity() {
|
||||
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private lateinit var listContainer: LinearLayout
|
||||
private lateinit var appListContainer: LinearLayout
|
||||
private lateinit var hiddenPackagesContainer: LinearLayout
|
||||
private lateinit var packageInput: EditText
|
||||
private lateinit var hiddenPackageInput: EditText
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -33,7 +35,7 @@ class SettingsActivity : Activity() {
|
||||
}
|
||||
|
||||
root.addView(TextView(this).apply {
|
||||
text = "Hide A11y"
|
||||
text = "Veil"
|
||||
textSize = 20f
|
||||
setTypeface(typeface, Typeface.BOLD)
|
||||
})
|
||||
@@ -44,57 +46,99 @@ class SettingsActivity : Activity() {
|
||||
setPadding(0, dp(8), 0, dp(16))
|
||||
})
|
||||
|
||||
val addRow = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
}
|
||||
packageInput = EditText(this).apply {
|
||||
hint = "e.g. com.example.targetapp"
|
||||
inputType = InputType.TYPE_CLASS_TEXT
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
}
|
||||
val addButton = Button(this).apply {
|
||||
text = "Add"
|
||||
setOnClickListener {
|
||||
val pkg = packageInput.text.toString().trim()
|
||||
if (pkg.isNotEmpty() && pkg.contains(".")) {
|
||||
prefs.edit().putStringSet(Features.keyFor(pkg), Features.ALL.toMutableSet()).apply()
|
||||
fixPermissions()
|
||||
packageInput.setText("")
|
||||
refreshList()
|
||||
}
|
||||
root.addView(addRow(packageInput) {
|
||||
val pkg = packageInput.text.toString().trim()
|
||||
if (pkg.isNotEmpty() && pkg.contains(".")) {
|
||||
prefs.edit().putStringSet(Features.keyFor(pkg), Features.ALL.toMutableSet()).apply()
|
||||
fixPermissions()
|
||||
packageInput.setText("")
|
||||
refreshAppList()
|
||||
}
|
||||
}
|
||||
addRow.addView(packageInput)
|
||||
addRow.addView(addButton)
|
||||
root.addView(addRow)
|
||||
})
|
||||
|
||||
listContainer = LinearLayout(this).apply {
|
||||
appListContainer = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(0, dp(16), 0, 0)
|
||||
}
|
||||
root.addView(listContainer)
|
||||
root.addView(appListContainer)
|
||||
|
||||
root.addView(TextView(this).apply {
|
||||
text = "Packages hidden from PackageManager"
|
||||
textSize = 18f
|
||||
setTypeface(typeface, Typeface.BOLD)
|
||||
setPadding(0, dp(24), 0, dp(4))
|
||||
})
|
||||
root.addView(TextView(this).apply {
|
||||
text = "Used by the \"Root/manager apps\" toggle above — apps in this list become " +
|
||||
"invisible to getPackageInfo/getInstalledPackages for any target app with that " +
|
||||
"toggle on."
|
||||
setPadding(0, 0, 0, dp(8))
|
||||
})
|
||||
|
||||
hiddenPackageInput = EditText(this).apply {
|
||||
hint = "e.g. me.weishu.kernelsu"
|
||||
inputType = InputType.TYPE_CLASS_TEXT
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
}
|
||||
root.addView(addRow(hiddenPackageInput) {
|
||||
val pkg = hiddenPackageInput.text.toString().trim()
|
||||
if (pkg.isNotEmpty() && pkg.contains(".")) {
|
||||
val current = hiddenPackages().toMutableSet()
|
||||
current.add(pkg)
|
||||
prefs.edit().putStringSet(Features.HIDDEN_PACKAGES_KEY, current).apply()
|
||||
fixPermissions()
|
||||
hiddenPackageInput.setText("")
|
||||
refreshHiddenPackages()
|
||||
}
|
||||
})
|
||||
|
||||
hiddenPackagesContainer = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(0, dp(8), 0, 0)
|
||||
}
|
||||
root.addView(hiddenPackagesContainer)
|
||||
|
||||
setContentView(ScrollView(this).apply { addView(root) })
|
||||
refreshList()
|
||||
refreshAppList()
|
||||
refreshHiddenPackages()
|
||||
}
|
||||
|
||||
private fun refreshList() {
|
||||
listContainer.removeAllViews()
|
||||
private fun addRow(input: EditText, onAdd: () -> Unit): View {
|
||||
return LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
addView(input)
|
||||
addView(Button(this@SettingsActivity).apply {
|
||||
text = "Add"
|
||||
setOnClickListener { onAdd() }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun hiddenPackages(): Set<String> =
|
||||
prefs.getStringSet(Features.HIDDEN_PACKAGES_KEY, Features.DEFAULT_HIDDEN_PACKAGES) ?: Features.DEFAULT_HIDDEN_PACKAGES
|
||||
|
||||
private fun refreshAppList() {
|
||||
appListContainer.removeAllViews()
|
||||
val packages = prefs.all.keys
|
||||
.filter { it.startsWith(Features.KEY_PREFIX) }
|
||||
.map { it.removePrefix(Features.KEY_PREFIX) }
|
||||
.sorted()
|
||||
|
||||
if (packages.isEmpty()) {
|
||||
listContainer.addView(TextView(this).apply {
|
||||
appListContainer.addView(TextView(this).apply {
|
||||
text = "No apps configured yet."
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for (pkg in packages) {
|
||||
listContainer.addView(buildPackageCard(pkg))
|
||||
appListContainer.addView(buildPackageCard(pkg))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,13 +173,47 @@ class SettingsActivity : Activity() {
|
||||
setOnClickListener {
|
||||
prefs.edit().remove(Features.keyFor(pkg)).apply()
|
||||
fixPermissions()
|
||||
refreshList()
|
||||
refreshAppList()
|
||||
}
|
||||
})
|
||||
|
||||
return card
|
||||
}
|
||||
|
||||
private fun refreshHiddenPackages() {
|
||||
hiddenPackagesContainer.removeAllViews()
|
||||
val packages = hiddenPackages().sorted()
|
||||
|
||||
if (packages.isEmpty()) {
|
||||
hiddenPackagesContainer.addView(TextView(this).apply {
|
||||
text = "None configured."
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for (pkg in packages) {
|
||||
val row = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
}
|
||||
row.addView(TextView(this).apply {
|
||||
text = pkg
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
})
|
||||
row.addView(Button(this).apply {
|
||||
text = "Remove"
|
||||
setOnClickListener {
|
||||
val current = hiddenPackages().toMutableSet()
|
||||
current.remove(pkg)
|
||||
prefs.edit().putStringSet(Features.HIDDEN_PACKAGES_KEY, current).apply()
|
||||
fixPermissions()
|
||||
refreshHiddenPackages()
|
||||
}
|
||||
})
|
||||
hiddenPackagesContainer.addView(row)
|
||||
}
|
||||
}
|
||||
|
||||
// LSPosed's own SELinux policy is what actually lets hooked processes read this file;
|
||||
// this just ensures the DAC bits don't block it too.
|
||||
private fun fixPermissions() {
|
||||
|
||||
+103
-8
@@ -1,5 +1,9 @@
|
||||
package space.bdeshi.veil
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import de.robv.android.xposed.IXposedHookLoadPackage
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import de.robv.android.xposed.XC_MethodReplacement
|
||||
@@ -21,12 +25,13 @@ private val ZERO_VALUE_KEYS_BY_FEATURE = mapOf(
|
||||
Features.MOCK_LOCATION to setOf("mock_location", "allow_mock_location"),
|
||||
)
|
||||
|
||||
class HideA11yHook : IXposedHookLoadPackage {
|
||||
class VeilHook : IXposedHookLoadPackage {
|
||||
|
||||
override fun handleLoadPackage(lpparam: LoadPackageParam) {
|
||||
if (lpparam.packageName == SELF_PACKAGE) return
|
||||
|
||||
val enabled = readEnabledFeatures(lpparam.packageName)
|
||||
val prefs = openPrefs()
|
||||
val enabled = readEnabledFeatures(prefs, lpparam.packageName)
|
||||
if (enabled.isEmpty()) return
|
||||
|
||||
if (Features.ACCESSIBILITY in enabled) {
|
||||
@@ -39,19 +44,37 @@ class HideA11yHook : IXposedHookLoadPackage {
|
||||
hookSettingsClass("android.provider.Settings\$Secure", lpparam, nullStringKeys, zeroValueKeys)
|
||||
hookSettingsClass("android.provider.Settings\$Global", lpparam, nullStringKeys, zeroValueKeys)
|
||||
}
|
||||
|
||||
if (Features.ROOT_APPS in enabled) {
|
||||
val hiddenPackages = readHiddenPackages(prefs)
|
||||
if (hiddenPackages.isNotEmpty()) {
|
||||
hookPackageManager(lpparam, hiddenPackages)
|
||||
}
|
||||
}
|
||||
|
||||
if (Features.BUILD_TAGS in enabled) {
|
||||
spoofBuildTags()
|
||||
}
|
||||
}
|
||||
|
||||
private fun readEnabledFeatures(packageName: String): Set<String> {
|
||||
private fun openPrefs(): XSharedPreferences? {
|
||||
return try {
|
||||
val prefs = XSharedPreferences(SELF_PACKAGE, Features.PREFS_NAME)
|
||||
prefs.reload()
|
||||
prefs.getStringSet(Features.keyFor(packageName), Features.ALL) ?: Features.ALL
|
||||
XSharedPreferences(SELF_PACKAGE, Features.PREFS_NAME).apply { reload() }
|
||||
} catch (t: Throwable) {
|
||||
// Config unreadable (e.g. never configured, or permissions not yet fixed) — fail open to "hide everything".
|
||||
Features.ALL
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun readEnabledFeatures(prefs: XSharedPreferences?, packageName: String): Set<String> {
|
||||
// Config unreadable (never configured, or permissions not yet fixed) — fail open to "hide everything".
|
||||
return prefs?.getStringSet(Features.keyFor(packageName), Features.ALL) ?: Features.ALL
|
||||
}
|
||||
|
||||
private fun readHiddenPackages(prefs: XSharedPreferences?): Set<String> {
|
||||
return prefs?.getStringSet(Features.HIDDEN_PACKAGES_KEY, Features.DEFAULT_HIDDEN_PACKAGES)
|
||||
?: Features.DEFAULT_HIDDEN_PACKAGES
|
||||
}
|
||||
|
||||
private fun hookAccessibilityManager(lpparam: LoadPackageParam) {
|
||||
val clazz = XposedHelpers.findClassIfExists(
|
||||
"android.view.accessibility.AccessibilityManager",
|
||||
@@ -138,4 +161,76 @@ class HideA11yHook : IXposedHookLoadPackage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hookPackageManager(lpparam: LoadPackageParam, hiddenPackages: Set<String>) {
|
||||
val clazz = XposedHelpers.findClassIfExists(
|
||||
"android.app.ApplicationPackageManager",
|
||||
lpparam.classLoader,
|
||||
) ?: return
|
||||
|
||||
val throwIfHidden = object : XC_MethodReplacement() {
|
||||
override fun replaceHookedMethod(param: XC_MethodHook.MethodHookParam): Any? {
|
||||
val name = param.args.getOrNull(0) as? String
|
||||
if (name != null && name in hiddenPackages) {
|
||||
throw PackageManager.NameNotFoundException(name)
|
||||
}
|
||||
return XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args)
|
||||
}
|
||||
}
|
||||
|
||||
val filterList = object : XC_MethodHook() {
|
||||
override fun afterHookedMethod(param: MethodHookParam) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = param.result as? MutableList<Any?> ?: return
|
||||
list.removeAll { entry ->
|
||||
val name = (entry as? PackageInfo)?.packageName ?: (entry as? ApplicationInfo)?.packageName
|
||||
name != null && name in hiddenPackages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val flagsClasses = listOfNotNull(
|
||||
Int::class.javaPrimitiveType,
|
||||
XposedHelpers.findClassIfExists("android.content.pm.PackageManager\$PackageInfoFlags", lpparam.classLoader),
|
||||
)
|
||||
val appFlagsClasses = listOfNotNull(
|
||||
Int::class.javaPrimitiveType,
|
||||
XposedHelpers.findClassIfExists("android.content.pm.PackageManager\$ApplicationInfoFlags", lpparam.classLoader),
|
||||
)
|
||||
val stringType = String::class.java
|
||||
|
||||
for (flagsType in flagsClasses) {
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(clazz, "getPackageInfo", stringType, flagsType, throwIfHidden)
|
||||
} catch (t: Throwable) {
|
||||
// Not present with this signature on this API level.
|
||||
}
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(clazz, "getInstalledPackages", flagsType, filterList)
|
||||
} catch (t: Throwable) {
|
||||
// Not present with this signature on this API level.
|
||||
}
|
||||
}
|
||||
for (flagsType in appFlagsClasses) {
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(clazz, "getApplicationInfo", stringType, flagsType, throwIfHidden)
|
||||
} catch (t: Throwable) {
|
||||
// Not present with this signature on this API level.
|
||||
}
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(clazz, "getInstalledApplications", flagsType, filterList)
|
||||
} catch (t: Throwable) {
|
||||
// Not present with this signature on this API level.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun spoofBuildTags() {
|
||||
try {
|
||||
XposedHelpers.setStaticObjectField(Build::class.java, "TAGS", "release-keys")
|
||||
XposedHelpers.setStaticObjectField(Build::class.java, "TYPE", "user")
|
||||
} catch (t: Throwable) {
|
||||
// Field not present/settable on this API level — safe to skip.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +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>
|
||||
<string name="app_name">Veil</string>
|
||||
<string name="module_description">Hides accessibility, debugging, and root/hook state from apps selected per-package in the settings screen</string>
|
||||
</resources>
|
||||
|
||||
+1
-1
@@ -15,5 +15,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "hidea11y"
|
||||
rootProject.name = "veil"
|
||||
include(":app")
|
||||
|
||||
Reference in New Issue
Block a user