Cache reflection results using lazy fields
This commit is contained in:
@@ -19,30 +19,67 @@ import java.io.IOException
|
|||||||
|
|
||||||
object PrivilegedServiceUtils {
|
object PrivilegedServiceUtils {
|
||||||
|
|
||||||
|
private val iPackageManagerStubClass by lazy { Class.forName("android.content.pm.IPackageManager\$Stub") }
|
||||||
|
private val asInterfaceMethod by lazy { iPackageManagerStubClass.getMethod("asInterface", IBinder::class.java) }
|
||||||
|
private val iPackageManagerClass by lazy { Class.forName("android.content.pm.IPackageManager") }
|
||||||
|
|
||||||
|
private val getInstalledPackagesMethodLong by lazy {
|
||||||
|
iPackageManagerClass.getMethod(
|
||||||
|
"getInstalledPackages",
|
||||||
|
Long::class.javaPrimitiveType,
|
||||||
|
Int::class.javaPrimitiveType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val getInstalledPackagesMethodInt by lazy {
|
||||||
|
iPackageManagerClass.getMethod(
|
||||||
|
"getInstalledPackages",
|
||||||
|
Int::class.javaPrimitiveType,
|
||||||
|
Int::class.javaPrimitiveType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val getPackageInstallerMethod by lazy { iPackageManagerClass.getMethod("getPackageInstaller") }
|
||||||
|
|
||||||
|
private val packageInstallerCtorS by lazy {
|
||||||
|
PackageInstaller::class.java.getConstructor(
|
||||||
|
IPackageInstaller::class.java,
|
||||||
|
String::class.java,
|
||||||
|
String::class.java,
|
||||||
|
Int::class.javaPrimitiveType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val packageInstallerCtorPre by lazy {
|
||||||
|
PackageInstaller::class.java.getConstructor(
|
||||||
|
IPackageInstaller::class.java,
|
||||||
|
String::class.java,
|
||||||
|
Int::class.javaPrimitiveType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val sessionCtor by lazy {
|
||||||
|
PackageInstaller.Session::class.java.getConstructor(IPackageInstallerSession::class.java)
|
||||||
|
}
|
||||||
|
private val intentSenderCtor by lazy {
|
||||||
|
IntentSender::class.java.getConstructor(IIntentSender::class.java)
|
||||||
|
}
|
||||||
|
private val installFlagsField by lazy {
|
||||||
|
PackageInstaller.SessionParams::class.java.getDeclaredField("installFlags").apply { isAccessible = true }
|
||||||
|
}
|
||||||
|
private val getListMethod by lazy {
|
||||||
|
Class.forName("android.content.pm.ParceledListSlice").getMethod("getList")
|
||||||
|
}
|
||||||
|
|
||||||
private fun getPackageManager(): Any {
|
private fun getPackageManager(): Any {
|
||||||
val binder = SystemServiceHelperCompat.getSystemService("package") ?: throw IllegalStateException("package service not available")
|
val binder = SystemServiceHelperCompat.getSystemService("package")
|
||||||
val stubClass = Class.forName("android.content.pm.IPackageManager\$Stub")
|
?: throw IllegalStateException("package service not available")
|
||||||
val asInterface = stubClass.getMethod("asInterface", IBinder::class.java)
|
return asInterfaceMethod.invoke(null, binder)
|
||||||
return asInterface.invoke(null, binder) ?: throw IllegalStateException("IPackageManager is null")
|
?: throw IllegalStateException("IPackageManager is null")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInstalledPackages(flags: Int, userId: Int): List<PackageInfo> {
|
fun getInstalledPackages(flags: Int, userId: Int): List<PackageInfo> {
|
||||||
val iPackageManager = getPackageManager()
|
val iPackageManager = getPackageManager()
|
||||||
val iPackageManagerClass = Class.forName("android.content.pm.IPackageManager")
|
|
||||||
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
val method = iPackageManagerClass.getMethod(
|
getInstalledPackagesMethodLong.invoke(iPackageManager, flags.toLong(), userId)
|
||||||
"getInstalledPackages",
|
|
||||||
Long::class.javaPrimitiveType,
|
|
||||||
Int::class.javaPrimitiveType,
|
|
||||||
)
|
|
||||||
method.invoke(iPackageManager, flags.toLong(), userId)
|
|
||||||
} else {
|
} else {
|
||||||
val method = iPackageManagerClass.getMethod(
|
getInstalledPackagesMethodInt.invoke(iPackageManager, flags, userId)
|
||||||
"getInstalledPackages",
|
|
||||||
Int::class.javaPrimitiveType,
|
|
||||||
Int::class.javaPrimitiveType,
|
|
||||||
)
|
|
||||||
method.invoke(iPackageManager, flags, userId)
|
|
||||||
}
|
}
|
||||||
return extractPackageList(result)
|
return extractPackageList(result)
|
||||||
}
|
}
|
||||||
@@ -62,9 +99,6 @@ object PrivilegedServiceUtils {
|
|||||||
|
|
||||||
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||||
params.setAppPackageName(BuildConfig.APPLICATION_ID)
|
params.setAppPackageName(BuildConfig.APPLICATION_ID)
|
||||||
// Set INSTALL_REPLACE_EXISTING flag (value = 2)
|
|
||||||
val installFlagsField = PackageInstaller.SessionParams::class.java.getDeclaredField("installFlags")
|
|
||||||
installFlagsField.isAccessible = true
|
|
||||||
installFlagsField.setInt(params, installFlagsField.getInt(params) or 2)
|
installFlagsField.setInt(params, installFlagsField.getInt(params) or 2)
|
||||||
val sessionId = packageInstaller.createSession(params)
|
val sessionId = packageInstaller.createSession(params)
|
||||||
|
|
||||||
@@ -106,9 +140,7 @@ object PrivilegedServiceUtils {
|
|||||||
|
|
||||||
private fun getPackageInstaller(): IPackageInstaller {
|
private fun getPackageInstaller(): IPackageInstaller {
|
||||||
val iPackageManager = getPackageManager()
|
val iPackageManager = getPackageManager()
|
||||||
val iPackageManagerClass = Class.forName("android.content.pm.IPackageManager")
|
val installer = getPackageInstallerMethod.invoke(iPackageManager) as IPackageInstaller
|
||||||
val method = iPackageManagerClass.getMethod("getPackageInstaller")
|
|
||||||
val installer = method.invoke(iPackageManager) as IPackageInstaller
|
|
||||||
return IPackageInstaller.Stub.asInterface(installer.asBinder())
|
return IPackageInstaller.Stub.asInterface(installer.asBinder())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,23 +151,14 @@ object PrivilegedServiceUtils {
|
|||||||
userId: Int,
|
userId: Int,
|
||||||
): PackageInstaller {
|
): PackageInstaller {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
PackageInstaller::class.java.getConstructor(
|
packageInstallerCtorS.newInstance(installer, installerPackageName, installerAttributionTag, userId)
|
||||||
IPackageInstaller::class.java,
|
|
||||||
String::class.java,
|
|
||||||
String::class.java,
|
|
||||||
Int::class.javaPrimitiveType,
|
|
||||||
).newInstance(installer, installerPackageName, installerAttributionTag, userId)
|
|
||||||
} else {
|
} else {
|
||||||
PackageInstaller::class.java.getConstructor(
|
packageInstallerCtorPre.newInstance(installer, installerPackageName, userId)
|
||||||
IPackageInstaller::class.java,
|
|
||||||
String::class.java,
|
|
||||||
Int::class.javaPrimitiveType,
|
|
||||||
).newInstance(installer, installerPackageName, userId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSession(session: IPackageInstallerSession): PackageInstaller.Session {
|
private fun createSession(session: IPackageInstallerSession): PackageInstaller.Session {
|
||||||
return PackageInstaller.Session::class.java.getConstructor(IPackageInstallerSession::class.java).newInstance(session)
|
return sessionCtor.newInstance(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createIntentSender(onResult: (Intent) -> Unit): IntentSender {
|
private fun createIntentSender(onResult: (Intent) -> Unit): IntentSender {
|
||||||
@@ -152,13 +175,12 @@ object PrivilegedServiceUtils {
|
|||||||
onResult(intent)
|
onResult(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return IntentSender::class.java.getConstructor(IIntentSender::class.java).newInstance(sender)
|
return intentSenderCtor.newInstance(sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun extractPackageList(parceledListSlice: Any?): List<PackageInfo> {
|
private fun extractPackageList(parceledListSlice: Any?): List<PackageInfo> {
|
||||||
if (parceledListSlice == null) return emptyList()
|
if (parceledListSlice == null) return emptyList()
|
||||||
val getListMethod = parceledListSlice.javaClass.getMethod("getList")
|
|
||||||
val list = getListMethod.invoke(parceledListSlice) as? List<*>
|
val list = getListMethod.invoke(parceledListSlice) as? List<*>
|
||||||
return list?.filterIsInstance<PackageInfo>() ?: emptyList()
|
return list?.filterIsInstance<PackageInfo>() ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package io.nekohasekai.sfa.xposed
|
package io.nekohasekai.sfa.xposed
|
||||||
|
|
||||||
object HookModuleVersion {
|
object HookModuleVersion {
|
||||||
const val CURRENT = 2
|
const val CURRENT = 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ package io.nekohasekai.sfa.xposed
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import java.lang.reflect.Method
|
||||||
import io.nekohasekai.sfa.BuildConfig
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
object PrivilegeChecker {
|
object PrivilegeChecker {
|
||||||
@@ -21,6 +20,13 @@ object PrivilegeChecker {
|
|||||||
private val exemptCache = ConcurrentHashMap<Int, Boolean>()
|
private val exemptCache = ConcurrentHashMap<Int, Boolean>()
|
||||||
private val privilegedCache = ConcurrentHashMap<Int, Boolean>()
|
private val privilegedCache = ConcurrentHashMap<Int, Boolean>()
|
||||||
|
|
||||||
|
private val appGlobalsClass by lazy { Class.forName("android.app.AppGlobals") }
|
||||||
|
private val getPackageManagerMethod by lazy { appGlobalsClass.getMethod("getPackageManager") }
|
||||||
|
private var getPackagesForUidMethod: Method? = null
|
||||||
|
private var checkUidPermissionMethod: Method? = null
|
||||||
|
private var getApplicationInfoMethodLong: Method? = null
|
||||||
|
private var getApplicationInfoMethodInt: Method? = null
|
||||||
|
|
||||||
fun isPrivilegedUid(uid: Int): Boolean {
|
fun isPrivilegedUid(uid: Int): Boolean {
|
||||||
if (uid < Process.FIRST_APPLICATION_UID) {
|
if (uid < Process.FIRST_APPLICATION_UID) {
|
||||||
return true
|
return true
|
||||||
@@ -44,9 +50,16 @@ object PrivilegeChecker {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val checkMethod = checkUidPermissionMethod ?: run {
|
||||||
|
pm.javaClass.getMethod(
|
||||||
|
"checkUidPermission",
|
||||||
|
String::class.java,
|
||||||
|
Int::class.javaPrimitiveType
|
||||||
|
).also { checkUidPermissionMethod = it }
|
||||||
|
}
|
||||||
for (permission in privilegedPermissions) {
|
for (permission in privilegedPermissions) {
|
||||||
val result = try {
|
val result = try {
|
||||||
XposedHelpers.callMethod(pm, "checkUidPermission", permission, uid) as? Int
|
checkMethod.invoke(pm, permission, uid) as? Int
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -83,7 +96,11 @@ object PrivilegeChecker {
|
|||||||
private fun getPackagesForUid(uid: Int): List<String> {
|
private fun getPackagesForUid(uid: Int): List<String> {
|
||||||
val pm = getPackageManager() ?: return emptyList()
|
val pm = getPackageManager() ?: return emptyList()
|
||||||
return try {
|
return try {
|
||||||
val method = pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType)
|
val method = getPackagesForUidMethod ?: run {
|
||||||
|
pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType).also {
|
||||||
|
getPackagesForUidMethod = it
|
||||||
|
}
|
||||||
|
}
|
||||||
val result = method.invoke(pm, uid)
|
val result = method.invoke(pm, uid)
|
||||||
when (result) {
|
when (result) {
|
||||||
is Array<*> -> result.filterIsInstance<String>()
|
is Array<*> -> result.filterIsInstance<String>()
|
||||||
@@ -97,9 +114,7 @@ object PrivilegeChecker {
|
|||||||
|
|
||||||
private fun getPackageManager(): Any? {
|
private fun getPackageManager(): Any? {
|
||||||
return try {
|
return try {
|
||||||
val appGlobals = Class.forName("android.app.AppGlobals")
|
getPackageManagerMethod.invoke(null)
|
||||||
val method = appGlobals.getMethod("getPackageManager")
|
|
||||||
method.invoke(null)
|
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -107,10 +122,26 @@ object PrivilegeChecker {
|
|||||||
|
|
||||||
private fun getApplicationInfo(pm: Any, pkg: String, userId: Int): ApplicationInfo? {
|
private fun getApplicationInfo(pm: Any, pkg: String, userId: Int): ApplicationInfo? {
|
||||||
return try {
|
return try {
|
||||||
XposedHelpers.callMethod(pm, "getApplicationInfo", pkg, 0, userId) as? ApplicationInfo
|
val method = getApplicationInfoMethodInt ?: run {
|
||||||
|
pm.javaClass.getMethod(
|
||||||
|
"getApplicationInfo",
|
||||||
|
String::class.java,
|
||||||
|
Int::class.javaPrimitiveType,
|
||||||
|
Int::class.javaPrimitiveType
|
||||||
|
).also { getApplicationInfoMethodInt = it }
|
||||||
|
}
|
||||||
|
method.invoke(pm, pkg, 0, userId) as? ApplicationInfo
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
try {
|
try {
|
||||||
XposedHelpers.callMethod(pm, "getApplicationInfo", pkg, 0L, userId) as? ApplicationInfo
|
val method = getApplicationInfoMethodLong ?: run {
|
||||||
|
pm.javaClass.getMethod(
|
||||||
|
"getApplicationInfo",
|
||||||
|
String::class.java,
|
||||||
|
Long::class.javaPrimitiveType,
|
||||||
|
Int::class.javaPrimitiveType
|
||||||
|
).also { getApplicationInfoMethodLong = it }
|
||||||
|
}
|
||||||
|
method.invoke(pm, pkg, 0L, userId) as? ApplicationInfo
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package io.nekohasekai.sfa.xposed
|
package io.nekohasekai.sfa.xposed
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.reflect.Method
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import io.nekohasekai.sfa.xposed.HookErrorStore
|
|
||||||
|
|
||||||
object PrivilegeSettingsStore {
|
object PrivilegeSettingsStore {
|
||||||
private const val SETTINGS_DIR = "/data/system/sing-box"
|
private const val SETTINGS_DIR = "/data/system/sing-box"
|
||||||
@@ -17,6 +17,10 @@ object PrivilegeSettingsStore {
|
|||||||
private var interfacePrefix = "en"
|
private var interfacePrefix = "en"
|
||||||
private val uidCache = ConcurrentHashMap<Int, Boolean>()
|
private val uidCache = ConcurrentHashMap<Int, Boolean>()
|
||||||
|
|
||||||
|
private val appGlobalsClass by lazy { Class.forName("android.app.AppGlobals") }
|
||||||
|
private val getPackageManagerMethod by lazy { appGlobalsClass.getMethod("getPackageManager") }
|
||||||
|
private var getPackagesForUidMethod: Method? = null
|
||||||
|
|
||||||
fun update(
|
fun update(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
packages: Set<String>,
|
packages: Set<String>,
|
||||||
@@ -110,7 +114,11 @@ object PrivilegeSettingsStore {
|
|||||||
private fun getPackagesForUid(uid: Int): List<String> {
|
private fun getPackagesForUid(uid: Int): List<String> {
|
||||||
val pm = getPackageManager() ?: return emptyList()
|
val pm = getPackageManager() ?: return emptyList()
|
||||||
return try {
|
return try {
|
||||||
val method = pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType)
|
val method = getPackagesForUidMethod ?: run {
|
||||||
|
pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType).also {
|
||||||
|
getPackagesForUidMethod = it
|
||||||
|
}
|
||||||
|
}
|
||||||
val result = method.invoke(pm, uid)
|
val result = method.invoke(pm, uid)
|
||||||
when (result) {
|
when (result) {
|
||||||
is Array<*> -> result.filterIsInstance<String>()
|
is Array<*> -> result.filterIsInstance<String>()
|
||||||
@@ -125,9 +133,7 @@ object PrivilegeSettingsStore {
|
|||||||
|
|
||||||
private fun getPackageManager(): Any? {
|
private fun getPackageManager(): Any? {
|
||||||
return try {
|
return try {
|
||||||
val appGlobals = Class.forName("android.app.AppGlobals")
|
getPackageManagerMethod.invoke(null)
|
||||||
val method = appGlobals.getMethod("getPackageManager")
|
|
||||||
method.invoke(null)
|
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
HookErrorStore.e("PrivilegeSettingsStore", "getPackageManager failed", e)
|
HookErrorStore.e("PrivilegeSettingsStore", "getPackageManager failed", e)
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.content.pm.PackageManager
|
|||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import io.nekohasekai.sfa.BuildConfig
|
import io.nekohasekai.sfa.BuildConfig
|
||||||
|
import java.lang.reflect.Method
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
object VpnAppStore {
|
object VpnAppStore {
|
||||||
@@ -20,6 +21,16 @@ object VpnAppStore {
|
|||||||
private val uidVpnCache = ConcurrentHashMap<Int, CacheEntry<Boolean>>()
|
private val uidVpnCache = ConcurrentHashMap<Int, CacheEntry<Boolean>>()
|
||||||
private val uidPackagesCache = ConcurrentHashMap<Int, CacheEntry<List<String>>>()
|
private val uidPackagesCache = ConcurrentHashMap<Int, CacheEntry<List<String>>>()
|
||||||
|
|
||||||
|
private val appGlobalsClass by lazy { Class.forName("android.app.AppGlobals") }
|
||||||
|
private val getPackageManagerMethod by lazy { appGlobalsClass.getMethod("getPackageManager") }
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var pmClass: Class<*>? = null
|
||||||
|
private var getPackagesForUidMethod: Method? = null
|
||||||
|
private var getInstalledPackagesMethodLong: Method? = null
|
||||||
|
private var getInstalledPackagesMethodInt: Method? = null
|
||||||
|
private var getListMethod: Method? = null
|
||||||
|
|
||||||
fun isVpnUid(uid: Int): Boolean {
|
fun isVpnUid(uid: Int): Boolean {
|
||||||
val now = SystemClock.uptimeMillis()
|
val now = SystemClock.uptimeMillis()
|
||||||
val cached = uidVpnCache[uid]
|
val cached = uidVpnCache[uid]
|
||||||
@@ -57,7 +68,11 @@ object VpnAppStore {
|
|||||||
val result = binderLocalScope {
|
val result = binderLocalScope {
|
||||||
val pm = getPackageManager() ?: return@binderLocalScope emptyList<String>()
|
val pm = getPackageManager() ?: return@binderLocalScope emptyList<String>()
|
||||||
try {
|
try {
|
||||||
val method = pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType)
|
val method = getPackagesForUidMethod ?: run {
|
||||||
|
pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType).also {
|
||||||
|
getPackagesForUidMethod = it
|
||||||
|
}
|
||||||
|
}
|
||||||
when (val raw = method.invoke(pm, uid)) {
|
when (val raw = method.invoke(pm, uid)) {
|
||||||
is Array<*> -> raw.filterIsInstance<String>()
|
is Array<*> -> raw.filterIsInstance<String>()
|
||||||
is List<*> -> raw.filterIsInstance<String>()
|
is List<*> -> raw.filterIsInstance<String>()
|
||||||
@@ -108,19 +123,23 @@ object VpnAppStore {
|
|||||||
|
|
||||||
private fun getInstalledPackagesCompat(pm: Any, flags: Long, userId: Int): List<PackageInfo> {
|
private fun getInstalledPackagesCompat(pm: Any, flags: Long, userId: Int): List<PackageInfo> {
|
||||||
val result = try {
|
val result = try {
|
||||||
val method = pm.javaClass.getMethod(
|
val method = getInstalledPackagesMethodLong ?: run {
|
||||||
"getInstalledPackages",
|
pm.javaClass.getMethod(
|
||||||
Long::class.javaPrimitiveType,
|
"getInstalledPackages",
|
||||||
Int::class.javaPrimitiveType,
|
Long::class.javaPrimitiveType,
|
||||||
)
|
Int::class.javaPrimitiveType,
|
||||||
|
).also { getInstalledPackagesMethodLong = it }
|
||||||
|
}
|
||||||
method.invoke(pm, flags, userId)
|
method.invoke(pm, flags, userId)
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
try {
|
try {
|
||||||
val method = pm.javaClass.getMethod(
|
val method = getInstalledPackagesMethodInt ?: run {
|
||||||
"getInstalledPackages",
|
pm.javaClass.getMethod(
|
||||||
Int::class.javaPrimitiveType,
|
"getInstalledPackages",
|
||||||
Int::class.javaPrimitiveType,
|
Int::class.javaPrimitiveType,
|
||||||
)
|
Int::class.javaPrimitiveType,
|
||||||
|
).also { getInstalledPackagesMethodInt = it }
|
||||||
|
}
|
||||||
method.invoke(pm, flags.toInt(), userId)
|
method.invoke(pm, flags.toInt(), userId)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
HookErrorStore.e("VpnAppStore", "getInstalledPackages failed", e)
|
HookErrorStore.e("VpnAppStore", "getInstalledPackages failed", e)
|
||||||
@@ -137,9 +156,7 @@ object VpnAppStore {
|
|||||||
|
|
||||||
private fun getPackageManager(): Any? {
|
private fun getPackageManager(): Any? {
|
||||||
return try {
|
return try {
|
||||||
val appGlobals = Class.forName("android.app.AppGlobals")
|
getPackageManagerMethod.invoke(null)
|
||||||
val method = appGlobals.getMethod("getPackageManager")
|
|
||||||
method.invoke(null)
|
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
HookErrorStore.e("VpnAppStore", "getPackageManager failed", e)
|
HookErrorStore.e("VpnAppStore", "getPackageManager failed", e)
|
||||||
null
|
null
|
||||||
@@ -161,7 +178,9 @@ object VpnAppStore {
|
|||||||
return raw.filterIsInstance<PackageInfo>()
|
return raw.filterIsInstance<PackageInfo>()
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
val method = raw.javaClass.getMethod("getList")
|
val method = getListMethod ?: run {
|
||||||
|
raw.javaClass.getMethod("getList").also { getListMethod = it }
|
||||||
|
}
|
||||||
val list = method.invoke(raw)
|
val list = method.invoke(raw)
|
||||||
if (list is List<*>) {
|
if (list is List<*>) {
|
||||||
list.filterIsInstance<PackageInfo>()
|
list.filterIsInstance<PackageInfo>()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.nekohasekai.sfa.xposed
|
|||||||
import android.net.LinkProperties
|
import android.net.LinkProperties
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.net.NetworkInfo
|
import android.net.NetworkInfo
|
||||||
|
import android.net.ProxyInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
@@ -14,6 +15,22 @@ object VpnSanitizer {
|
|||||||
"tun",
|
"tun",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val getStackedLinksMethod by lazy {
|
||||||
|
LinkProperties::class.java.getMethod("getStackedLinks")
|
||||||
|
}
|
||||||
|
private val removeStackedLinkMethod by lazy {
|
||||||
|
LinkProperties::class.java.getMethod("removeStackedLink", String::class.java)
|
||||||
|
}
|
||||||
|
private val setHttpProxyMethod by lazy {
|
||||||
|
LinkProperties::class.java.getMethod("setHttpProxy", ProxyInfo::class.java)
|
||||||
|
}
|
||||||
|
private val removeTransportTypeMethod by lazy {
|
||||||
|
NetworkCapabilities::class.java.getMethod("removeTransportType", Int::class.javaPrimitiveType)
|
||||||
|
}
|
||||||
|
private val addCapabilityMethod by lazy {
|
||||||
|
NetworkCapabilities::class.java.getMethod("addCapability", Int::class.javaPrimitiveType)
|
||||||
|
}
|
||||||
|
|
||||||
fun shouldHide(uid: Int): Boolean {
|
fun shouldHide(uid: Int): Boolean {
|
||||||
if (!PrivilegeSettingsStore.shouldHideUid(uid)) {
|
if (!PrivilegeSettingsStore.shouldHideUid(uid)) {
|
||||||
return false
|
return false
|
||||||
@@ -47,13 +64,13 @@ object VpnSanitizer {
|
|||||||
lp.setInterfaceName(null)
|
lp.setInterfaceName(null)
|
||||||
}
|
}
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val stacked = XposedHelpers.callMethod(lp, "getStackedLinks") as? List<LinkProperties>
|
val stacked = getStackedLinksMethod.invoke(lp) as? List<LinkProperties>
|
||||||
if (!stacked.isNullOrEmpty()) {
|
if (!stacked.isNullOrEmpty()) {
|
||||||
for (link in stacked) {
|
for (link in stacked) {
|
||||||
clearHttpProxy(link)
|
clearHttpProxy(link)
|
||||||
val name = link.interfaceName
|
val iface = link.interfaceName
|
||||||
if (isVpnInterface(name)) {
|
if (iface != null && isVpnInterface(iface)) {
|
||||||
XposedHelpers.callMethod(lp, "removeStackedLink", name)
|
removeStackedLinkMethod.invoke(lp, iface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,8 +82,7 @@ object VpnSanitizer {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val stacked = XposedHelpers.callMethod(lp, "getStackedLinks") as? List<LinkProperties>
|
val stacked = getStackedLinksMethod.invoke(lp) as? List<LinkProperties> ?: return false
|
||||||
?: return false
|
|
||||||
return stacked.any { isVpnInterface(it.interfaceName) }
|
return stacked.any { isVpnInterface(it.interfaceName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +93,8 @@ object VpnSanitizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun sanitizeTransport(caps: NetworkCapabilities) {
|
private fun sanitizeTransport(caps: NetworkCapabilities) {
|
||||||
XposedHelpers.callMethod(caps, "removeTransportType", NetworkCapabilities.TRANSPORT_VPN)
|
removeTransportTypeMethod.invoke(caps, NetworkCapabilities.TRANSPORT_VPN)
|
||||||
XposedHelpers.callMethod(caps, "addCapability", NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
addCapabilityMethod.invoke(caps, NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearUnderlyingNetworks(caps: NetworkCapabilities) {
|
private fun clearUnderlyingNetworks(caps: NetworkCapabilities) {
|
||||||
@@ -110,7 +126,7 @@ object VpnSanitizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun clearHttpProxy(lp: LinkProperties) {
|
private fun clearHttpProxy(lp: LinkProperties) {
|
||||||
XposedHelpers.callMethod(lp, "setHttpProxy", null)
|
setHttpProxyMethod.invoke(lp, null as ProxyInfo?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cloneLinkProperties(source: LinkProperties): LinkProperties {
|
fun cloneLinkProperties(source: LinkProperties): LinkProperties {
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ class XposedInit(
|
|||||||
param: XposedModuleInterface.ModuleLoadedParam,
|
param: XposedModuleInterface.ModuleLoadedParam,
|
||||||
) : XposedModule(base, param) {
|
) : XposedModule(base, param) {
|
||||||
|
|
||||||
|
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
|
||||||
|
private val currentActivityThreadMethod by lazy { activityThreadClass.getMethod("currentActivityThread") }
|
||||||
|
private val getSystemContextMethod by lazy { activityThreadClass.getMethod("getSystemContext") }
|
||||||
|
|
||||||
override fun onSystemServerLoaded(param: XposedModuleInterface.SystemServerLoadedParam) {
|
override fun onSystemServerLoaded(param: XposedModuleInterface.SystemServerLoadedParam) {
|
||||||
val systemContext = resolveSystemContext()
|
val systemContext = resolveSystemContext()
|
||||||
HookErrorStore.i("XposedInit", "handleSystemServerLoaded")
|
HookErrorStore.i("XposedInit", "handleSystemServerLoaded")
|
||||||
@@ -45,9 +49,8 @@ class XposedInit(
|
|||||||
|
|
||||||
private fun resolveSystemContext(): Context? {
|
private fun resolveSystemContext(): Context? {
|
||||||
return try {
|
return try {
|
||||||
val activityThread = Class.forName("android.app.ActivityThread")
|
val currentThread = currentActivityThreadMethod.invoke(null)
|
||||||
val currentThread = activityThread.getMethod("currentActivityThread").invoke(null)
|
getSystemContextMethod.invoke(currentThread) as? Context
|
||||||
activityThread.getMethod("getSystemContext").invoke(currentThread) as? Context
|
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
HookErrorStore.e("XposedInit", "resolveSystemContext failed", e)
|
HookErrorStore.e("XposedInit", "resolveSystemContext failed", e)
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import io.nekohasekai.sfa.xposed.VpnAppStore
|
|||||||
import io.nekohasekai.sfa.xposed.VpnSanitizer
|
import io.nekohasekai.sfa.xposed.VpnSanitizer
|
||||||
import io.nekohasekai.sfa.xposed.hooks.SafeMethodHook
|
import io.nekohasekai.sfa.xposed.hooks.SafeMethodHook
|
||||||
import io.nekohasekai.sfa.xposed.hooks.XHook
|
import io.nekohasekai.sfa.xposed.hooks.XHook
|
||||||
|
import java.lang.reflect.Method
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
@@ -33,6 +34,17 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
|||||||
lateinit var cls: Class<*>
|
lateinit var cls: Class<*>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private val serviceManagerClass by lazy { Class.forName("android.os.ServiceManager") }
|
||||||
|
private val checkServiceMethod by lazy { serviceManagerClass.getMethod("checkService", String::class.java) }
|
||||||
|
|
||||||
|
private var getVpnForUidMethod: Method? = null
|
||||||
|
private lateinit var getVpnUnderlyingNetworksMethod: Method
|
||||||
|
private lateinit var getNetworkAgentInfoForNetworkMethod: Method
|
||||||
|
private var getFilteredNetworkInfoMethod: Method? = null
|
||||||
|
private lateinit var getDefaultNetworkMethod: Method
|
||||||
|
private lateinit var isVPNMethod: Method
|
||||||
|
private var networkMethod: Method? = null
|
||||||
|
|
||||||
override fun injectHook() {
|
override fun injectHook() {
|
||||||
val foundClass = findConnectivityServiceClass()
|
val foundClass = findConnectivityServiceClass()
|
||||||
if (foundClass != null) {
|
if (foundClass != null) {
|
||||||
@@ -50,6 +62,7 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
|||||||
}
|
}
|
||||||
this.cls = cls
|
this.cls = cls
|
||||||
connectivityClassLoader = cls.classLoader ?: classLoader
|
connectivityClassLoader = cls.classLoader ?: classLoader
|
||||||
|
initMethodCache()
|
||||||
HookErrorStore.i(
|
HookErrorStore.i(
|
||||||
SOURCE,
|
SOURCE,
|
||||||
"Installing ConnectivityService hooks ($source) cls=${cls.name} loader=${connectivityClassLoader.javaClass.name}",
|
"Installing ConnectivityService hooks ($source) cls=${cls.name} loader=${connectivityClassLoader.javaClass.name}",
|
||||||
@@ -72,6 +85,38 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
|||||||
HookErrorStore.i(SOURCE, "Hooked ConnectivityService ($source) cls=${cls.name}")
|
HookErrorStore.i(SOURCE, "Hooked ConnectivityService ($source) cls=${cls.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initMethodCache() {
|
||||||
|
val intType = Int::class.javaPrimitiveType!!
|
||||||
|
val booleanType = Boolean::class.javaPrimitiveType!!
|
||||||
|
val naiClass = resolveConnectivityModuleClass("NetworkAgentInfo", "connectivity")
|
||||||
|
if (sdkInt >= 31) {
|
||||||
|
getVpnForUidMethod = findDeclaredMethod(cls, "getVpnForUid", intType)
|
||||||
|
if (getVpnForUidMethod == null) {
|
||||||
|
HookErrorStore.w(SOURCE, "getVpnForUid not found; falling back to underlying networks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getVpnUnderlyingNetworksMethod = requireDeclaredMethod(cls, "getVpnUnderlyingNetworks", intType)
|
||||||
|
getNetworkAgentInfoForNetworkMethod = requireDeclaredMethod(cls, "getNetworkAgentInfoForNetwork", Network::class.java)
|
||||||
|
if (sdkInt >= 31) {
|
||||||
|
getFilteredNetworkInfoMethod = findDeclaredMethod(
|
||||||
|
cls,
|
||||||
|
"getFilteredNetworkInfo",
|
||||||
|
naiClass,
|
||||||
|
intType,
|
||||||
|
booleanType
|
||||||
|
)
|
||||||
|
if (getFilteredNetworkInfoMethod == null) {
|
||||||
|
HookErrorStore.w(SOURCE, "getFilteredNetworkInfo not found; network info sanitization disabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDefaultNetworkMethod = requireDeclaredMethod(cls, "getDefaultNetwork")
|
||||||
|
isVPNMethod = requireDeclaredMethod(naiClass, "isVPN")
|
||||||
|
networkMethod = findDeclaredMethod(naiClass, "network")
|
||||||
|
if (networkMethod == null) {
|
||||||
|
HookErrorStore.w(SOURCE, "NetworkAgentInfo.network() not found; falling back to field access")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// region Service Discovery
|
// region Service Discovery
|
||||||
|
|
||||||
private fun findConnectivityServiceClass(): Class<*>? {
|
private fun findConnectivityServiceClass(): Class<*>? {
|
||||||
@@ -229,9 +274,7 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
|||||||
private fun tryHookFromServiceManager() {
|
private fun tryHookFromServiceManager() {
|
||||||
if (hooked.get()) return
|
if (hooked.get()) return
|
||||||
val binder = try {
|
val binder = try {
|
||||||
val serviceManager = Class.forName("android.os.ServiceManager")
|
checkServiceMethod.invoke(null, Context.CONNECTIVITY_SERVICE) as? IBinder
|
||||||
val checkService = serviceManager.getMethod("checkService", String::class.java)
|
|
||||||
checkService.invoke(null, Context.CONNECTIVITY_SERVICE) as? IBinder
|
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -365,56 +408,88 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
|||||||
|
|
||||||
fun hasVpnForUid(connectivityService: Any, uid: Int): Boolean {
|
fun hasVpnForUid(connectivityService: Any, uid: Int): Boolean {
|
||||||
if (sdkInt >= 31) {
|
if (sdkInt >= 31) {
|
||||||
return XposedHelpers.callMethod(connectivityService, "getVpnForUid", uid) != null
|
val vpnForUidMethod = getVpnForUidMethod
|
||||||
|
if (vpnForUidMethod != null) {
|
||||||
|
return vpnForUidMethod.invoke(connectivityService, uid) != null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val networks = XposedHelpers.callMethod(connectivityService, "getVpnUnderlyingNetworks", uid)
|
val networks = getVpnUnderlyingNetworksMethod.invoke(connectivityService, uid) as? Array<Network>
|
||||||
as? Array<Network>
|
|
||||||
return networks != null && networks.isNotEmpty()
|
return networks != null && networks.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isVpnNetwork(connectivityService: Any, network: Network): Boolean {
|
fun isVpnNetwork(connectivityService: Any, network: Network): Boolean {
|
||||||
val nai = XposedHelpers.callMethod(connectivityService, "getNetworkAgentInfoForNetwork", network)
|
val nai = getNetworkAgentInfoForNetworkMethod.invoke(connectivityService, network) ?: return false
|
||||||
?: return false
|
|
||||||
return isVpnNai(nai)
|
return isVpnNai(nai)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isVpnNai(nai: Any): Boolean {
|
fun isVpnNai(nai: Any): Boolean {
|
||||||
return XposedHelpers.callMethod(nai, "isVPN") as Boolean
|
return isVPNMethod.invoke(nai) as Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUnderlyingNetwork(connectivityService: Any, uid: Int): Network? {
|
fun getUnderlyingNetwork(connectivityService: Any, uid: Int): Network? {
|
||||||
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
||||||
return XposedHelpers.callMethod(nai, "network") as Network?
|
val method = networkMethod
|
||||||
|
return if (method != null) {
|
||||||
|
method.invoke(nai) as Network?
|
||||||
|
} else {
|
||||||
|
XposedHelpers.getObjectField(nai, "network") as? Network
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUnderlyingLinkProperties(connectivityService: Any, uid: Int): LinkProperties? {
|
fun getUnderlyingLinkProperties(connectivityService: Any, uid: Int): LinkProperties? {
|
||||||
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
||||||
val lp = XposedHelpers.getObjectField(nai, "linkProperties") as LinkProperties?
|
val lp = XposedHelpers.getObjectField(nai, "linkProperties") as LinkProperties? ?: return null
|
||||||
?: return null
|
|
||||||
return VpnSanitizer.cloneLinkProperties(lp)
|
return VpnSanitizer.cloneLinkProperties(lp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUnderlyingNetworkInfo(connectivityService: Any, uid: Int): NetworkInfo? {
|
fun getUnderlyingNetworkInfo(connectivityService: Any, uid: Int): NetworkInfo? {
|
||||||
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
||||||
return XposedHelpers.callMethod(connectivityService, "getFilteredNetworkInfo", nai, uid, false)
|
val method = getFilteredNetworkInfoMethod
|
||||||
as NetworkInfo?
|
if (method != null) {
|
||||||
|
return method.invoke(connectivityService, nai, uid, false) as NetworkInfo?
|
||||||
|
}
|
||||||
|
return XposedHelpers.getObjectField(nai, "networkInfo") as? NetworkInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUnderlyingNai(connectivityService: Any, uid: Int): Any? {
|
fun getUnderlyingNai(connectivityService: Any, uid: Int): Any? {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val networks = XposedHelpers.callMethod(connectivityService, "getVpnUnderlyingNetworks", uid)
|
val networks = getVpnUnderlyingNetworksMethod.invoke(connectivityService, uid) as? Array<Network>
|
||||||
as? Array<Network>
|
|
||||||
if (networks != null && networks.isNotEmpty()) {
|
if (networks != null && networks.isNotEmpty()) {
|
||||||
return XposedHelpers.callMethod(connectivityService, "getNetworkAgentInfoForNetwork", networks[0])
|
return getNetworkAgentInfoForNetworkMethod.invoke(connectivityService, networks[0])
|
||||||
}
|
}
|
||||||
val defaultNai = XposedHelpers.callMethod(connectivityService, "getDefaultNetwork")
|
val defaultNai = getDefaultNetworkMethod.invoke(connectivityService)
|
||||||
if (defaultNai != null && !isVpnNai(defaultNai)) {
|
if (defaultNai != null && !isVpnNai(defaultNai)) {
|
||||||
return defaultNai
|
return defaultNai
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findDeclaredMethod(
|
||||||
|
target: Class<*>,
|
||||||
|
name: String,
|
||||||
|
vararg parameterTypes: Class<*>,
|
||||||
|
): Method? {
|
||||||
|
var current: Class<*>? = target
|
||||||
|
while (current != null) {
|
||||||
|
try {
|
||||||
|
return current.getDeclaredMethod(name, *parameterTypes).apply { isAccessible = true }
|
||||||
|
} catch (_: NoSuchMethodException) {
|
||||||
|
current = current.superclass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requireDeclaredMethod(
|
||||||
|
target: Class<*>,
|
||||||
|
name: String,
|
||||||
|
vararg parameterTypes: Class<*>,
|
||||||
|
): Method {
|
||||||
|
return findDeclaredMethod(target, name, *parameterTypes)
|
||||||
|
?: throw NoSuchMethodException("${target.name}#$name")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a class from the Connectivity module, handling APEX package rewriting.
|
* Resolves a class from the Connectivity module, handling APEX package rewriting.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,6 +17,25 @@ class HookNetworkCapabilitiesWriteToParcel : XHook {
|
|||||||
private const val SOURCE = "HookNCWriteToParcel"
|
private const val SOURCE = "HookNCWriteToParcel"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val copyCtor by lazy {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||||
|
NetworkCapabilities::class.java.getDeclaredConstructor(
|
||||||
|
NetworkCapabilities::class.java,
|
||||||
|
Long::class.javaPrimitiveType
|
||||||
|
).apply { isAccessible = true }
|
||||||
|
} else {
|
||||||
|
NetworkCapabilities::class.java.getDeclaredConstructor(
|
||||||
|
NetworkCapabilities::class.java
|
||||||
|
).apply { isAccessible = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val removeTransportTypeMethod by lazy {
|
||||||
|
NetworkCapabilities::class.java.getMethod("removeTransportType", Int::class.javaPrimitiveType)
|
||||||
|
}
|
||||||
|
private val addCapabilityMethod by lazy {
|
||||||
|
NetworkCapabilities::class.java.getMethod("addCapability", Int::class.javaPrimitiveType)
|
||||||
|
}
|
||||||
|
|
||||||
private val inWrite = ThreadLocal.withInitial { false }
|
private val inWrite = ThreadLocal.withInitial { false }
|
||||||
|
|
||||||
override fun injectHook() {
|
override fun injectHook() {
|
||||||
@@ -58,22 +77,15 @@ class HookNetworkCapabilitiesWriteToParcel : XHook {
|
|||||||
|
|
||||||
private fun copyNetworkCapabilities(caps: NetworkCapabilities): NetworkCapabilities {
|
private fun copyNetworkCapabilities(caps: NetworkCapabilities): NetworkCapabilities {
|
||||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||||
val ctor = NetworkCapabilities::class.java.getDeclaredConstructor(
|
copyCtor.newInstance(caps, 0L) as NetworkCapabilities
|
||||||
NetworkCapabilities::class.java,
|
|
||||||
Long::class.javaPrimitiveType!!
|
|
||||||
)
|
|
||||||
ctor.isAccessible = true
|
|
||||||
ctor.newInstance(caps, 0L)
|
|
||||||
} else {
|
} else {
|
||||||
val ctor = NetworkCapabilities::class.java.getDeclaredConstructor(NetworkCapabilities::class.java)
|
copyCtor.newInstance(caps) as NetworkCapabilities
|
||||||
ctor.isAccessible = true
|
|
||||||
ctor.newInstance(caps)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sanitizeNetworkCapabilities(caps: NetworkCapabilities) {
|
private fun sanitizeNetworkCapabilities(caps: NetworkCapabilities) {
|
||||||
XposedHelpers.callMethod(caps, "removeTransportType", NetworkCapabilities.TRANSPORT_VPN)
|
removeTransportTypeMethod.invoke(caps, NetworkCapabilities.TRANSPORT_VPN)
|
||||||
XposedHelpers.callMethod(caps, "addCapability", NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
addCapabilityMethod.invoke(caps, NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||||
clearVpnTransportInfo(caps)
|
clearVpnTransportInfo(caps)
|
||||||
clearUnderlyingNetworks(caps)
|
clearUnderlyingNetworks(caps)
|
||||||
clearOwnerUid(caps)
|
clearOwnerUid(caps)
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ class HookNetworkInterfaceGetName(private val classLoader: ClassLoader) : XHook
|
|||||||
private const val IFF_UP = 0x1
|
private const val IFF_UP = 0x1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val netlinkSocketAddressClass by lazy { Class.forName("android.system.NetlinkSocketAddress") }
|
||||||
|
private val netlinkSocketAddressCtor by lazy {
|
||||||
|
netlinkSocketAddressClass.getConstructor(Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
|
||||||
|
}
|
||||||
|
|
||||||
private val seq = AtomicInteger(1)
|
private val seq = AtomicInteger(1)
|
||||||
|
|
||||||
override fun injectHook() {
|
override fun injectHook() {
|
||||||
@@ -223,9 +228,7 @@ class HookNetworkInterfaceGetName(private val classLoader: ClassLoader) : XHook
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNetlinkAddress(): SocketAddress {
|
private fun buildNetlinkAddress(): SocketAddress {
|
||||||
val cls = Class.forName("android.system.NetlinkSocketAddress")
|
return netlinkSocketAddressCtor.newInstance(0, 0) as SocketAddress
|
||||||
val ctor = cls.getConstructor(Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
|
|
||||||
return ctor.newInstance(0, 0) as SocketAddress
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildLinkMessage(
|
private fun buildLinkMessage(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import io.nekohasekai.sfa.xposed.PrivilegeSettingsStore
|
|||||||
import io.nekohasekai.sfa.xposed.VpnAppStore
|
import io.nekohasekai.sfa.xposed.VpnAppStore
|
||||||
import io.nekohasekai.sfa.xposed.hooks.SafeMethodHook
|
import io.nekohasekai.sfa.xposed.hooks.SafeMethodHook
|
||||||
import io.nekohasekai.sfa.xposed.hooks.XHook
|
import io.nekohasekai.sfa.xposed.hooks.XHook
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
class HookPackageManagerGetInstalledPackages(private val classLoader: ClassLoader) : XHook {
|
class HookPackageManagerGetInstalledPackages(private val classLoader: ClassLoader) : XHook {
|
||||||
private companion object {
|
private companion object {
|
||||||
@@ -20,6 +21,10 @@ class HookPackageManagerGetInstalledPackages(private val classLoader: ClassLoade
|
|||||||
private const val PER_USER_RANGE = 100000
|
private const val PER_USER_RANGE = 100000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var lastPackageNameClass: Class<*>? = null
|
||||||
|
private var getPackageNameMethod: Method? = null
|
||||||
|
|
||||||
override fun injectHook() {
|
override fun injectHook() {
|
||||||
val hooked = ArrayList<String>()
|
val hooked = ArrayList<String>()
|
||||||
val sdk = Build.VERSION.SDK_INT
|
val sdk = Build.VERSION.SDK_INT
|
||||||
@@ -238,7 +243,15 @@ class HookPackageManagerGetInstalledPackages(private val classLoader: ClassLoade
|
|||||||
private fun extractPackageName(arg: Any?): String? {
|
private fun extractPackageName(arg: Any?): String? {
|
||||||
if (arg == null) return null
|
if (arg == null) return null
|
||||||
try {
|
try {
|
||||||
val method = arg.javaClass.getMethod("getPackageName")
|
val argClass = arg.javaClass
|
||||||
|
val method = if (lastPackageNameClass == argClass && getPackageNameMethod != null) {
|
||||||
|
getPackageNameMethod!!
|
||||||
|
} else {
|
||||||
|
argClass.getMethod("getPackageName").also {
|
||||||
|
lastPackageNameClass = argClass
|
||||||
|
getPackageNameMethod = it
|
||||||
|
}
|
||||||
|
}
|
||||||
val result = method.invoke(arg) as String?
|
val result = method.invoke(arg) as String?
|
||||||
if (!result.isNullOrEmpty()) {
|
if (!result.isNullOrEmpty()) {
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user